feat(apps): expand the self-serve connection catalog (#12344)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - A useful app store needs accurate and selectable provider
definitions.
> - Local brand assets now cover the expanded provider set.
> - Provider methods differ in transport, authentication, ownership, and
required scope.
> - This pull request expands the catalog and encodes those provider
contracts.
> - The benefit is a larger self-serve store with explicit setup
choices.

## Linked Issues or Issue Description

Refs #11965

This is stack 6 of 11. It depends on stack 5 and replaces another
reviewable part of #11965.

## What Changed

- Add and update provider definitions for the self-serve catalog.
- Add Google Workspace connection methods and capability profiles.
- Add catalog generation, ingestion, URL matching, and contract tests.
- Update legacy key tests to use a provider that still uses header
credentials.

## Verification

- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/tool-access-service.test.ts`
- Result: 164 tests passed.
- `pnpm build`

## Risks

- An incorrect provider definition can offer the wrong setup method.
- Contract tests verify transport, authentication, and provider URL
behavior.
- The change does not add a database migration.

> I checked `ROADMAP.md`. This stack continues the existing app
connection work from #11965 and does not duplicate another planned item.

## Model Used

OpenAI Codex, GPT-5. The runtime model ID and context window were not
exposed. The model used reasoning, tool use, and code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Refs #` or (b)
described the issue in this pull request
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-08-29 12:08:34 -05:00 committed by GitHub
parent 10c4902ffc
commit fcb2e99e8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
81 changed files with 5057 additions and 355 deletions

View File

@ -18,9 +18,19 @@ describe("tool app gallery URL matching", () => {
expect(getAppDefinitionForUrl("https://docs.googleapis.com/drive/v3/files")).toBeNull();
});
it("does not list Google Drive until its OAuth client flow is supported", () => {
expect(CONNECTABLE_APP_DEFINITIONS.map((app) => app.slug)).not.toContain("google-drive");
expect(getAppDefinitionForUrl("https://mcp.google.com/drive")).toBeNull();
it("lists each reviewed Google Workspace MCP endpoint independently", () => {
expect(CONNECTABLE_APP_DEFINITIONS.map((app) => app.slug)).toEqual(expect.arrayContaining([
"gmail",
"google-drive",
"google-docs",
"google-sheets",
"google-slides",
"google-calendar",
"google-chat",
"google-people",
"google-workspace-search",
]));
expect(getAppDefinitionForUrl("https://drivemcp.googleapis.com/mcp/v1")?.slug).toBe("google-drive");
});
it("lists Composio as a connectable API-key app", () => {

View File

@ -4,14 +4,61 @@ import a2 from "./app-definitions/slack.json" with { type: "json" };
import a3 from "./app-definitions/notion.json" with { type: "json" };
import a4 from "./app-definitions/posthog.json" with { type: "json" };
import a5 from "./app-definitions/linear.json" with { type: "json" };
import a6 from "./app-definitions/google-sheets.json" with { type: "json" };
import a7 from "./app-definitions/context7.json" with { type: "json" };
import a6 from "./app-definitions/context7.json" with { type: "json" };
import a7 from "./app-definitions/shopify.json" with { type: "json" };
import a8 from "./app-definitions/composio.json" with { type: "json" };
import a9 from "./app-definitions/oauth-generic.json" with { type: "json" };
import a10 from "./app-definitions/api-key-generic.json" with { type: "json" };
import a11 from "./app-definitions/sentry.json" with { type: "json" };
import a12 from "./app-definitions/vercel.json" with { type: "json" };
import a13 from "./app-definitions/anthropic.json" with { type: "json" };
import a14 from "./app-definitions/gmail.json" with { type: "json" };
import a14 from "./app-definitions/jira.json" with { type: "json" };
import a15 from "./app-definitions/airtable.json" with { type: "json" };
import a16 from "./app-definitions/beehiiv.json" with { type: "json" };
import a17 from "./app-definitions/bitly.json" with { type: "json" };
import a18 from "./app-definitions/candid.json" with { type: "json" };
import a19 from "./app-definitions/cloudflare.json" with { type: "json" };
import a20 from "./app-definitions/cloudinary.json" with { type: "json" };
import a21 from "./app-definitions/coda.json" with { type: "json" };
import a22 from "./app-definitions/hugging-face.json" with { type: "json" };
import a23 from "./app-definitions/kernel.json" with { type: "json" };
import a24 from "./app-definitions/local-falcon.json" with { type: "json" };
import a25 from "./app-definitions/make.json" with { type: "json" };
import a26 from "./app-definitions/manufact.json" with { type: "json" };
import a27 from "./app-definitions/miro.json" with { type: "json" };
import a28 from "./app-definitions/netlify.json" with { type: "json" };
import a29 from "./app-definitions/oreilly.json" with { type: "json" };
import a30 from "./app-definitions/planetscale.json" with { type: "json" };
import a31 from "./app-definitions/resend.json" with { type: "json" };
import a32 from "./app-definitions/ticktick.json" with { type: "json" };
import a33 from "./app-definitions/todoist.json" with { type: "json" };
import a34 from "./app-definitions/webflow.json" with { type: "json" };
import a35 from "./app-definitions/wix.json" with { type: "json" };
import a36 from "./app-definitions/brex.json" with { type: "json" };
import a37 from "./app-definitions/clickhouse.json" with { type: "json" };
import a38 from "./app-definitions/egnyte.json" with { type: "json" };
import a39 from "./app-definitions/embat.json" with { type: "json" };
import a40 from "./app-definitions/mixpanel.json" with { type: "json" };
import a41 from "./app-definitions/postman.json" with { type: "json" };
import a42 from "./app-definitions/razorpay.json" with { type: "json" };
import a43 from "./app-definitions/sanity.json" with { type: "json" };
import a44 from "./app-definitions/stripe.json" with { type: "json" };
import a45 from "./app-definitions/supabase.json" with { type: "json" };
import a46 from "./app-definitions/ticket-tailor.json" with { type: "json" };
import a47 from "./app-definitions/asana.json" with { type: "json" };
import a48 from "./app-definitions/box.json" with { type: "json" };
import a49 from "./app-definitions/mem0.json" with { type: "json" };
import a50 from "./app-definitions/pagerduty.json" with { type: "json" };
import a51 from "./app-definitions/similarweb.json" with { type: "json" };
import a52 from "./app-definitions/xero.json" with { type: "json" };
import a53 from "./app-definitions/gmail.json" with { type: "json" };
import a54 from "./app-definitions/google-drive.json" with { type: "json" };
import a55 from "./app-definitions/google-docs.json" with { type: "json" };
import a56 from "./app-definitions/google-sheets.json" with { type: "json" };
import a57 from "./app-definitions/google-slides.json" with { type: "json" };
import a58 from "./app-definitions/google-calendar.json" with { type: "json" };
import a59 from "./app-definitions/google-chat.json" with { type: "json" };
import a60 from "./app-definitions/google-people.json" with { type: "json" };
import a61 from "./app-definitions/google-workspace-search.json" with { type: "json" };
import type { AppDefinition } from "./types/app-definition.js";
export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14] as AppDefinition[];
export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61] as AppDefinition[];

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,200 @@
import { describe,expect,it } from "vitest";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { APP_DEFINITIONS } from "./app-definitions.generated.js";
import { recommendedDefaultsForApp } from "./app-definitions.js";
import { APP_STORE_DEFINITIONS, APP_STORE_HIDDEN_SLUGS, CONNECTABLE_APP_DEFINITIONS, appSupportsCatalogSetup, getAvailableConnectionMethod, getRecommendedConnectionMethod, recommendedDefaultsForApp, resolveConnectionMethodServerUrl } from "./app-definitions.js";
import { BLOCKED_MCP_PROVIDERS, SELF_SERVE_MCP_CANDIDATES, SELF_SERVE_MCP_RESEARCH } from "./self-serve-mcp-research.js";
import { appDefinitionsSchema } from "./validators/app-definition.js";
describe("AppDefinition catalog",()=>{
it("validates all Wave 1 definitions",()=>expect(()=>appDefinitionsSchema.parse(APP_DEFINITIONS)).not.toThrow());
it("contains the reviewed providers",()=>expect(APP_DEFINITIONS.map((app)=>app.slug)).toEqual(["zapier","github","slack","notion","posthog","linear","google-sheets","context7","composio","oauth-generic","api-key-generic","sentry","vercel","anthropic","gmail"]));
it("contains every established provider plus the reviewed self-serve catalog",()=>{
expect(APP_DEFINITIONS.map((app)=>app.slug)).toEqual(expect.arrayContaining(["zapier","github","slack","notion","posthog","linear","google-sheets","context7","composio","oauth-generic","api-key-generic","sentry","vercel","anthropic","gmail","google-drive","google-docs","google-slides","google-calendar","google-chat","google-people","google-workspace-search"]));
expect(SELF_SERVE_MCP_CANDIDATES).toHaveLength(43);
expect(BLOCKED_MCP_PROVIDERS.map((entry)=>entry.slug)).toEqual(["g2","vercel","zomato"]);
const definitionSlugs=new Set(APP_DEFINITIONS.map((app)=>app.slug));
const connectableSlugs=new Set(CONNECTABLE_APP_DEFINITIONS.map((app)=>app.slug));
expect(SELF_SERVE_MCP_CANDIDATES.filter((entry)=>!definitionSlugs.has(entry.slug))).toEqual([]);
expect(SELF_SERVE_MCP_CANDIDATES.filter((entry)=>!connectableSlugs.has(entry.slug))).toEqual([]);
for(const entry of BLOCKED_MCP_PROVIDERS)expect(connectableSlugs.has(entry.slug)).toBe(false);
});
it("keeps a complete, unique, dated evidence ledger for all 46 researched MCP providers",()=>{
expect(SELF_SERVE_MCP_RESEARCH.verifiedAt).toBe("2026-08-26");
expect(SELF_SERVE_MCP_RESEARCH.entries).toHaveLength(46);
expect(new Set(SELF_SERVE_MCP_RESEARCH.entries.map((entry)=>entry.slug))).toHaveProperty("size",46);
for(const entry of SELF_SERVE_MCP_RESEARCH.entries){
expect(new URL(entry.docsUrl).protocol).toBe("https:");
expect(new URL(entry.serverUrl).protocol).toBe("https:");
expect(entry.authMode).toBeTruthy();
expect(entry.prerequisite.length).toBeGreaterThan(10);
expect(["S1","S2","S3","S4"]).toContain(entry.riskTier);
}
});
it("uses the reviewed current endpoints and configuration modes",()=>{
const method=(slug:string,key?:string)=>APP_DEFINITIONS.find((app)=>app.slug===slug)?.methods.find((candidate)=>!key||candidate.key===key);
expect(method("jira")?.defaults?.serverUrl).toBe("https://mcp.atlassian.com/v1/mcp/authv2");
expect(method("jira")?.defaults?.scopesHint).toEqual([
"read:me",
"read:account",
"offline_access",
"email",
"read:jira-work",
"write:jira-work",
"search:confluence",
"read:confluence-user",
"read:page:confluence",
"write:page:confluence",
"read:comment:confluence",
"write:comment:confluence",
"read:space:confluence",
"read:hierarchical-content:confluence",
"write:component:compass",
"read:component:compass",
"read:scorecard:compass",
"write:scorecard:compass",
"read:event:compass",
"read:metric:compass",
"read:all:twg",
"write:all:twg",
]);
expect(method("cloudinary")?.defaults?.serverUrl).toBe("https://asset-management.mcp.cloudinary.com/mcp");
expect(method("kernel")?.defaults?.serverUrl).toBe("https://mcp.onkernel.com/mcp");
expect(method("resend")?.defaults?.serverUrl).toBe("https://mcp.resend.com/mcp");
expect(method("clickhouse")?.defaults?.serverUrl).toBe("https://mcp.clickhouse.cloud/clickstack");
expect(method("clickhouse")?.tenantFields?.[0]?.transport).toEqual({location:"header",name:"x-service-id"});
expect(method("mem0")).toMatchObject({auth:"api_key",keyPlacement:{location:"header",name:"Authorization",prefix:"Bearer "}});
expect(method("mem0")?.defaults?.serverUrl).toBe("https://mcp.mem0.ai/mcp/");
expect(method("xero")?.defaults?.scopesHint).toEqual([
"openid","profile","email","offline_access","accounting.settings","accounting.invoices.read","accounting.reports.aged.read","accounting.reports.balancesheet.read","accounting.reports.profitandloss.read",
]);
expect(APP_DEFINITIONS.find((app)=>app.slug==="pagerduty")?.methods.map((candidate)=>({key:candidate.key,serverUrl:candidate.defaults?.serverUrl}))).toEqual([
{key:"mcp-api-key-us",serverUrl:"https://mcp.pagerduty.com/mcp"},
{key:"mcp-api-key-eu",serverUrl:"https://mcp.eu.pagerduty.com/mcp"},
]);
expect(method("context7")).toMatchObject({auth:"none",defaults:{serverUrl:"https://mcp.context7.com/mcp"}});
expect(APP_DEFINITIONS.find((app)=>app.slug==="planetscale")?.methods.map((candidate)=>candidate.key)).toEqual(["mcp-oauth","mcp-insights-only"]);
expect(APP_DEFINITIONS.find((app)=>app.slug==="postman")?.methods.map((candidate)=>candidate.key)).toEqual([
"mcp-oauth-minimal","mcp-oauth-code","mcp-oauth-full","mcp-eu-key-minimal","mcp-eu-key-code","mcp-eu-key-full",
]);
expect(method("supabase")?.tenantFields?.find((field)=>field.key==="readOnly")?.defaultValue).toBe(false);
expect(method("asana")?.ownershipModes).toEqual(["customer"]);
expect(method("zapier")).toMatchObject({key:"generated-url",auth:"none",defaults:{}});
expect(method("zapier")?.credentialFields).toBeUndefined();
});
it("uses discovery-first Notion MCP OAuth metadata",()=>{
const notion=APP_DEFINITIONS.find((app)=>app.slug==="notion");
expect(notion?.redirectConstraints).toBe("https-or-loopback-http");
expect(notion?.methods[0]?.defaults).toEqual({serverUrl:"https://mcp.notion.com/mcp"});
});
it("preserves required Linear OAuth scopes",()=>expect(APP_DEFINITIONS.find((app)=>app.slug==="linear")?.methods[0]?.defaults?.scopesHint).toEqual(["read","write"]));
it("requests only Hugging Face's MCP read scope",()=>expect(APP_DEFINITIONS.find((app)=>app.slug==="hugging-face")?.methods[0]?.defaults?.scopesHint).toEqual(["read-mcp"]));
it("defaults S2-S4 write and destructive actions to ask-first",()=>{for(const app of APP_DEFINITIONS)for(const method of app.methods)expect(recommendedDefaultsForApp(app,method.key)).toEqual({access:"all_agents",askFirstRiskLevels:method.riskTier==="S1"?[]:["write","destructive"]})});
it("keeps Gmail personal-only and bound to the Paperclip ID broker",()=>expect(APP_DEFINITIONS.find((app)=>app.slug==="gmail")?.methods[0]).toMatchObject({oauthStrategy:"paperclip_id_connector",grantKinds:["user"],defaults:{serverUrl:"https://gmailmcp.googleapis.com/mcp/v1",scopesHint:["https://www.googleapis.com/auth/gmail.readonly","https://www.googleapis.com/auth/gmail.compose"]}}));
it("offers PostHog OAuth and API-key methods with broad defaults and advanced narrowing",()=>{const posthog=APP_DEFINITIONS.find((app)=>app.slug==="posthog");expect(posthog?.methods.map((method)=>method.key)).toEqual(["mcp-oauth","mcp-api-key"]);for(const method of posthog?.methods??[]){expect(method.riskTier).toBe("S3");expect(method.tenantFields?.find((field)=>field.key==="readOnly")?.defaultValue).toBe(false);expect(method.tenantFields?.find((field)=>field.key==="projectId")?.transport).toEqual({location:"header",name:"x-posthog-project-id"});expect(method.tenantFields?.filter((field)=>field.advanced).map((field)=>field.key)).toEqual(["features","tools","mode"]);expect(method.configRequirements).toBeUndefined();expect(method.requiredResourceFilters).toEqual(["project"])}});
it("defaults explicit read/write capability groups to their write-capable method",()=>{
const drive=APP_DEFINITIONS.find((app)=>app.slug==="google-drive")!;
const gmail=APP_DEFINITIONS.find((app)=>app.slug==="gmail")!;
expect(getAvailableConnectionMethod(drive)?.key).toBe("customer-write-oauth");
expect(getAvailableConnectionMethod(gmail)?.key).toBe("customer-draft-oauth");
expect(getRecommendedConnectionMethod(drive.methods.filter((candidate)=>candidate.ownershipModes.includes("customer")))?.key).toBe("customer-write-oauth");
});
it("explains Google Workspace Developer Preview enrollment before connection",()=>{
const googleWorkspaceMcpSlugs=["gmail","google-drive","google-docs","google-slides","google-calendar","google-chat","google-people","google-workspace-search"];
for(const slug of googleWorkspaceMcpSlugs){
const prerequisite=APP_DEFINITIONS.find((app)=>app.slug===slug)?.setupPrerequisite;
expect(prerequisite?.actionUrl,slug).toBe("https://developers.google.com/workspace/preview");
expect(prerequisite?.description,slug).toContain("does not enable unrelated Paperclip customers");
expect(prerequisite?.steps?.join(" "),slug).toContain("final project-registration email");
}
});
it("withholds unverified and reserved providers from the app store without deleting their definitions",()=>{
expect([...APP_STORE_HIDDEN_SLUGS].sort()).toEqual([
"beehiiv","bitly","brex","candid","coda","composio","context7","egnyte","embat","github","kernel","local-falcon","make","manufact","oreilly","planetscale","razorpay","sanity","similarweb","slack","ticket-tailor","ticktick","xero",
]);
expect(APP_STORE_DEFINITIONS).toHaveLength(35);
const connectableSlugs=new Set(CONNECTABLE_APP_DEFINITIONS.map((entry)=>entry.slug));
const storeSlugs=new Set(APP_STORE_DEFINITIONS.map((entry)=>entry.slug));
for(const slug of APP_STORE_HIDDEN_SLUGS){
expect(connectableSlugs.has(slug),slug).toBe(true);
expect(storeSlugs.has(slug),slug).toBe(false);
}
});
it("ships complete local branding provenance for all 35 store-visible providers",()=>{
const uiPublic=path.resolve(path.dirname(fileURLToPath(import.meta.url)),"../../../ui/public");
const manifest=JSON.parse(fs.readFileSync(path.join(uiPublic,"brands/apps/manifest.json"),"utf8")) as {providers:Array<{slug:string;catalogVisible:boolean;localAsset:string;darkAsset?:string;officialSourceUrl:string;upstreamAssetUrl:string;assetType:"svg"|"png";darkVariantRequired:boolean}>};
const visible=manifest.providers.filter((entry)=>entry.catalogVisible);
expect(visible).toHaveLength(35);
expect(new Set(visible.map((entry)=>entry.slug))).toHaveProperty("size",35);
expect(new Set(visible.map((entry)=>entry.localAsset))).toHaveProperty("size",35);
expect(new Set(APP_STORE_DEFINITIONS.map((entry)=>entry.slug))).toEqual(new Set(visible.map((entry)=>entry.slug)));
for(const app of APP_STORE_DEFINITIONS){
const provenance=visible.find((entry)=>entry.slug===app.slug)!;
expect(provenance).toBeTruthy();
expect(provenance.localAsset).toBe(app.branding.logoUrl);
expect(provenance.darkAsset).toBe(app.branding.darkLogoUrl);
expect(provenance.darkVariantRequired).toBe(Boolean(provenance.darkAsset));
expect(new URL(provenance.officialSourceUrl).protocol).toBe("https:");
expect(new URL(provenance.upstreamAssetUrl).protocol).toBe("https:");
expect(provenance.localAsset).toMatch(/^\/brands\/apps\/.+\.(svg|png)$/);
expect(provenance.localAsset).not.toContain("google.com/s2/favicons");
const asset=fs.readFileSync(path.join(uiPublic,provenance.localAsset));
if(provenance.assetType==="png"){
expect(asset.subarray(0,8).toString("hex")).toBe("89504e470d0a1a0a");
expect(asset.readUInt32BE(16)).toBeGreaterThanOrEqual(128);
expect(asset.readUInt32BE(20)).toBeGreaterThanOrEqual(128);
}else{
const svg=asset.toString("utf8");
expect(svg).toMatch(/^<svg\b/);
expect(svg).not.toMatch(/<script|<foreignObject|\son[a-z]+\s*=/i);
}
if(provenance.darkAsset)expect(fs.existsSync(path.join(uiPublic,provenance.darkAsset))).toBe(true);
}
});
it("keeps every researched self-serve candidate implemented while blocked providers stay absent",()=>{
const definitions=new Map(CONNECTABLE_APP_DEFINITIONS.map((entry)=>[entry.slug,entry]));
for(const candidate of SELF_SERVE_MCP_CANDIDATES)expect(appSupportsCatalogSetup(definitions.get(candidate.slug))).toBe(true);
for(const blocked of BLOCKED_MCP_PROVIDERS)expect(definitions.has(blocked.slug)).toBe(false);
});
it("ships each Google Workspace surface as an independent personal OAuth app",()=>{
const expected={
gmail:"https://gmailmcp.googleapis.com/mcp/v1",
"google-drive":"https://drivemcp.googleapis.com/mcp/v1",
"google-docs":"https://docsmcp.googleapis.com/mcp/v1",
"google-sheets":"https://sheetsmcp.googleapis.com/mcp/v1",
"google-slides":"https://slidesmcp.googleapis.com/mcp/v1",
"google-calendar":"https://calendarmcp.googleapis.com/mcp/v1",
"google-chat":"https://chatmcp.googleapis.com/mcp/v1",
"google-people":"https://people.googleapis.com/mcp/v1",
"google-workspace-search":"https://workspacemcp.googleapis.com/mcp/v1",
} as const;
for(const [slug,serverUrl] of Object.entries(expected)){
const app=APP_DEFINITIONS.find((candidate)=>candidate.slug===slug);
expect(app,slug).toBeTruthy();
expect(app?.methods.some((method)=>method.oauthStrategy==="paperclip_id_connector")).toBe(true);
for(const method of app?.methods.filter((candidate)=>candidate.auth==="oauth")??[]){
expect(method.grantKinds,`${slug}:${method.key}`).toEqual(["user"]);
expect(method.defaults?.serverUrl,`${slug}:${method.key}`).toBe(serverUrl);
expect(method.capabilityProfile,`${slug}:${method.key}`).toBeTruthy();
}
}
});
it("does not advertise a managed Gmail read-only method before profile-scoped connector support",()=>{
const gmail=APP_DEFINITIONS.find((app)=>app.slug==="gmail");
const managedMethods=gmail?.methods.filter((method)=>method.oauthStrategy==="paperclip_id_connector")??[];
expect(managedMethods.map((method)=>method.key)).toEqual(["paperclip-draft"]);
expect(managedMethods[0]?.defaults?.scopesHint).toEqual([
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.compose",
]);
});
it("configures Shopify's official tenant-scoped Storefront MCP without OAuth",()=>{const method=APP_DEFINITIONS.find((app)=>app.slug==="shopify")?.methods[0];expect(method).toMatchObject({key:"storefront-mcp",auth:"none",defaults:{serverUrlTemplate:"https://{storeDomain}/api/mcp"},tenantFields:[expect.objectContaining({key:"storeDomain",required:true})]});expect(resolveConnectionMethodServerUrl(method!,{storeDomain:"paperclip-demo.myshopify.com"})).toBe("https://paperclip-demo.myshopify.com/api/mcp");expect(resolveConnectionMethodServerUrl(method!,{})).toBeNull()});
it("offers PostHog OAuth and API-key methods with zero-config defaults and advanced narrowing",()=>{const posthog=APP_DEFINITIONS.find((app)=>app.slug==="posthog");expect(posthog?.methods.map((method)=>method.key)).toEqual(["mcp-oauth","mcp-api-key"]);for(const method of posthog?.methods??[]){const projectField=method.tenantFields?.find((field)=>field.key==="projectId");expect(method.riskTier).toBe("S3");expect(method.tenantFields?.find((field)=>field.key==="readOnly")).toMatchObject({defaultValue:false,advanced:true});expect(projectField).toMatchObject({advanced:true,transport:{location:"header",name:"x-posthog-project-id"}});expect(projectField?.required).not.toBe(true);expect(method.tenantFields?.filter((field)=>field.advanced).map((field)=>field.key)).toEqual(["projectId","readOnly","features","tools"]);expect(method.tenantFields?.find((field)=>field.key==="mode")).toMatchObject({hidden:true,defaultValue:"tools",transport:{location:"query",name:"mode"}});expect(method.configRequirements).toBeUndefined();expect(method.requiredResourceFilters).toBeUndefined();expect(method.guidanceMd).toContain("optional advanced controls")}});
it("requires only reviewed provider or safety-boundary configuration on the default path",()=>{const required=APP_DEFINITIONS.flatMap((app)=>app.methods.flatMap((method)=>[...(method.tenantFields??[]),...(method.extensionFields??[])].filter((field)=>field.required&&field.advanced!==true&&!field.hidden).map((field)=>`${app.slug}:${method.key}:${field.key}`))).sort();expect(required).toEqual(["clickhouse:mcp-oauth:serviceId","shopify:storefront-mcp:storeDomain","supabase:mcp-api-key:projectRef","supabase:mcp-oauth:projectRef"])});
it("limits Vercel Connect setup to the reviewed pilot methods",()=>{
const reviewed=APP_DEFINITIONS.flatMap((app)=>app.methods.flatMap((method)=>method.credentialSources?.vercelConnect?[{slug:app.slug,key:method.key,review:method.credentialSources.vercelConnect}]:[]));
expect(reviewed.map(({slug,key})=>`${slug}:${key}`).sort()).toEqual(["linear:mcp-oauth","notion:mcp-oauth","posthog:mcp-api-key","posthog:mcp-oauth"]);
expect(reviewed.find(({slug})=>slug==="linear")?.review).toMatchObject({services:["linear"],principalModes:["user"],scopes:["read","write"],header:{name:"Authorization",prefix:"Bearer "}});
expect(reviewed.find(({slug,key})=>slug==="posthog"&&key==="mcp-oauth")?.review.services).toEqual(["posthog","mcp.posthog.com/mcp"]);
expect(reviewed.find(({slug,key})=>slug==="posthog"&&key==="mcp-api-key")?.review.principalModes).toEqual(["app"]);
expect(APP_DEFINITIONS.find((app)=>app.slug==="vercel")?.availability?.available).toBe(false);
});
it("enforces method and field invariants",()=>{for(const app of APP_DEFINITIONS)for(const method of app.methods){if(method.auth==="api_key")expect(method.keyPlacement).toBeTruthy();if(method.auth==="oauth")expect(method.ownershipModes.length).toBeGreaterThan(0);for(const field of method.credentialFields??[])if(field.required&&field.type!=="checkbox")expect(field.placeholder).toBeTruthy()}});
});

View File

@ -1,8 +1,10 @@
import { APP_DEFINITIONS } from "./app-definitions.generated.js";
import { SELF_SERVE_MCP_CANDIDATES } from "./self-serve-mcp-research.js";
import type { AppDefinition, ConnectionMethodDef, FieldDef } from "./types/app-definition.js";
import type { ToolConnectionOwnership } from "./types/tool-access.js";
const CONNECTABLE_APP_SLUGS = new Set([
export const CONNECTABLE_APP_SLUGS = new Set([
...SELF_SERVE_MCP_CANDIDATES.map((entry) => entry.slug),
"zapier",
"github",
"slack",
@ -11,14 +13,58 @@ const CONNECTABLE_APP_SLUGS = new Set([
"linear",
"google-sheets",
"context7",
"shopify",
"composio",
"gmail",
"google-drive",
"google-docs",
"google-slides",
"google-calendar",
"google-chat",
"google-people",
"google-workspace-search",
]);
export const CONNECTABLE_APP_DEFINITIONS = APP_DEFINITIONS.filter((app) =>
CONNECTABLE_APP_SLUGS.has(app.slug)
);
/**
* Definitions retained for existing connections and later verification, but
* intentionally withheld from the customer-facing store. Keeping visibility
* separate from recognition avoids breaking saved connections when a provider
* is pulled from Browse or reserved for a future first-party experience.
*/
export const APP_STORE_HIDDEN_SLUGS = new Set([
"beehiiv",
"bitly",
"brex",
"candid",
"coda",
"composio",
"context7",
"egnyte",
"embat",
"github",
"kernel",
"local-falcon",
"make",
"manufact",
"oreilly",
"planetscale",
"razorpay",
"sanity",
"similarweb",
"slack",
"ticket-tailor",
"ticktick",
"xero",
]);
export const APP_STORE_DEFINITIONS = CONNECTABLE_APP_DEFINITIONS.filter((app) =>
!APP_STORE_HIDDEN_SLUGS.has(app.slug)
);
export const DEFAULT_OWNERSHIP_AVAILABILITY: Record<ToolConnectionOwnership, boolean> = {
platform_shared: false,
platform_provisioned: false,
@ -30,6 +76,14 @@ export function getConnectableAppDefinition(slug: string): AppDefinition | null
return CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === slug) ?? null;
}
export function getAppStoreDefinition(slug: string): AppDefinition | null {
return APP_STORE_DEFINITIONS.find((app) => app.slug === slug) ?? null;
}
export function isAppStoreVisibleSlug(slug: string | null | undefined): boolean {
return Boolean(slug && !APP_STORE_HIDDEN_SLUGS.has(slug) && CONNECTABLE_APP_SLUGS.has(slug));
}
function wildcardPatternToRegExp(pattern: string): RegExp {
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
return new RegExp(`^${escaped}$`, "i");
@ -57,22 +111,125 @@ export function getAvailableConnectionMethods(app: AppDefinition): ConnectionMet
);
}
/**
* Pick the method that gives a new connection the app's useful write surface.
*
* Google Workspace publishes separate read and write capability groups. The
* read method is intentionally listed first for documentation, but treating
* array order as a product default silently created read-only connections and
* left every write action Off. Capability metadata is the durable signal; apps
* without an explicit write/draft capability retain their declared order.
*/
export function getRecommendedConnectionMethod(
methods: readonly ConnectionMethodDef[],
): ConnectionMethodDef | null {
return methods.find((method) => {
const capabilityKey = method.capabilityProfile?.key;
return capabilityKey === "write" || capabilityKey === "draft";
}) ?? methods[0] ?? null;
}
export function getAvailableConnectionMethod(
app: AppDefinition,
methodKey?: string | null,
): ConnectionMethodDef | null {
const methods = getAvailableConnectionMethods(app);
return methodKey ? methods.find((method) => method.key === methodKey) ?? null : methods[0] ?? null;
return methodKey
? methods.find((method) => method.key === methodKey) ?? null
: getRecommendedConnectionMethod(methods);
}
export function connectionMethodSupportsAutomaticOAuth(method: ConnectionMethodDef | null | undefined): boolean {
return method?.auth === "oauth" && (
method.oauthStrategy === "paperclip_id_connector"
|| method.ownershipModes.includes("dcr")
);
}
export function connectionMethodAcceptsCustomerOAuthClient(method: ConnectionMethodDef | null | undefined): boolean {
return method?.auth === "oauth"
&& !method.oauthStrategy
&& method.ownershipModes.includes("customer");
}
export function connectionMethodSupportsCatalogSetup(method: ConnectionMethodDef | null | undefined): boolean {
if (!method) return false;
if (method.auth === "none" || method.auth === "api_key") return true;
return connectionMethodSupportsAutomaticOAuth(method)
|| connectionMethodAcceptsCustomerOAuthClient(method);
}
export function connectionMethodRequiresConfiguration(method: ConnectionMethodDef | null | undefined): boolean {
if (!method) return false;
const visibleTenantFields = method.tenantFields?.filter((field) => !field.hidden) ?? [];
const visibleExtensionFields = method.extensionFields?.filter((field) => !field.hidden) ?? [];
return Boolean(
method.credentialFields?.length
|| visibleTenantFields.length
|| visibleExtensionFields.length
|| method.configRequirements?.atLeastOneOf?.length
// "Use your own OAuth app" is an advanced alternative when DCR/CIMD is
// available, not a required setup field. Only customer-client-only methods
// must stop on the configuration screen.
|| (
connectionMethodAcceptsCustomerOAuthClient(method)
&& !connectionMethodSupportsAutomaticOAuth(method)
),
);
}
export function appSupportsCatalogSetup(app: AppDefinition | null | undefined): boolean {
return Boolean(app && getAvailableConnectionMethods(app).some(connectionMethodSupportsCatalogSetup));
}
export function isConnectableAppSlug(slug: string | null | undefined): boolean {
return Boolean(slug && CONNECTABLE_APP_SLUGS.has(slug));
}
export function appSupportsAutomaticOAuth(app: AppDefinition | null | undefined): boolean {
return Boolean(app && getAvailableConnectionMethods(app).some(connectionMethodSupportsAutomaticOAuth));
}
export function appAcceptsCustomerOAuthClient(app: AppDefinition | null | undefined): boolean {
return Boolean(app && getAvailableConnectionMethods(app).some(connectionMethodAcceptsCustomerOAuthClient));
}
export function credentialConfigPath(field: FieldDef): string {
return `credentials.${field.key}`;
}
export function resolveConnectionMethodServerUrl(
method: ConnectionMethodDef,
configValues: Record<string, string | boolean>,
): string | null {
const template = method.defaults?.serverUrlTemplate;
if (!template) return method.defaults?.serverUrl ?? null;
let missingValue = false;
const resolved = template.replace(/\{([a-zA-Z0-9_-]+)\}/g, (_placeholder, key: string) => {
const value = configValues[key];
if (value === undefined || String(value).trim().length === 0) {
missingValue = true;
return "";
}
return encodeURIComponent(String(value).trim());
});
if (missingValue) return null;
try {
return new URL(resolved).toString();
} catch {
return null;
}
}
export function recommendedDefaultsForApp(app: AppDefinition, methodKey?: string | null): Record<string, unknown> {
const method = getAvailableConnectionMethod(app, methodKey);
const normalizedMethodKey = app.slug === "gmail" && methodKey === "paperclip-id-oauth" ? "paperclip-draft" : methodKey;
const method = normalizedMethodKey
? app.methods.find((candidate) => candidate.key === normalizedMethodKey) ?? null
: getAvailableConnectionMethod(app, null);
return {
access: "all_agents",
askFirstRiskLevels: method?.riskTier === "S1" ? [] : ["write", "destructive"],
askFirstRiskLevels: method && method.riskTier !== "S1" ? ["write", "destructive"] : [],
};
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "airtable",
"name": "Airtable",
"description": "Connect Airtable's provider-hosted MCP server.",
"categories": [
"data"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/airtable.svg"
},
"urlPatterns": [
"https://mcp.airtable.com/*"
],
"docsUrl": "https://support.airtable.com/articles/9897799762-using-the-airtable-mcp-server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.airtable.com/mcp"
},
"guidanceMd": "Connect Airtable in the browser. An Airtable account; enterprise administrators may need to allowlist the client.",
"riskTier": "S3",
"label": "Sign in with Airtable",
"consoleLinks": {
"docs": "https://support.airtable.com/articles/9897799762-using-the-airtable-mcp-server"
},
"warnings": [
"An Airtable account; enterprise administrators may need to allowlist the client."
]
}
]
}

View File

@ -8,7 +8,8 @@
],
"featured": false,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=anthropic.com&sz=128"
"logoUrl": "/brands/apps/anthropic.svg",
"darkLogoUrl": "/brands/apps/anthropic-dark.svg"
},
"urlPatterns": [
"https://api.anthropic.com/*"

View File

@ -8,7 +8,7 @@
],
"featured": false,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=openapis.org&sz=128"
"logoUrl": "/brands/apps/api-key-generic.svg"
},
"urlPatterns": [],
"methods": [

View File

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"slug": "asana",
"name": "Asana",
"description": "Connect Asana's provider-hosted MCP server.",
"categories": [
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/asana.svg"
},
"urlPatterns": [
"https://mcp.asana.com/*"
],
"docsUrl": "https://developers.asana.com/docs/integrating-with-asanas-mcp-server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-own-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"customer"
],
"whenToUse": "Register an OAuth app with Asana, then enter its client ID and secret.",
"defaults": {
"serverUrl": "https://mcp.asana.com/v2/mcp"
},
"guidanceMd": "Connect Asana in the browser. Create an Asana MCP OAuth app and register Paperclip's callback URI; DCR is not supported.",
"riskTier": "S3",
"label": "Use your own OAuth app",
"consoleLinks": {
"register": "https://developers.asana.com/docs/integrating-with-asanas-mcp-server",
"docs": "https://developers.asana.com/docs/integrating-with-asanas-mcp-server"
},
"warnings": [
"Create an Asana MCP OAuth app and register Paperclip's callback URI; DCR is not supported."
]
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "beehiiv",
"name": "beehiiv",
"description": "Connect beehiiv's provider-hosted MCP server.",
"categories": [
"content"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/beehiiv.png"
},
"urlPatterns": [
"https://mcp.beehiiv.com/*"
],
"docsUrl": "https://www.beehiiv.com/features/mcp/getting-started",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.beehiiv.com/mcp"
},
"guidanceMd": "Connect beehiiv in the browser. A beehiiv account; the subscription plan controls available write capabilities.",
"riskTier": "S3",
"label": "Sign in with beehiiv",
"consoleLinks": {
"docs": "https://www.beehiiv.com/features/mcp/getting-started"
},
"warnings": [
"A beehiiv account; the subscription plan controls available write capabilities."
]
}
]
}

View File

@ -0,0 +1,78 @@
{
"schemaVersion": 1,
"slug": "bitly",
"name": "Bitly",
"description": "Connect Bitly's provider-hosted MCP server.",
"categories": [
"analytics"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/bitly.svg"
},
"urlPatterns": [
"https://api-ssl.bitly.com/*"
],
"docsUrl": "https://dev.bitly.com/bitly-mcp/overview/quickstart/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://api-ssl.bitly.com/v4/mcp"
},
"guidanceMd": "Connect Bitly in the browser. A Bitly account with either browser authorization or an API token.",
"riskTier": "S2",
"label": "Sign in with Bitly",
"consoleLinks": {
"docs": "https://dev.bitly.com/bitly-mcp/overview/quickstart/"
},
"warnings": [
"A Bitly account with either browser authorization or an API token."
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://api-ssl.bitly.com/v4/mcp"
},
"guidanceMd": "Use a customer-created Bitly key. A Bitly account with either browser authorization or an API token.",
"riskTier": "S2",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Bitly API key",
"type": "password",
"required": true,
"placeholder": "Paste your Bitly API token",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
},
"consoleLinks": {
"keys": "https://dev.bitly.com/bitly-mcp/overview/quickstart/",
"docs": "https://dev.bitly.com/bitly-mcp/overview/quickstart/"
},
"warnings": [
"A Bitly account with either browser authorization or an API token."
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"slug": "box",
"name": "Box",
"description": "Connect Box's provider-hosted MCP server.",
"categories": [
"content"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/box.svg"
},
"urlPatterns": [
"https://mcp.box.com/*"
],
"docsUrl": "https://support.box.com/hc/en-us/articles/43847256139923-Managing-Box-MCP-Servers",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-own-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"customer"
],
"whenToUse": "Register an OAuth app with Box, then enter its client ID and secret.",
"defaults": {
"serverUrl": "https://mcp.box.com"
},
"guidanceMd": "Connect Box in the browser. A Box administrator creates the OAuth integration and enables AI access.",
"riskTier": "S3",
"label": "Use your own OAuth app",
"consoleLinks": {
"register": "https://support.box.com/hc/en-us/articles/43847256139923-Managing-Box-MCP-Servers",
"docs": "https://support.box.com/hc/en-us/articles/43847256139923-Managing-Box-MCP-Servers"
},
"warnings": [
"A Box administrator creates the OAuth integration and enables AI access."
]
}
]
}

View File

@ -0,0 +1,43 @@
{
"schemaVersion": 1,
"slug": "brex",
"name": "Brex",
"description": "Connect Brex's provider-hosted MCP server.",
"categories": [
"commerce"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/brex.svg",
"darkLogoUrl": "/brands/apps/brex-dark.svg"
},
"urlPatterns": [
"https://api.brex.com/*"
],
"docsUrl": "https://www.brex.com/support/using-brex-in-ai-apps",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://api.brex.com/mcp"
},
"guidanceMd": "Connect Brex in the browser. Brex early access and an administrator enabling the integration; financial actions require explicit approval.",
"riskTier": "S4",
"label": "Sign in with Brex",
"consoleLinks": {
"docs": "https://www.brex.com/support/using-brex-in-ai-apps"
},
"warnings": [
"Brex early access and an administrator enabling the integration; financial actions require explicit approval.",
"Financial or destructive actions must be explicitly approved before execution."
]
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "candid",
"name": "Candid",
"description": "Connect Candid's provider-hosted MCP server.",
"categories": [
"data"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/candid.png"
},
"urlPatterns": [
"https://mcp.candid.org/*"
],
"docsUrl": "https://learning.candid.org/getting-started-with-the-candid-mcp-connector/375441",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.candid.org/mcp"
},
"guidanceMd": "Connect Candid in the browser. A Candid account with access to the MCP connector.",
"riskTier": "S2",
"label": "Sign in with Candid",
"consoleLinks": {
"docs": "https://learning.candid.org/getting-started-with-the-candid-mcp-connector/375441"
},
"warnings": [
"A Candid account with access to the MCP connector."
]
}
]
}

View File

@ -0,0 +1,58 @@
{
"schemaVersion": 1,
"slug": "clickhouse",
"name": "ClickHouse",
"description": "Connect ClickHouse's provider-hosted MCP server.",
"categories": [
"data"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/clickhouse.svg"
},
"urlPatterns": [
"https://mcp.clickhouse.cloud/*"
],
"docsUrl": "https://clickhouse.com/blog/announcing-managed-clickstack-mcp-server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.clickhouse.cloud/clickstack"
},
"guidanceMd": "Connect ClickHouse in the browser. A ClickHouse Cloud ClickStack service and its service ID.",
"riskTier": "S4",
"label": "Sign in with ClickHouse",
"consoleLinks": {
"docs": "https://clickhouse.com/blog/announcing-managed-clickstack-mcp-server"
},
"warnings": [
"A ClickHouse Cloud ClickStack service and its service ID."
],
"tenantFields": [
{
"key": "serviceId",
"label": "ClickHouse Cloud service ID",
"type": "text",
"required": true,
"placeholder": "11e1031f-9a13-4cac-9bc7-d4ec9286ec17",
"helperMd": "Copy the service ID from ClickStack → Team Settings → API & Agents.",
"transport": {
"location": "header",
"name": "x-service-id"
}
}
],
"requiredResourceFilters": [
"service"
]
}
]
}

View File

@ -0,0 +1,78 @@
{
"schemaVersion": 1,
"slug": "cloudflare",
"name": "Cloudflare",
"description": "Connect Cloudflare's provider-hosted MCP server.",
"categories": [
"developer"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/cloudflare.svg"
},
"urlPatterns": [
"https://mcp.cloudflare.com/*"
],
"docsUrl": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.cloudflare.com/mcp"
},
"guidanceMd": "Connect Cloudflare in the browser. A Cloudflare account with access to the resources being connected.",
"riskTier": "S3",
"label": "Sign in with Cloudflare",
"consoleLinks": {
"docs": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/"
},
"warnings": [
"A Cloudflare account with access to the resources being connected."
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.cloudflare.com/mcp"
},
"guidanceMd": "Use a customer-created Cloudflare key. A Cloudflare account with access to the resources being connected.",
"riskTier": "S3",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Cloudflare API key",
"type": "password",
"required": true,
"placeholder": "Paste your Cloudflare API token",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
},
"consoleLinks": {
"keys": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/",
"docs": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/"
},
"warnings": [
"A Cloudflare account with access to the resources being connected."
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"slug": "cloudinary",
"name": "Cloudinary",
"description": "Connect Cloudinary's provider-hosted MCP server.",
"categories": [
"content"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/cloudinary.svg",
"darkLogoUrl": "/brands/apps/cloudinary-dark.svg"
},
"urlPatterns": [
"https://asset-management.mcp.cloudinary.com/*"
],
"docsUrl": "https://cloudinary.com/documentation/cloudinary_llm_mcp",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://asset-management.mcp.cloudinary.com/mcp"
},
"guidanceMd": "Connect Cloudinary in the browser. A Cloudinary account; authorization is limited by the signed-in user's roles.",
"riskTier": "S3",
"label": "Sign in with Cloudinary",
"consoleLinks": {
"docs": "https://cloudinary.com/documentation/cloudinary_llm_mcp"
},
"warnings": [
"A Cloudinary account; authorization is limited by the signed-in user's roles."
]
}
]
}

View File

@ -0,0 +1,80 @@
{
"schemaVersion": 1,
"slug": "coda",
"name": "Coda",
"description": "Connect Coda's provider-hosted MCP server.",
"categories": [
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/coda.svg"
},
"urlPatterns": [
"https://coda.io/*"
],
"docsUrl": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://coda.io/apis/mcp"
},
"guidanceMd": "Connect Coda in the browser. A Coda account; the hosted MCP service is currently beta.",
"riskTier": "S3",
"label": "Sign in with Coda",
"consoleLinks": {
"docs": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP"
},
"warnings": [
"A Coda account; the hosted MCP service is currently beta.",
"This provider's hosted MCP server is currently beta or preview."
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://coda.io/apis/mcp"
},
"guidanceMd": "Use a customer-created Coda key. A Coda account; the hosted MCP service is currently beta.",
"riskTier": "S3",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Coda API key",
"type": "password",
"required": true,
"placeholder": "Paste your Coda API token",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
},
"consoleLinks": {
"keys": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP",
"docs": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP"
},
"warnings": [
"A Coda account; the hosted MCP service is currently beta.",
"This provider's hosted MCP server is currently beta or preview."
]
}
]
}

View File

@ -3,18 +3,25 @@
"slug": "composio",
"name": "Composio",
"description": "Connect Composio so Paperclip can discover and manage the toolkits in your project.",
"categories": ["productivity"],
"categories": [
"productivity"
],
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=composio.dev&sz=128"
"logoUrl": "/brands/apps/composio.svg",
"darkLogoUrl": "/brands/apps/composio-dark.svg"
},
"urlPatterns": ["https://backend.composio.dev/*"],
"urlPatterns": [
"https://backend.composio.dev/*"
],
"methods": [
{
"key": "api-key",
"transport": "rest_api",
"auth": "api_key",
"ownershipModes": ["customer"],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a project API key from the Composio project that owns the toolkits and connected accounts.",
"defaults": {
"serviceHost": "backend.composio.dev"

View File

@ -8,7 +8,8 @@
],
"featured": false,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=context7.com&sz=128"
"logoUrl": "/brands/apps/context7.svg",
"darkLogoUrl": "/brands/apps/context7-dark.svg"
},
"urlPatterns": [
"https://mcp.context7.com/*"

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "egnyte",
"name": "Egnyte",
"description": "Connect Egnyte's provider-hosted MCP server.",
"categories": [
"content"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/egnyte.svg"
},
"urlPatterns": [
"https://mcp-server.egnyte.com/*"
],
"docsUrl": "https://developers.egnyte.com/docs/Remote_MCP_Server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp-server.egnyte.com/mcp"
},
"guidanceMd": "Connect Egnyte in the browser. An eligible Egnyte plan and administrator approval for external LLM access.",
"riskTier": "S3",
"label": "Sign in with Egnyte",
"consoleLinks": {
"docs": "https://developers.egnyte.com/docs/Remote_MCP_Server"
},
"warnings": [
"An eligible Egnyte plan and administrator approval for external LLM access."
]
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "embat",
"name": "Embat",
"description": "Connect Embat's provider-hosted MCP server.",
"categories": [
"commerce"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/embat.svg"
},
"urlPatterns": [
"https://tellme.embat.io/*"
],
"docsUrl": "https://tellme.embat.io/.well-known/oauth-protected-resource/mcp",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://tellme.embat.io/mcp"
},
"guidanceMd": "Connect Embat in the browser. An Embat account; pilot the connection because provider setup documentation is sparse.",
"riskTier": "S4",
"label": "Sign in with Embat",
"consoleLinks": {
"docs": "https://tellme.embat.io/.well-known/oauth-protected-resource/mcp"
},
"warnings": [
"An Embat account; pilot the connection because provider setup documentation is sparse."
]
}
]
}

View File

@ -8,7 +8,8 @@
],
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=github.com&sz=128"
"logoUrl": "/brands/apps/github.svg",
"darkLogoUrl": "/brands/apps/github-dark.svg"
},
"urlPatterns": [
"https://api.githubcopilot.com/mcp/*"

View File

@ -3,24 +3,90 @@
"slug": "gmail",
"name": "Gmail",
"description": "Search and read Gmail messages and create drafts without enabling mail sending.",
"categories": ["communication", "productivity"],
"categories": [
"communication",
"productivity"
],
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=gmail.com&sz=128"
"logoUrl": "/brands/apps/gmail.svg"
},
"urlPatterns": [
"https://gmailmcp.googleapis.com/*"
],
"docsUrl": "https://developers.google.com/workspace/gmail/api/reference/mcp",
"setupPrerequisite": {
"title": "Google Developer Preview access required",
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
"steps": [
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
],
"actionLabel": "Apply or verify Developer Preview enrollment",
"actionUrl": "https://developers.google.com/workspace/preview"
},
"urlPatterns": ["https://gmailmcp.googleapis.com/*"],
"docsUrl": "https://developers.google.com/workspace/guides/configure-mcp-servers",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "paperclip-id-oauth",
"label": "Connect Gmail",
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Search and read messages, threads, drafts, and labels."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned Google OAuth client for read-only Gmail access.",
"defaults": {
"serverUrl": "https://gmailmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/gmail.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Gmail and Gmail MCP APIs in your Google Cloud project, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "paperclip-draft",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"grantKinds": ["user"],
"ownershipModes": ["customer"],
"whenToUse": "Use Paperclip ID for a personal Gmail connection with centrally registered Google OAuth.",
"connectorProfile": "gmail.draft",
"capabilityProfile": {
"key": "draft",
"label": "Read & create drafts",
"description": "Read Gmail and create drafts for review in Gmail."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed Google OAuth to read Gmail and create drafts.",
"defaults": {
"serverUrl": "https://gmailmcp.googleapis.com/mcp/v1",
"scopesHint": [
@ -28,11 +94,54 @@
"https://www.googleapis.com/auth/gmail.compose"
]
},
"guidanceMd": "Connect your Gmail identity. Paperclip can search and read mail and create drafts. Sending mail is not enabled.",
"guidanceMd": "Connect Gmail to search and read mail and create drafts. Sending mail is permanently disabled.",
"warnings": [
"This connection is personal. Agents need an explicit install, profile, and delegation before they can use it."
"Draft creation requires approval. Sending mail is not enabled.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
"riskTier": "S4"
},
{
"key": "customer-draft-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "draft",
"label": "Read & create drafts",
"description": "Read Gmail and create drafts for review in Gmail."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned Google OAuth client to read Gmail and create drafts.",
"defaults": {
"serverUrl": "https://gmailmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.compose"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Gmail and Gmail MCP APIs in your Google Cloud project, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Draft creation requires approval. Sending mail is not enabled.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
}
]
}

View File

@ -0,0 +1,184 @@
{
"schemaVersion": 1,
"slug": "google-calendar",
"name": "Google Calendar",
"description": "Read calendars and manage Google Calendar events.",
"categories": [
"productivity"
],
"featured": true,
"branding": {
"logoUrl": "/brands/apps/google-calendar.svg"
},
"urlPatterns": [
"https://calendarmcp.googleapis.com/*",
"https://calendar.google.com/*"
],
"docsUrl": "https://developers.google.com/workspace/calendar/api/reference/mcp",
"setupPrerequisite": {
"title": "Google Developer Preview access required",
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
"steps": [
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
],
"actionLabel": "Apply or verify Developer Preview enrollment",
"actionUrl": "https://developers.google.com/workspace/preview"
},
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "paperclip-read",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "calendar.read",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Read calendars, events, and availability."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for read-only Calendar access.",
"defaults": {
"serverUrl": "https://calendarmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
"https://www.googleapis.com/auth/calendar.events.freebusy",
"https://www.googleapis.com/auth/calendar.events.readonly"
]
},
"guidanceMd": "Connect Google Calendar to read schedules and availability.",
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Read calendars, events, and availability."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client for read-only Calendar access.",
"defaults": {
"serverUrl": "https://calendarmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
"https://www.googleapis.com/auth/calendar.events.freebusy",
"https://www.googleapis.com/auth/calendar.events.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Calendar and Calendar MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "paperclip-write",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "calendar.write",
"capabilityProfile": {
"key": "write",
"label": "Read & manage",
"description": "Create, update, respond to, and delete events."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth to read and manage Calendar events.",
"defaults": {
"serverUrl": "https://calendarmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
"https://www.googleapis.com/auth/calendar.events.freebusy",
"https://www.googleapis.com/auth/calendar.events"
]
},
"guidanceMd": "Connect Google Calendar to read and manage events.",
"warnings": [
"All event mutations require approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
},
{
"key": "customer-write-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "write",
"label": "Read & manage",
"description": "Create, update, respond to, and delete events."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client to read and manage Calendar events.",
"defaults": {
"serverUrl": "https://calendarmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
"https://www.googleapis.com/auth/calendar.events.freebusy",
"https://www.googleapis.com/auth/calendar.events"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Calendar and Calendar MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"All event mutations require approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
}
]
}

View File

@ -0,0 +1,193 @@
{
"schemaVersion": 1,
"slug": "google-chat",
"name": "Google Chat",
"description": "Search and read Google Chat conversations and send messages.",
"categories": [
"communication",
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/google-chat.svg"
},
"urlPatterns": [
"https://chatmcp.googleapis.com/*",
"https://chat.google.com/*"
],
"docsUrl": "https://developers.google.com/workspace/chat/api/reference/mcp",
"setupPrerequisite": {
"title": "Google Developer Preview access required",
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
"steps": [
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
],
"actionLabel": "Apply or verify Developer Preview enrollment",
"actionUrl": "https://developers.google.com/workspace/preview"
},
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "paperclip-read",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "chat.read",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Search conversations and read messages."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for read-only Chat access.",
"defaults": {
"serverUrl": "https://chatmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/chat.spaces.readonly",
"https://www.googleapis.com/auth/chat.memberships.readonly",
"https://www.googleapis.com/auth/chat.messages.readonly",
"https://www.googleapis.com/auth/chat.users.readstate.readonly"
]
},
"guidanceMd": "Connect Google Chat to search conversations and read messages.",
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Search conversations and read messages."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client for read-only Chat access.",
"defaults": {
"serverUrl": "https://chatmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/chat.spaces.readonly",
"https://www.googleapis.com/auth/chat.memberships.readonly",
"https://www.googleapis.com/auth/chat.messages.readonly",
"https://www.googleapis.com/auth/chat.users.readstate.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Chat and Chat MCP APIs, configure a Chat app, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"A Google Chat app must be configured in the Cloud project.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "paperclip-write",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "chat.write",
"capabilityProfile": {
"key": "write",
"label": "Read & send",
"description": "Read Chat and send messages."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth to read Chat and send messages.",
"defaults": {
"serverUrl": "https://chatmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/chat.spaces.readonly",
"https://www.googleapis.com/auth/chat.memberships.readonly",
"https://www.googleapis.com/auth/chat.messages.readonly",
"https://www.googleapis.com/auth/chat.users.readstate.readonly",
"https://www.googleapis.com/auth/chat.messages.create"
]
},
"guidanceMd": "Connect Google Chat to read conversations and send approved messages.",
"warnings": [
"Sending messages requires approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
},
{
"key": "customer-write-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "write",
"label": "Read & send",
"description": "Read Chat and send messages."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client to read Chat and send messages.",
"defaults": {
"serverUrl": "https://chatmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/chat.spaces.readonly",
"https://www.googleapis.com/auth/chat.memberships.readonly",
"https://www.googleapis.com/auth/chat.messages.readonly",
"https://www.googleapis.com/auth/chat.users.readstate.readonly",
"https://www.googleapis.com/auth/chat.messages.create"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Chat and Chat MCP APIs, configure a Chat app, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Sending messages requires approval.",
"A Google Chat app must be configured in the Cloud project.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
}
]
}

View File

@ -0,0 +1,183 @@
{
"schemaVersion": 1,
"slug": "google-docs",
"name": "Google Docs",
"description": "Read and update Google Docs documents.",
"categories": [
"content",
"productivity"
],
"featured": true,
"branding": {
"logoUrl": "/brands/apps/google-docs.svg"
},
"urlPatterns": [
"https://docsmcp.googleapis.com/*",
"https://docs.google.com/document/*"
],
"docsUrl": "https://developers.google.com/workspace/docs/api/reference/mcp",
"setupPrerequisite": {
"title": "Google Developer Preview access required",
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
"steps": [
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
],
"actionLabel": "Apply or verify Developer Preview enrollment",
"actionUrl": "https://developers.google.com/workspace/preview"
},
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "paperclip-read",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "docs.read",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Read document text and structure."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for read-only Docs access.",
"defaults": {
"serverUrl": "https://docsmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/documents.readonly"
]
},
"guidanceMd": "Connect Google Docs to read documents.",
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Read document text and structure."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client for read-only Docs access.",
"defaults": {
"serverUrl": "https://docsmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/documents.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Drive, Docs, and Docs MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "paperclip-write",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "docs.write",
"capabilityProfile": {
"key": "write",
"label": "Read & edit",
"description": "Read and update documents."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth to read and update Docs.",
"defaults": {
"serverUrl": "https://docsmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/documents"
]
},
"guidanceMd": "Connect Google Docs to read and update documents.",
"warnings": [
"Document updates require approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
},
{
"key": "customer-write-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "write",
"label": "Read & edit",
"description": "Read and update documents."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client to read and update Docs.",
"defaults": {
"serverUrl": "https://docsmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/documents"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Drive, Docs, and Docs MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Document updates require approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
}
]
}

View File

@ -0,0 +1,179 @@
{
"schemaVersion": 1,
"slug": "google-drive",
"name": "Google Drive",
"description": "Search, read, create, and copy files in Google Drive.",
"categories": [
"content",
"productivity"
],
"featured": true,
"branding": {
"logoUrl": "/brands/apps/google-drive.svg"
},
"urlPatterns": [
"https://drivemcp.googleapis.com/*",
"https://drive.google.com/*"
],
"docsUrl": "https://developers.google.com/workspace/drive/api/reference/mcp",
"setupPrerequisite": {
"title": "Google Developer Preview access required",
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
"steps": [
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
],
"actionLabel": "Apply or verify Developer Preview enrollment",
"actionUrl": "https://developers.google.com/workspace/preview"
},
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "paperclip-read",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "drive.read",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Search and read files and metadata."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for read-only Drive access.",
"defaults": {
"serverUrl": "https://drivemcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly"
]
},
"guidanceMd": "Connect Google Drive to search and read files. Google's Developer Preview Program must register the signed-in Workspace account and Google Cloud project before tools can run.",
"warnings": [
"Before connecting, enroll the signed-in Workspace account and Google Cloud project in Google's Developer Preview Program and wait for the registration confirmation."
],
"riskTier": "S3"
},
{
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Search and read files and metadata."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client for read-only Drive access.",
"defaults": {
"serverUrl": "https://drivemcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enroll the Workspace account and Google Cloud project in Google's Developer Preview Program, enable the Drive and Drive MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Before connecting, enroll the signed-in Workspace account and Google Cloud project in Google's Developer Preview Program and wait for the registration confirmation."
],
"riskTier": "S3"
},
{
"key": "paperclip-write",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "drive.write",
"capabilityProfile": {
"key": "write",
"label": "Read & create",
"description": "Read files and create or copy files."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for Drive read and create access.",
"defaults": {
"serverUrl": "https://drivemcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.file"
]
},
"guidanceMd": "Connect Google Drive to read files and create or copy app-accessible files. Google's Developer Preview Program must register the signed-in Workspace account and Google Cloud project before tools can run.",
"warnings": [
"File creation and copying require approval.",
"Before connecting, enroll the signed-in Workspace account and Google Cloud project in Google's Developer Preview Program and wait for the registration confirmation."
],
"riskTier": "S4"
},
{
"key": "customer-write-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "write",
"label": "Read & create",
"description": "Read files and create or copy files."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client for Drive read and create access.",
"defaults": {
"serverUrl": "https://drivemcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.file"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enroll the Workspace account and Google Cloud project in Google's Developer Preview Program, enable the Drive and Drive MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"File creation and copying require approval.",
"Before connecting, enroll the signed-in Workspace account and Google Cloud project in Google's Developer Preview Program and wait for the registration confirmation."
],
"riskTier": "S4"
}
]
}

View File

@ -0,0 +1,108 @@
{
"schemaVersion": 1,
"slug": "google-people",
"name": "Google People",
"description": "Search contacts and directory profiles with the Google People API.",
"categories": [
"communication",
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/google-people.svg"
},
"urlPatterns": [
"https://people.googleapis.com/*"
],
"docsUrl": "https://developers.google.com/people/api/mcp",
"setupPrerequisite": {
"title": "Google Developer Preview access required",
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
"steps": [
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
],
"actionLabel": "Apply or verify Developer Preview enrollment",
"actionUrl": "https://developers.google.com/workspace/preview"
},
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "paperclip-read",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "people.read",
"capabilityProfile": {
"key": "read",
"label": "Read contacts",
"description": "Search contacts, directory people, and your profile."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for Google People access.",
"defaults": {
"serverUrl": "https://people.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/directory.readonly",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/contacts.readonly"
]
},
"guidanceMd": "Connect Google People to search contacts and directory profiles.",
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Read contacts",
"description": "Search contacts, directory people, and your profile."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client for Google People access.",
"defaults": {
"serverUrl": "https://people.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/directory.readonly",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/contacts.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the People and People MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Directory search availability depends on your Workspace account.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
}
]
}

View File

@ -2,27 +2,187 @@
"schemaVersion": 1,
"slug": "google-sheets",
"name": "Google Sheets",
"description": "Read and update selected spreadsheets.",
"description": "Read and update Google Sheets spreadsheets.",
"categories": [
"data"
"data",
"productivity"
],
"featured": false,
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=sheets.google.com&sz=128"
"logoUrl": "/brands/apps/google-sheets.svg"
},
"urlPatterns": [
"https://sheetsmcp.googleapis.com/*",
"https://docs.google.com/spreadsheets/*",
"https://sheets.google.com/*"
],
"docsUrl": "https://developers.google.com/workspace/sheets/api/reference/mcp",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "local",
"transport": "local_stdio",
"auth": "none",
"key": "paperclip-read",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "sheets.read",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Read spreadsheet values and structure."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for read-only Sheets access.",
"defaults": {
"serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/spreadsheets.readonly"
]
},
"guidanceMd": "Connect Google Sheets to read spreadsheets.",
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Read spreadsheet values and structure."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use credentials from your provider account.",
"whenToUse": "Use a customer-owned OAuth client for read-only Sheets access.",
"defaults": {
"serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/spreadsheets.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Drive, Sheets, and Sheets MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "paperclip-write",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "sheets.write",
"capabilityProfile": {
"key": "write",
"label": "Read & edit",
"description": "Read and update spreadsheet values, formulas, and dimensions."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth to read and update Sheets.",
"defaults": {
"serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/spreadsheets"
]
},
"guidanceMd": "Connect Google Sheets to read and update spreadsheets.",
"warnings": [
"Spreadsheet updates require approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
},
{
"key": "customer-write-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "write",
"label": "Read & edit",
"description": "Read and update spreadsheet values, formulas, and dimensions."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client to read and update Sheets.",
"defaults": {
"serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/spreadsheets"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Drive, Sheets, and Sheets MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Spreadsheet updates require approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
},
{
"key": "local",
"label": "Use the Paperclip robot account",
"transport": "local_stdio",
"auth": "none",
"capabilityProfile": {
"key": "robot",
"label": "Share selected sheets",
"description": "Share only named spreadsheets with the Paperclip robot account."
},
"ownershipModes": [
"customer"
],
"whenToUse": "Share selected spreadsheets with the Paperclip robot account instead of connecting a Google identity.",
"defaults": {
"templateKey": "paperclip.google-sheets"
},

View File

@ -0,0 +1,183 @@
{
"schemaVersion": 1,
"slug": "google-slides",
"name": "Google Slides",
"description": "Read and update Google Slides presentations.",
"categories": [
"content",
"productivity"
],
"featured": true,
"branding": {
"logoUrl": "/brands/apps/google-slides.svg"
},
"urlPatterns": [
"https://slidesmcp.googleapis.com/*",
"https://docs.google.com/presentation/*"
],
"docsUrl": "https://developers.google.com/workspace/slides/api/reference/mcp",
"setupPrerequisite": {
"title": "Google Developer Preview access required",
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
"steps": [
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
],
"actionLabel": "Apply or verify Developer Preview enrollment",
"actionUrl": "https://developers.google.com/workspace/preview"
},
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "paperclip-read",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "slides.read",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Read presentation slides and content."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for read-only Slides access.",
"defaults": {
"serverUrl": "https://slidesmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/presentations.readonly"
]
},
"guidanceMd": "Connect Google Slides to read presentations.",
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Read only",
"description": "Read presentation slides and content."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client for read-only Slides access.",
"defaults": {
"serverUrl": "https://slidesmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/presentations.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Drive, Slides, and Slides MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "paperclip-write",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "slides.write",
"capabilityProfile": {
"key": "write",
"label": "Read & edit",
"description": "Read and update presentations."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth to read and update Slides.",
"defaults": {
"serverUrl": "https://slidesmcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/presentations"
]
},
"guidanceMd": "Connect Google Slides to read and update presentations.",
"warnings": [
"Presentation updates require approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
},
{
"key": "customer-write-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "write",
"label": "Read & edit",
"description": "Read and update presentations."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client to read and update Slides.",
"defaults": {
"serverUrl": "https://slidesmcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/presentations"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Drive, Slides, and Slides MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
},
"warnings": [
"Presentation updates require approval.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S4"
}
]
}

View File

@ -0,0 +1,111 @@
{
"schemaVersion": 1,
"slug": "google-workspace-search",
"name": "Google Workspace Search",
"description": "Search Gmail, Drive, Calendar, and Chat through one read-only Google Workspace search tool.",
"categories": [
"data",
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/google-workspace-search.svg"
},
"urlPatterns": [
"https://workspacemcp.googleapis.com/*"
],
"docsUrl": "https://developers.google.com/workspace/guides/universal-search-mcp",
"setupPrerequisite": {
"title": "Google Developer Preview access required",
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
"steps": [
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
],
"actionLabel": "Apply or verify Developer Preview enrollment",
"actionUrl": "https://developers.google.com/workspace/preview"
},
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "paperclip-read",
"label": "Connect with Paperclip",
"transport": "mcp_remote",
"auth": "oauth",
"oauthStrategy": "paperclip_id_connector",
"connectorProfile": "workspace-search.read",
"capabilityProfile": {
"key": "read",
"label": "Search Workspace",
"description": "Search Gmail, Drive, Calendar, and Chat without write access."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"platform_shared"
],
"whenToUse": "Use Paperclip-managed OAuth for cross-product Workspace search.",
"defaults": {
"serverUrl": "https://workspacemcp.googleapis.com/mcp/v1",
"scopesHint": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/chat.messages.readonly"
]
},
"guidanceMd": "Connect Google Workspace Search for one read-only search tool spanning Gmail, Drive, Calendar, and Chat.",
"warnings": [
"This requests read access to all four supported search corpora.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
},
{
"key": "customer-read-oauth",
"label": "Use your own Google OAuth app",
"transport": "mcp_remote",
"auth": "oauth",
"capabilityProfile": {
"key": "read",
"label": "Search Workspace",
"description": "Search Gmail, Drive, Calendar, and Chat without write access."
},
"grantKinds": [
"user"
],
"ownershipModes": [
"customer"
],
"whenToUse": "Use a customer-owned OAuth client for cross-product Workspace search.",
"defaults": {
"serverUrl": "https://workspacemcp.googleapis.com/mcp/v1",
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"tokenEndpoint": "https://oauth2.googleapis.com/token",
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
"scopesHint": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/chat.messages.readonly"
],
"oauthAuthorizationParams": {
"access_type": "offline",
"prompt": "consent"
}
},
"guidanceMd": "Enable the Gmail, Drive, Calendar, Chat, and Workspace MCP APIs, then register Paperclip's callback URI.",
"consoleLinks": {
"register": "https://console.cloud.google.com/auth/clients",
"docs": "https://developers.google.com/workspace/guides/universal-search-mcp"
},
"warnings": [
"This requests read access to all four supported search corpora.",
"Google Workspace MCP servers are in Developer Preview."
],
"riskTier": "S3"
}
]
}

View File

@ -0,0 +1,44 @@
{
"schemaVersion": 1,
"slug": "hugging-face",
"name": "Hugging Face",
"description": "Connect Hugging Face's provider-hosted MCP server.",
"categories": [
"ai"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/hugging-face.svg"
},
"urlPatterns": [
"https://huggingface.co/*"
],
"docsUrl": "https://huggingface.co/docs/hub/agents-mcp",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://huggingface.co/mcp?login&gradio=none",
"scopesHint": [
"read-mcp"
]
},
"guidanceMd": "Connect Hugging Face in the browser. A Hugging Face account.",
"riskTier": "S2",
"label": "Sign in with Hugging Face",
"consoleLinks": {
"docs": "https://huggingface.co/docs/hub/agents-mcp"
},
"warnings": [
"A Hugging Face account."
]
}
]
}

View File

@ -0,0 +1,66 @@
{
"schemaVersion": 1,
"slug": "jira",
"name": "Jira",
"description": "Connect Jira's provider-hosted MCP server.",
"categories": [
"productivity"
],
"featured": true,
"branding": {
"logoUrl": "/brands/apps/jira.svg",
"darkLogoUrl": "/brands/apps/jira-dark.svg"
},
"urlPatterns": [
"https://mcp.atlassian.com/*"
],
"docsUrl": "https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.atlassian.com/v1/mcp/authv2",
"scopesHint": [
"read:me",
"read:account",
"offline_access",
"email",
"read:jira-work",
"write:jira-work",
"search:confluence",
"read:confluence-user",
"read:page:confluence",
"write:page:confluence",
"read:comment:confluence",
"write:comment:confluence",
"read:space:confluence",
"read:hierarchical-content:confluence",
"write:component:compass",
"read:component:compass",
"read:scorecard:compass",
"write:scorecard:compass",
"read:event:compass",
"read:metric:compass",
"read:all:twg",
"write:all:twg"
]
},
"guidanceMd": "Connect Jira in the browser. An active Jira or Confluence site; tenant policy may require an administrator to approve the client.",
"riskTier": "S3",
"label": "Sign in with Jira",
"consoleLinks": {
"docs": "https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/"
},
"warnings": [
"An active Jira or Confluence site; tenant policy may require an administrator to approve the client."
]
}
]
}

View File

@ -0,0 +1,78 @@
{
"schemaVersion": 1,
"slug": "kernel",
"name": "Kernel",
"description": "Connect Kernel's provider-hosted MCP server.",
"categories": [
"developer"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/kernel.svg"
},
"urlPatterns": [
"https://mcp.onkernel.com/*"
],
"docsUrl": "https://www.kernel.sh/docs/reference/mcp-server/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.onkernel.com/mcp"
},
"guidanceMd": "Connect Kernel in the browser. A Kernel account with either browser authorization or an API key.",
"riskTier": "S3",
"label": "Sign in with Kernel",
"consoleLinks": {
"docs": "https://www.kernel.sh/docs/reference/mcp-server/"
},
"warnings": [
"A Kernel account with either browser authorization or an API key."
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.onkernel.com/mcp"
},
"guidanceMd": "Use a customer-created Kernel key. A Kernel account with either browser authorization or an API key.",
"riskTier": "S3",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Kernel API key",
"type": "password",
"required": true,
"placeholder": "Paste your Kernel API key",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "X-API-Key",
"prefix": null
},
"consoleLinks": {
"keys": "https://www.kernel.sh/docs/reference/mcp-server/",
"docs": "https://www.kernel.sh/docs/reference/mcp-server/"
},
"warnings": [
"A Kernel account with either browser authorization or an API key."
]
}
]
}

View File

@ -8,7 +8,7 @@
],
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=linear.app&sz=128"
"logoUrl": "/brands/apps/linear.svg"
},
"urlPatterns": [
"https://mcp.linear.app/*"
@ -19,8 +19,7 @@
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"customer",
"dcr"
"customer"
],
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
"defaults": {
@ -38,7 +37,25 @@
"workspace",
"team",
"project"
]
],
"credentialSources": {
"vercelConnect": {
"services": [
"linear"
],
"principalModes": [
"user"
],
"scopes": [
"read",
"write"
],
"header": {
"name": "Authorization",
"prefix": "Bearer "
}
}
}
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "local-falcon",
"name": "Local Falcon",
"description": "Connect Local Falcon's provider-hosted MCP server.",
"categories": [
"analytics"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/local-falcon.png"
},
"urlPatterns": [
"https://mcp.localfalcon.com/*"
],
"docsUrl": "https://docs.localfalcon.com/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.localfalcon.com"
},
"guidanceMd": "Connect Local Falcon in the browser. A Local Falcon account with MCP access.",
"riskTier": "S2",
"label": "Sign in with Local Falcon",
"consoleLinks": {
"docs": "https://docs.localfalcon.com/"
},
"warnings": [
"A Local Falcon account with MCP access."
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"slug": "make",
"name": "Make",
"description": "Connect Make's provider-hosted MCP server.",
"categories": [
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/make.svg",
"darkLogoUrl": "/brands/apps/make-dark.svg"
},
"urlPatterns": [
"https://mcp.make.com/*"
],
"docsUrl": "https://developers.make.com/mcp-server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.make.com"
},
"guidanceMd": "Connect Make in the browser. A Make account and access to the scenarios exposed to MCP.",
"riskTier": "S3",
"label": "Sign in with Make",
"consoleLinks": {
"docs": "https://developers.make.com/mcp-server"
},
"warnings": [
"A Make account and access to the scenarios exposed to MCP."
]
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "manufact",
"name": "Manufact",
"description": "Connect Manufact's provider-hosted MCP server.",
"categories": [
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/manufact.svg"
},
"urlPatterns": [
"https://mcp.manufact.com/*"
],
"docsUrl": "https://docs.manufact.com/mcp",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.manufact.com/mcp"
},
"guidanceMd": "Connect Manufact in the browser. A Manufact account with MCP access.",
"riskTier": "S3",
"label": "Sign in with Manufact",
"consoleLinks": {
"docs": "https://docs.manufact.com/mcp"
},
"warnings": [
"A Manufact account with MCP access."
]
}
]
}

View File

@ -0,0 +1,56 @@
{
"schemaVersion": 1,
"slug": "mem0",
"name": "Mem0",
"description": "Connect Mem0's provider-hosted MCP server.",
"categories": [
"ai"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/mem0.svg"
},
"urlPatterns": [
"https://mcp.mem0.ai/*"
],
"docsUrl": "https://docs.mem0.ai/platform/mem0-mcp",
"methods": [
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.mem0.ai/mcp/"
},
"guidanceMd": "Use a customer-created Mem0 key. A Mem0 API key; the live server currently requires the slash-normalized endpoint.",
"riskTier": "S3",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Mem0 API key",
"type": "password",
"required": true,
"placeholder": "m0sk_...",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
},
"consoleLinks": {
"keys": "https://docs.mem0.ai/platform/mem0-mcp",
"docs": "https://docs.mem0.ai/platform/mem0-mcp"
},
"warnings": [
"A Mem0 API key; the live server currently requires the slash-normalized endpoint."
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"slug": "miro",
"name": "Miro",
"description": "Connect Miro's provider-hosted MCP server.",
"categories": [
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/miro.svg",
"darkLogoUrl": "/brands/apps/miro-dark.svg"
},
"urlPatterns": [
"https://mcp.miro.com/*"
],
"docsUrl": "https://help.miro.com/hc/en-us/articles/31625301583890-How-to-enable-Miro-s-MCP-Server-user-guide",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.miro.com/"
},
"guidanceMd": "Connect Miro in the browser. A Miro account; enterprise administrators may restrict third-party MCP clients.",
"riskTier": "S3",
"label": "Sign in with Miro",
"consoleLinks": {
"docs": "https://help.miro.com/hc/en-us/articles/31625301583890-How-to-enable-Miro-s-MCP-Server-user-guide"
},
"warnings": [
"A Miro account; enterprise administrators may restrict third-party MCP clients."
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"slug": "mixpanel",
"name": "Mixpanel",
"description": "Connect Mixpanel's provider-hosted MCP server.",
"categories": [
"analytics"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/mixpanel.svg"
},
"urlPatterns": [
"https://mcp.mixpanel.com/*"
],
"docsUrl": "https://mixpanel.com/blog/mixpanel-mcp-server/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.mixpanel.com/mcp"
},
"guidanceMd": "Connect Mixpanel in the browser. A Mixpanel account; the hosted MCP server is currently beta.",
"riskTier": "S3",
"label": "Sign in with Mixpanel",
"consoleLinks": {
"docs": "https://mixpanel.com/blog/mixpanel-mcp-server/"
},
"warnings": [
"A Mixpanel account; the hosted MCP server is currently beta.",
"This provider's hosted MCP server is currently beta or preview."
]
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "netlify",
"name": "Netlify",
"description": "Connect Netlify's provider-hosted MCP server.",
"categories": [
"developer"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/netlify.svg"
},
"urlPatterns": [
"https://netlify-mcp.netlify.app/*"
],
"docsUrl": "https://docs.netlify.com/build/build-with-ai/agent-setup-guides/agent-setup-overview/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://netlify-mcp.netlify.app/mcp"
},
"guidanceMd": "Connect Netlify in the browser. A Netlify account with access to the relevant team and sites.",
"riskTier": "S3",
"label": "Sign in with Netlify",
"consoleLinks": {
"docs": "https://docs.netlify.com/build/build-with-ai/agent-setup-guides/agent-setup-overview/"
},
"warnings": [
"A Netlify account with access to the relevant team and sites."
]
}
]
}

View File

@ -8,7 +8,8 @@
],
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=notion.so&sz=128"
"logoUrl": "/brands/apps/notion.svg",
"darkLogoUrl": "/brands/apps/notion-dark.svg"
},
"urlPatterns": [
"https://mcp.notion.com/*"
@ -32,8 +33,26 @@
"workspace",
"page",
"database"
]
],
"credentialSources": {
"vercelConnect": {
"services": [
"notion"
],
"principalModes": [
"user"
],
"scopes": [
"*"
],
"header": {
"name": "Authorization",
"prefix": "Bearer "
}
}
}
}
],
"redirectConstraints": "https-or-loopback-http"
"redirectConstraints": "https-or-loopback-http",
"docsUrl": "https://developers.notion.com/guides/mcp/build-mcp-client"
}

View File

@ -8,7 +8,7 @@
],
"featured": false,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=oauth.net&sz=128"
"logoUrl": "/brands/apps/oauth-generic.svg"
},
"urlPatterns": [],
"methods": [

View File

@ -0,0 +1,78 @@
{
"schemaVersion": 1,
"slug": "oreilly",
"name": "O'Reilly",
"description": "Connect O'Reilly's provider-hosted MCP server.",
"categories": [
"content"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/oreilly.svg"
},
"urlPatterns": [
"https://api.oreilly.com/*"
],
"docsUrl": "https://learning.oreilly.com/apidocs/mcp/content/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://api.oreilly.com/api/content-discovery/v1/mcp/"
},
"guidanceMd": "Connect O'Reilly in the browser. An O'Reilly Learning subscription with MCP or API access.",
"riskTier": "S2",
"label": "Sign in with O'Reilly",
"consoleLinks": {
"docs": "https://learning.oreilly.com/apidocs/mcp/content/"
},
"warnings": [
"An O'Reilly Learning subscription with MCP or API access."
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://api.oreilly.com/api/content-discovery/v1/mcp/"
},
"guidanceMd": "Use a customer-created O'Reilly key. An O'Reilly Learning subscription with MCP or API access.",
"riskTier": "S2",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "O'Reilly API key",
"type": "password",
"required": true,
"placeholder": "Paste your O'Reilly API token",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
},
"consoleLinks": {
"keys": "https://learning.oreilly.com/apidocs/mcp/content/",
"docs": "https://learning.oreilly.com/apidocs/mcp/content/"
},
"warnings": [
"An O'Reilly Learning subscription with MCP or API access."
]
}
]
}

View File

@ -0,0 +1,93 @@
{
"schemaVersion": 1,
"slug": "pagerduty",
"name": "PagerDuty",
"description": "Connect PagerDuty's provider-hosted MCP server.",
"categories": [
"developer"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/pagerduty.svg"
},
"urlPatterns": [
"https://mcp.pagerduty.com/*"
],
"docsUrl": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server",
"methods": [
{
"key": "mcp-api-key-us",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.pagerduty.com/mcp"
},
"guidanceMd": "Use a customer-created PagerDuty key. A PagerDuty API token; choose the regional endpoint that hosts the account.",
"riskTier": "S4",
"label": "US service region",
"credentialFields": [
{
"key": "authorization",
"label": "PagerDuty API key",
"type": "password",
"required": true,
"placeholder": "Paste your PagerDuty user API token",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Token token="
},
"consoleLinks": {
"keys": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server",
"docs": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server"
},
"warnings": [
"A PagerDuty API token; choose the regional endpoint that hosts the account."
]
},
{
"key": "mcp-api-key-eu",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.eu.pagerduty.com/mcp"
},
"guidanceMd": "Use a customer-created PagerDuty key. A PagerDuty API token; choose the regional endpoint that hosts the account.",
"riskTier": "S4",
"label": "EU service region",
"credentialFields": [
{
"key": "authorization",
"label": "PagerDuty API key",
"type": "password",
"required": true,
"placeholder": "Paste your PagerDuty user API token",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Token token="
},
"consoleLinks": {
"keys": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server",
"docs": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server"
},
"warnings": [
"A PagerDuty API token; choose the regional endpoint that hosts the account."
]
}
]
}

View File

@ -0,0 +1,91 @@
{
"schemaVersion": 1,
"slug": "planetscale",
"name": "PlanetScale",
"description": "Connect PlanetScale's provider-hosted MCP server.",
"categories": [
"data"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/planetscale.svg",
"darkLogoUrl": "/brands/apps/planetscale-dark.svg"
},
"urlPatterns": [
"https://mcp.pscale.dev/*"
],
"docsUrl": "https://planetscale.com/docs/connect/mcp",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.pscale.dev/mcp/planetscale"
},
"guidanceMd": "Connect PlanetScale in the browser. A PlanetScale account; database and branch access are chosen during authorization.",
"riskTier": "S4",
"label": "Database access",
"consoleLinks": {
"docs": "https://planetscale.com/docs/connect/mcp"
},
"warnings": [
"A PlanetScale account; database and branch access are chosen during authorization."
],
"tenantFields": [
{
"key": "project",
"label": "Project or database",
"type": "text",
"advanced": true,
"placeholder": "Optional project or database name",
"helperMd": "Records the intended database boundary; final access is selected during PlanetScale authorization."
},
{
"key": "branch",
"label": "Branch",
"type": "text",
"advanced": true,
"placeholder": "Optional branch name",
"helperMd": "Records the intended branch boundary; final access is selected during PlanetScale authorization."
}
],
"requiredResourceFilters": [
"organization",
"database",
"branch"
]
},
{
"key": "mcp-insights-only",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use query insights and schema recommendations without query execution tools.",
"defaults": {
"serverUrl": "https://mcp.pscale.dev/mcp/planetscale-insights-only"
},
"guidanceMd": "Connect PlanetScale in the browser. A PlanetScale account; database and branch access are chosen during authorization.",
"riskTier": "S4",
"label": "Insights only",
"consoleLinks": {
"docs": "https://planetscale.com/docs/connect/mcp"
},
"warnings": [
"A PlanetScale account; database and branch access are chosen during authorization."
],
"requiredResourceFilters": [
"organization",
"database",
"branch"
]
}
]
}

View File

@ -2,13 +2,14 @@
"schemaVersion": 1,
"slug": "posthog",
"name": "PostHog",
"description": "Analyze product usage, errors, feature flags, and experiments in a pinned PostHog project.",
"description": "Analyze product usage, errors, feature flags, and experiments with PostHog's hosted MCP server.",
"categories": [
"analytics"
],
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=posthog.com&sz=128"
"logoUrl": "/brands/apps/posthog.svg",
"darkLogoUrl": "/brands/apps/posthog-dark.svg"
},
"urlPatterns": [
"https://mcp.posthog.com/*"
@ -26,16 +27,16 @@
"defaults": {
"serverUrl": "https://mcp.posthog.com/mcp"
},
"guidanceMd": "Pin the connection to one PostHog project and expose the full tool catalog by default. Narrow feature groups or tools only when needed.",
"guidanceMd": "Connect with PostHog's recommended defaults. Project pinning, read-only access, and catalog filters are optional advanced controls.",
"riskTier": "S3",
"tenantFields": [
{
"key": "projectId",
"label": "Project ID",
"label": "Pin to project ID",
"type": "text",
"required": true,
"placeholder": "12345",
"helperMd": "Find the numeric project ID in PostHog project settings.",
"advanced": true,
"placeholder": "Optional numeric project ID",
"helperMd": "Optional. Pin this connection to one project and remove PostHog's project-switching tool.",
"validation": {
"pattern": "^[0-9]+$",
"maxLength": 32
@ -49,6 +50,7 @@
"key": "readOnly",
"label": "Read-only mode",
"type": "checkbox",
"advanced": true,
"defaultValue": false,
"helperMd": "Turn on to hide tools that can change PostHog data.",
"transport": {
@ -94,7 +96,7 @@
"key": "mode",
"label": "Tool response mode",
"type": "select",
"advanced": true,
"hidden": true,
"required": true,
"placeholder": "Individual tools",
"defaultValue": "tools",
@ -111,12 +113,27 @@
}
}
],
"requiredResourceFilters": [
"project"
],
"label": "Sign in with PostHog",
"consoleLinks": {
"docs": "https://posthog.com/docs/model-context-protocol"
},
"credentialSources": {
"vercelConnect": {
"services": [
"posthog",
"mcp.posthog.com/mcp"
],
"principalModes": [
"user"
],
"scopes": [
"*"
],
"header": {
"name": "Authorization",
"prefix": "Bearer "
}
}
}
},
{
@ -130,16 +147,16 @@
"defaults": {
"serverUrl": "https://mcp.posthog.com/mcp"
},
"guidanceMd": "Pin the connection to one PostHog project and expose the full tool catalog by default. Narrow feature groups or tools only when needed.",
"guidanceMd": "Connect with PostHog's recommended defaults. Project pinning, read-only access, and catalog filters are optional advanced controls.",
"riskTier": "S3",
"tenantFields": [
{
"key": "projectId",
"label": "Project ID",
"label": "Pin to project ID",
"type": "text",
"required": true,
"placeholder": "12345",
"helperMd": "Find the numeric project ID in PostHog project settings.",
"advanced": true,
"placeholder": "Optional numeric project ID",
"helperMd": "Optional. Pin this connection to one project and remove PostHog's project-switching tool.",
"validation": {
"pattern": "^[0-9]+$",
"maxLength": 32
@ -153,6 +170,7 @@
"key": "readOnly",
"label": "Read-only mode",
"type": "checkbox",
"advanced": true,
"defaultValue": false,
"helperMd": "Turn on to hide tools that can change PostHog data.",
"transport": {
@ -198,7 +216,7 @@
"key": "mode",
"label": "Tool response mode",
"type": "select",
"advanced": true,
"hidden": true,
"required": true,
"placeholder": "Individual tools",
"defaultValue": "tools",
@ -215,9 +233,6 @@
}
}
],
"requiredResourceFilters": [
"project"
],
"label": "Use a personal API key",
"credentialFields": [
{
@ -237,7 +252,27 @@
"consoleLinks": {
"keys": "https://posthog.com/docs/model-context-protocol/faq",
"docs": "https://posthog.com/docs/model-context-protocol/faq"
},
"credentialSources": {
"vercelConnect": {
"services": [
"posthog",
"mcp.posthog.com/mcp"
],
"principalModes": [
"app"
],
"scopes": [
"*"
],
"header": {
"name": "Authorization",
"prefix": "Bearer "
}
}
}
}
]
],
"docsUrl": "https://posthog.com/docs/model-context-protocol",
"redirectConstraints": "https-or-loopback-http"
}

View File

@ -0,0 +1,194 @@
{
"schemaVersion": 1,
"slug": "postman",
"name": "Postman",
"description": "Connect Postman's provider-hosted MCP server.",
"categories": [
"developer"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/postman.svg"
},
"urlPatterns": [
"https://mcp.postman.com/*"
],
"docsUrl": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth-minimal",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.postman.com/minimal"
},
"guidanceMd": "Connect Postman in the browser. A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints.",
"riskTier": "S3",
"label": "US · Minimal",
"consoleLinks": {
"docs": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server"
},
"warnings": [
"A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints."
]
},
{
"key": "mcp-oauth-code",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.postman.com/code"
},
"guidanceMd": "Connect Postman in the browser. A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints.",
"riskTier": "S3",
"label": "US · Code",
"consoleLinks": {
"docs": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server"
},
"warnings": [
"A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints."
]
},
{
"key": "mcp-oauth-full",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.postman.com/mcp"
},
"guidanceMd": "Connect Postman in the browser. A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints.",
"riskTier": "S3",
"label": "US · Full",
"consoleLinks": {
"docs": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server"
},
"warnings": [
"A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints."
]
},
{
"key": "mcp-eu-key-minimal",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.eu.postman.com/minimal"
},
"guidanceMd": "Use a customer-created Postman key. A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints.",
"riskTier": "S3",
"label": "EU · Minimal",
"credentialFields": [
{
"key": "authorization",
"label": "Postman API key",
"type": "password",
"required": true,
"placeholder": "PMAK-...",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "X-API-Key",
"prefix": null
},
"consoleLinks": {
"keys": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server",
"docs": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server"
},
"warnings": [
"A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints."
]
},
{
"key": "mcp-eu-key-code",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.eu.postman.com/code"
},
"guidanceMd": "Use a customer-created Postman key. A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints.",
"riskTier": "S3",
"label": "EU · Code",
"credentialFields": [
{
"key": "authorization",
"label": "Postman API key",
"type": "password",
"required": true,
"placeholder": "PMAK-...",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "X-API-Key",
"prefix": null
},
"consoleLinks": {
"keys": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server",
"docs": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server"
},
"warnings": [
"A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints."
]
},
{
"key": "mcp-eu-key-full",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.eu.postman.com/mcp"
},
"guidanceMd": "Use a customer-created Postman key. A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints.",
"riskTier": "S3",
"label": "EU · Full",
"credentialFields": [
{
"key": "authorization",
"label": "Postman API key",
"type": "password",
"required": true,
"placeholder": "PMAK-...",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "X-API-Key",
"prefix": null
},
"consoleLinks": {
"keys": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server",
"docs": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server"
},
"warnings": [
"A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints."
]
}
]
}

View File

@ -0,0 +1,81 @@
{
"schemaVersion": 1,
"slug": "razorpay",
"name": "Razorpay",
"description": "Connect Razorpay's provider-hosted MCP server.",
"categories": [
"commerce"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/razorpay.svg",
"darkLogoUrl": "/brands/apps/razorpay-dark.svg"
},
"urlPatterns": [
"https://mcp.razorpay.com/*"
],
"docsUrl": "https://razorpay.com/docs/mcp-server/oauth/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.razorpay.com/mcp"
},
"guidanceMd": "Connect Razorpay in the browser. A Razorpay account; financial or destructive actions always require explicit approval.",
"riskTier": "S4",
"label": "Sign in with Razorpay",
"consoleLinks": {
"docs": "https://razorpay.com/docs/mcp-server/oauth/"
},
"warnings": [
"A Razorpay account; financial or destructive actions always require explicit approval.",
"Financial or destructive actions must be explicitly approved before execution."
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.razorpay.com/mcp"
},
"guidanceMd": "Use a customer-created Razorpay key. A Razorpay account; financial or destructive actions always require explicit approval.",
"riskTier": "S4",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Razorpay API key",
"type": "password",
"required": true,
"placeholder": "Paste the base64-encoded key ID and secret",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Basic "
},
"consoleLinks": {
"keys": "https://razorpay.com/docs/mcp-server/oauth/",
"docs": "https://razorpay.com/docs/mcp-server/oauth/"
},
"warnings": [
"A Razorpay account; financial or destructive actions always require explicit approval.",
"Financial or destructive actions must be explicitly approved before execution."
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"slug": "resend",
"name": "Resend",
"description": "Connect Resend's provider-hosted MCP server.",
"categories": [
"communication"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/resend.svg",
"darkLogoUrl": "/brands/apps/resend-dark.svg"
},
"urlPatterns": [
"https://mcp.resend.com/*"
],
"docsUrl": "https://resend.com/changelog/remote-mcp-server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.resend.com/mcp"
},
"guidanceMd": "Connect Resend in the browser. A Resend account with access to the relevant domains.",
"riskTier": "S3",
"label": "Sign in with Resend",
"consoleLinks": {
"docs": "https://resend.com/changelog/remote-mcp-server"
},
"warnings": [
"A Resend account with access to the relevant domains."
]
}
]
}

View File

@ -0,0 +1,79 @@
{
"schemaVersion": 1,
"slug": "sanity",
"name": "Sanity",
"description": "Connect Sanity's provider-hosted MCP server.",
"categories": [
"content"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/sanity.svg",
"darkLogoUrl": "/brands/apps/sanity-dark.svg"
},
"urlPatterns": [
"https://mcp.sanity.io/*"
],
"docsUrl": "https://www.sanity.io/docs/ai/mcp-server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.sanity.io"
},
"guidanceMd": "Connect Sanity in the browser. A Sanity account with access to the relevant projects and datasets.",
"riskTier": "S3",
"label": "Sign in with Sanity",
"consoleLinks": {
"docs": "https://www.sanity.io/docs/ai/mcp-server"
},
"warnings": [
"A Sanity account with access to the relevant projects and datasets."
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.sanity.io"
},
"guidanceMd": "Use a customer-created Sanity key. A Sanity account with access to the relevant projects and datasets.",
"riskTier": "S3",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Sanity API key",
"type": "password",
"required": true,
"placeholder": "sk...",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
},
"consoleLinks": {
"keys": "https://www.sanity.io/docs/ai/mcp-server",
"docs": "https://www.sanity.io/docs/ai/mcp-server"
},
"warnings": [
"A Sanity account with access to the relevant projects and datasets."
]
}
]
}

View File

@ -8,7 +8,8 @@
],
"featured": false,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=sentry.io&sz=128"
"logoUrl": "/brands/apps/sentry.svg",
"darkLogoUrl": "/brands/apps/sentry-dark.svg"
},
"urlPatterns": [
"https://mcp.sentry.dev/*"
@ -35,5 +36,7 @@
"environment"
]
}
]
],
"docsUrl": "https://mcp.sentry.dev/.well-known/oauth-authorization-server",
"redirectConstraints": "https-or-loopback-http"
}

View File

@ -0,0 +1,57 @@
{
"schemaVersion": 1,
"slug": "shopify",
"name": "Shopify",
"description": "Search a store's products and policies, and manage shopping carts.",
"categories": [
"commerce"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/shopify.svg"
},
"urlPatterns": [
"https://*.myshopify.com/api/mcp"
],
"methods": [
{
"key": "storefront-mcp",
"transport": "mcp_remote",
"auth": "none",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a store's public myshopify.com domain. No Shopify app or OAuth registration is required.",
"defaults": {
"serverUrlTemplate": "https://{storeDomain}/api/mcp"
},
"guidanceMd": "Connect Shopify's official Storefront MCP server for shopper-facing catalog, policy, and cart tools.",
"riskTier": "S3",
"label": "Connect a Shopify storefront",
"tenantFields": [
{
"key": "storeDomain",
"label": "Store domain",
"type": "text",
"required": true,
"placeholder": "your-store.myshopify.com",
"helperMd": "Enter the full myshopify.com domain without https://.",
"validation": {
"pattern": "^[A-Za-z0-9][A-Za-z0-9-]*\\.myshopify\\.com$",
"maxLength": 255
}
}
],
"consoleLinks": {
"docs": "https://shopify.dev/docs/apps/build/storefront-mcp/servers/storefront"
},
"warnings": [
"This is Shopify's Storefront MCP, not Admin API access. It does not manage merchant products, orders, or customers.",
"The storefront must be publicly reachable. Password-protected or restricted trial stores can return HTTP 401."
],
"requiredResourceFilters": [
"store"
]
}
]
}

View File

@ -0,0 +1,57 @@
{
"schemaVersion": 1,
"slug": "similarweb",
"name": "Similarweb",
"description": "Connect Similarweb's provider-hosted MCP server.",
"categories": [
"analytics"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/similarweb.svg",
"darkLogoUrl": "/brands/apps/similarweb-dark.svg"
},
"urlPatterns": [
"https://mcp.similarweb.com/*"
],
"docsUrl": "https://developers.similarweb.com/docs/similarweb-mcp",
"methods": [
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.similarweb.com"
},
"guidanceMd": "Use a customer-created Similarweb key. A Similarweb subscription with API access and an API key.",
"riskTier": "S2",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Similarweb API key",
"type": "password",
"required": true,
"placeholder": "Paste your Similarweb API key",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "api-key",
"prefix": null
},
"consoleLinks": {
"keys": "https://developers.similarweb.com/docs/similarweb-mcp",
"docs": "https://developers.similarweb.com/docs/similarweb-mcp"
},
"warnings": [
"A Similarweb subscription with API access and an API key."
]
}
]
}

View File

@ -8,7 +8,7 @@
],
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=slack.com&sz=128"
"logoUrl": "/brands/apps/slack.png"
},
"urlPatterns": [
"https://mcp.slack.com/*"
@ -19,8 +19,7 @@
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"customer",
"dcr"
"customer"
],
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
"defaults": {

View File

@ -0,0 +1,80 @@
{
"schemaVersion": 1,
"slug": "stripe",
"name": "Stripe",
"description": "Connect Stripe's provider-hosted MCP server.",
"categories": [
"commerce"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/stripe.svg"
},
"urlPatterns": [
"https://mcp.stripe.com/*"
],
"docsUrl": "https://docs.stripe.com/mcp",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.stripe.com"
},
"guidanceMd": "Connect Stripe in the browser. A Stripe account; the server is public preview and payment actions require explicit approval.",
"riskTier": "S4",
"label": "Sign in with Stripe",
"consoleLinks": {
"docs": "https://docs.stripe.com/mcp"
},
"warnings": [
"A Stripe account; the server is public preview and payment actions require explicit approval.",
"Financial or destructive actions must be explicitly approved before execution."
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.stripe.com"
},
"guidanceMd": "Use a customer-created Stripe key. A Stripe account; the server is public preview and payment actions require explicit approval.",
"riskTier": "S4",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Stripe API key",
"type": "password",
"required": true,
"placeholder": "sk_...",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
},
"consoleLinks": {
"keys": "https://docs.stripe.com/mcp",
"docs": "https://docs.stripe.com/mcp"
},
"warnings": [
"A Stripe account; the server is public preview and payment actions require explicit approval.",
"Financial or destructive actions must be explicitly approved before execution."
]
}
]
}

View File

@ -0,0 +1,164 @@
{
"schemaVersion": 1,
"slug": "supabase",
"name": "Supabase",
"description": "Connect Supabase's provider-hosted MCP server.",
"categories": [
"data"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/supabase.svg"
},
"urlPatterns": [
"https://mcp.supabase.com/*"
],
"docsUrl": "https://supabase.com/docs/guides/ai-tools/mcp",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.supabase.com/mcp"
},
"guidanceMd": "Connect Supabase in the browser and scope the connection to one development project. Write tools start enabled and remain governed by Paperclip's action policies.",
"riskTier": "S4",
"label": "Sign in with Supabase",
"consoleLinks": {
"docs": "https://supabase.com/docs/guides/ai-tools/mcp"
},
"warnings": [
"A Supabase account; use a development project and review write actions before connecting production data.",
"Do not connect production data unless you have reviewed Supabase's MCP security guidance."
],
"tenantFields": [
{
"key": "projectRef",
"label": "Project reference",
"type": "text",
"required": true,
"placeholder": "abcdefghijklmnopqrst",
"helperMd": "Scope the connection to one development project.",
"transport": {
"location": "query",
"name": "project_ref"
}
},
{
"key": "readOnly",
"label": "Read-only mode",
"type": "checkbox",
"defaultValue": false,
"helperMd": "Enable this to prevent the connection from changing the database.",
"transport": {
"location": "query",
"name": "read_only",
"format": "boolean"
}
},
{
"key": "features",
"label": "Feature groups",
"type": "textarea",
"advanced": true,
"placeholder": "database,docs",
"helperMd": "Optional comma-separated feature groups.",
"transport": {
"location": "query",
"name": "features",
"format": "csv"
}
}
],
"requiredResourceFilters": [
"project"
]
},
{
"key": "mcp-api-key",
"transport": "mcp_remote",
"auth": "api_key",
"ownershipModes": [
"customer"
],
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
"defaults": {
"serverUrl": "https://mcp.supabase.com/mcp"
},
"guidanceMd": "Use a customer-created Supabase key scoped to one development project. Write tools start enabled and remain governed by Paperclip's action policies.",
"riskTier": "S4",
"label": "Use an API key",
"credentialFields": [
{
"key": "authorization",
"label": "Supabase API key",
"type": "password",
"required": true,
"placeholder": "sbp_...",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
},
"consoleLinks": {
"keys": "https://supabase.com/docs/guides/ai-tools/mcp",
"docs": "https://supabase.com/docs/guides/ai-tools/mcp"
},
"warnings": [
"A Supabase account; use a development project and review write actions before connecting production data.",
"Do not connect production data unless you have reviewed Supabase's MCP security guidance."
],
"tenantFields": [
{
"key": "projectRef",
"label": "Project reference",
"type": "text",
"required": true,
"placeholder": "abcdefghijklmnopqrst",
"helperMd": "Scope the connection to one development project.",
"transport": {
"location": "query",
"name": "project_ref"
}
},
{
"key": "readOnly",
"label": "Read-only mode",
"type": "checkbox",
"defaultValue": false,
"helperMd": "Enable this to prevent the connection from changing the database.",
"transport": {
"location": "query",
"name": "read_only",
"format": "boolean"
}
},
{
"key": "features",
"label": "Feature groups",
"type": "textarea",
"advanced": true,
"placeholder": "database,docs",
"helperMd": "Optional comma-separated feature groups.",
"transport": {
"location": "query",
"name": "features",
"format": "csv"
}
}
],
"requiredResourceFilters": [
"project"
]
}
]
}

View File

@ -0,0 +1,42 @@
{
"schemaVersion": 1,
"slug": "ticket-tailor",
"name": "Ticket Tailor",
"description": "Connect Ticket Tailor's provider-hosted MCP server.",
"categories": [
"commerce"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/ticket-tailor.svg",
"darkLogoUrl": "/brands/apps/ticket-tailor-dark.svg"
},
"urlPatterns": [
"https://mcp.tickettailor.ai/*"
],
"docsUrl": "https://developers.tickettailor.com/docs/mcp/authentication/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.tickettailor.ai/mcp"
},
"guidanceMd": "Connect Ticket Tailor in the browser. A Ticket Tailor account; the provider may request an API key during its hosted authorization prompt.",
"riskTier": "S3",
"label": "Sign in with Ticket Tailor",
"consoleLinks": {
"docs": "https://developers.tickettailor.com/docs/mcp/authentication/"
},
"warnings": [
"A Ticket Tailor account; the provider may request an API key during its hosted authorization prompt."
]
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "ticktick",
"name": "TickTick",
"description": "Connect TickTick's provider-hosted MCP server.",
"categories": [
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/ticktick.svg"
},
"urlPatterns": [
"https://mcp.ticktick.com/*"
],
"docsUrl": "https://help.ticktick.com/articles/7438129581631995904",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.ticktick.com"
},
"guidanceMd": "Connect TickTick in the browser. A TickTick account with MCP access.",
"riskTier": "S3",
"label": "Sign in with TickTick",
"consoleLinks": {
"docs": "https://help.ticktick.com/articles/7438129581631995904"
},
"warnings": [
"A TickTick account with MCP access."
]
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "todoist",
"name": "Todoist",
"description": "Connect Todoist's provider-hosted MCP server.",
"categories": [
"productivity"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/todoist.svg"
},
"urlPatterns": [
"https://ai.todoist.net/*"
],
"docsUrl": "https://developer.todoist.com/",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://ai.todoist.net/mcp"
},
"guidanceMd": "Connect Todoist in the browser. A Todoist account.",
"riskTier": "S3",
"label": "Sign in with Todoist",
"consoleLinks": {
"docs": "https://developer.todoist.com/"
},
"warnings": [
"A Todoist account."
]
}
]
}

View File

@ -8,7 +8,8 @@
],
"featured": false,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=vercel.com&sz=128"
"logoUrl": "/brands/apps/vercel.svg",
"darkLogoUrl": "/brands/apps/vercel-dark.svg"
},
"urlPatterns": [
"https://mcp.vercel.com/*"
@ -34,5 +35,9 @@
"environment"
]
}
]
],
"availability": {
"available": false,
"reason": "Vercel currently reviews and approves MCP clients."
}
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "webflow",
"name": "Webflow",
"description": "Connect Webflow's provider-hosted MCP server.",
"categories": [
"content"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/webflow.svg"
},
"urlPatterns": [
"https://mcp.webflow.com/*"
],
"docsUrl": "https://developers.webflow.com/mcp/reference/getting-started",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.webflow.com/mcp"
},
"guidanceMd": "Connect Webflow in the browser. A Webflow account; workspace and site roles constrain accessible sites.",
"riskTier": "S3",
"label": "Sign in with Webflow",
"consoleLinks": {
"docs": "https://developers.webflow.com/mcp/reference/getting-started"
},
"warnings": [
"A Webflow account; workspace and site roles constrain accessible sites."
]
}
]
}

View File

@ -0,0 +1,41 @@
{
"schemaVersion": 1,
"slug": "wix",
"name": "Wix",
"description": "Connect Wix's provider-hosted MCP server.",
"categories": [
"content"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/wix.svg"
},
"urlPatterns": [
"https://mcp.wix.com/*"
],
"docsUrl": "https://www.wix.com/studio/developers/mcp-server",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"dcr"
],
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
"defaults": {
"serverUrl": "https://mcp.wix.com/mcp"
},
"guidanceMd": "Connect Wix in the browser. A Wix account with access to the relevant sites.",
"riskTier": "S3",
"label": "Sign in with Wix",
"consoleLinks": {
"docs": "https://www.wix.com/studio/developers/mcp-server"
},
"warnings": [
"A Wix account with access to the relevant sites."
]
}
]
}

View File

@ -0,0 +1,53 @@
{
"schemaVersion": 1,
"slug": "xero",
"name": "Xero",
"description": "Connect Xero's provider-hosted MCP server.",
"categories": [
"commerce"
],
"featured": false,
"branding": {
"logoUrl": "/brands/apps/xero.svg"
},
"urlPatterns": [
"https://mcp.xero.com/*"
],
"docsUrl": "https://developer.xero.com/ai",
"redirectConstraints": "https-or-loopback-http",
"methods": [
{
"key": "mcp-own-oauth",
"transport": "mcp_remote",
"auth": "oauth",
"ownershipModes": [
"customer"
],
"whenToUse": "Register an OAuth app with Xero, then enter its client ID and secret.",
"defaults": {
"serverUrl": "https://mcp.xero.com/mcp",
"scopesHint": [
"openid",
"profile",
"email",
"offline_access",
"accounting.settings",
"accounting.invoices.read",
"accounting.reports.aged.read",
"accounting.reports.balancesheet.read",
"accounting.reports.profitandloss.read"
]
},
"guidanceMd": "Connect Xero in the browser. Create a Xero OAuth app and confirm the applicable AI and data-use terms before connecting.",
"riskTier": "S4",
"label": "Use your own OAuth app",
"consoleLinks": {
"register": "https://developer.xero.com/ai",
"docs": "https://developer.xero.com/ai"
},
"warnings": [
"Create a Xero OAuth app and confirm the applicable AI and data-use terms before connecting."
]
}
]
}

View File

@ -8,40 +8,25 @@
],
"featured": true,
"branding": {
"logoUrl": "https://www.google.com/s2/favicons?domain=zapier.com&sz=128"
"logoUrl": "/brands/apps/zapier.svg"
},
"urlPatterns": [
"https://mcp.zapier.com/*"
],
"methods": [
{
"key": "mcp-key",
"key": "generated-url",
"transport": "mcp_remote",
"auth": "api_key",
"auth": "none",
"ownershipModes": [
"customer"
],
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
"defaults": {
"serverUrl": "https://mcp.zapier.com/api/mcp"
},
"guidanceMd": "Create a Zapier MCP connection, then paste its token here.",
"whenToUse": "Use the complete provider-generated MCP URL from Zapier.",
"defaults": {},
"guidanceMd": "Create a Zapier MCP server, then paste the complete generated connection URL. The token remains embedded in that URL.",
"riskTier": "S3",
"credentialFields": [
{
"key": "authorization",
"label": "Zapier MCP token",
"type": "password",
"required": true,
"placeholder": "Paste your Zapier token",
"secret": true
}
],
"keyPlacement": {
"location": "header",
"name": "Authorization",
"prefix": "Bearer "
}
"label": "Paste generated MCP URL"
}
]
],
"docsUrl": "https://docs.zapier.com/mcp/quickstart"
}

View File

@ -0,0 +1,41 @@
export const GOOGLE_WORKSPACE_CONNECTOR_PROFILE_IDS = [
"gmail.read", "gmail.draft", "drive.read", "drive.write", "docs.read", "docs.write",
"sheets.read", "sheets.write", "slides.read", "slides.write", "calendar.read",
"calendar.write", "chat.read", "chat.write", "people.read", "workspace-search.read",
] as const;
export type GoogleWorkspaceConnectorProfileId = (typeof GOOGLE_WORKSPACE_CONNECTOR_PROFILE_IDS)[number];
const auth = (scope: string) => `https://www.googleapis.com/auth/${scope}`;
export const GOOGLE_WORKSPACE_CONNECTOR_PROFILES: Readonly<Record<GoogleWorkspaceConnectorProfileId, {
appSlug: string;
serverUrl: string;
scopes: readonly string[];
writeTools: readonly string[];
}>> = {
"gmail.read": def("gmail", "https://gmailmcp.googleapis.com/mcp/v1", [auth("gmail.readonly")]),
"gmail.draft": def("gmail", "https://gmailmcp.googleapis.com/mcp/v1", [auth("gmail.readonly"), auth("gmail.compose")], ["create_draft"]),
"drive.read": def("google-drive", "https://drivemcp.googleapis.com/mcp/v1", [auth("drive.readonly")]),
"drive.write": def("google-drive", "https://drivemcp.googleapis.com/mcp/v1", [auth("drive.readonly"), auth("drive.file")], ["copy_file", "create_file"]),
"docs.read": def("google-docs", "https://docsmcp.googleapis.com/mcp/v1", [auth("drive.readonly"), auth("documents.readonly")]),
"docs.write": def("google-docs", "https://docsmcp.googleapis.com/mcp/v1", [auth("drive.readonly"), auth("drive.file"), auth("documents")], ["update_doc"]),
"sheets.read": def("google-sheets", "https://sheetsmcp.googleapis.com/mcp/v1", [auth("drive.readonly"), auth("spreadsheets.readonly")]),
"sheets.write": def("google-sheets", "https://sheetsmcp.googleapis.com/mcp/v1", [auth("drive.readonly"), auth("drive.file"), auth("spreadsheets")], ["update_spreadsheet", "update_values", "update_formulas", "insert_dimension"]),
"slides.read": def("google-slides", "https://slidesmcp.googleapis.com/mcp/v1", [auth("drive.readonly"), auth("presentations.readonly")]),
"slides.write": def("google-slides", "https://slidesmcp.googleapis.com/mcp/v1", [auth("drive.readonly"), auth("drive.file"), auth("presentations")], ["update_presentation"]),
"calendar.read": def("google-calendar", "https://calendarmcp.googleapis.com/mcp/v1", [auth("calendar.calendarlist.readonly"), auth("calendar.events.freebusy"), auth("calendar.events.readonly")]),
"calendar.write": def("google-calendar", "https://calendarmcp.googleapis.com/mcp/v1", [auth("calendar.calendarlist.readonly"), auth("calendar.events.freebusy"), auth("calendar.events")], ["create_event", "update_event", "delete_event", "respond_to_event"]),
"chat.read": def("google-chat", "https://chatmcp.googleapis.com/mcp/v1", [auth("chat.spaces.readonly"), auth("chat.memberships.readonly"), auth("chat.messages.readonly"), auth("chat.users.readstate.readonly")]),
"chat.write": def("google-chat", "https://chatmcp.googleapis.com/mcp/v1", [auth("chat.spaces.readonly"), auth("chat.memberships.readonly"), auth("chat.messages.readonly"), auth("chat.users.readstate.readonly"), auth("chat.messages.create")], ["send_message"]),
"people.read": def("google-people", "https://people.googleapis.com/mcp/v1", [auth("directory.readonly"), auth("userinfo.profile"), auth("contacts.readonly")]),
"workspace-search.read": def("google-workspace-search", "https://workspacemcp.googleapis.com/mcp/v1", [auth("gmail.readonly"), auth("drive.readonly"), auth("calendar.readonly"), auth("chat.messages.readonly")]),
};
export function isGoogleWorkspaceConnectorProfileId(value: string): value is GoogleWorkspaceConnectorProfileId {
return Object.prototype.hasOwnProperty.call(GOOGLE_WORKSPACE_CONNECTOR_PROFILES, value);
}
function def(appSlug: string, serverUrl: string, scopes: readonly string[], writeTools: readonly string[] = []) {
return { appSlug, serverUrl, scopes, writeTools };
}

View File

@ -269,6 +269,7 @@ export {
getAvailableConnectionMethods,
getConnectableAppDefinition,
recommendedDefaultsForApp,
resolveConnectionMethodServerUrl,
} from "./app-definitions.js";
export { APP_DEFINITIONS } from "./app-definitions.generated.js";
export * from "./validators/status-card.js";

View File

@ -0,0 +1,55 @@
{
"schemaVersion": 1,
"verifiedAt": "2026-08-26",
"entries": [
{ "slug": "jira", "name": "Jira", "wave": 1, "status": "self_serve", "docsUrl": "https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/", "serverUrl": "https://mcp.atlassian.com/v1/mcp/authv2", "authMode": "dcr_cimd", "prerequisite": "An active Jira or Confluence site; tenant policy may require an administrator to approve the client.", "riskTier": "S3" },
{ "slug": "airtable", "name": "Airtable", "wave": 1, "status": "self_serve", "docsUrl": "https://support.airtable.com/articles/9897799762-using-the-airtable-mcp-server", "serverUrl": "https://mcp.airtable.com/mcp", "authMode": "dcr", "prerequisite": "An Airtable account; enterprise administrators may need to allowlist the client.", "riskTier": "S3" },
{ "slug": "beehiiv", "name": "beehiiv", "wave": 1, "status": "self_serve", "docsUrl": "https://www.beehiiv.com/features/mcp/getting-started", "serverUrl": "https://mcp.beehiiv.com/mcp", "authMode": "dcr", "prerequisite": "A beehiiv account; the subscription plan controls available write capabilities.", "riskTier": "S3" },
{ "slug": "bitly", "name": "Bitly", "wave": 1, "status": "self_serve", "docsUrl": "https://dev.bitly.com/bitly-mcp/overview/quickstart/", "serverUrl": "https://api-ssl.bitly.com/v4/mcp", "authMode": "dcr_or_api_key", "prerequisite": "A Bitly account with either browser authorization or an API token.", "riskTier": "S2" },
{ "slug": "candid", "name": "Candid", "wave": 1, "status": "self_serve", "docsUrl": "https://learning.candid.org/getting-started-with-the-candid-mcp-connector/375441", "serverUrl": "https://mcp.candid.org/mcp", "authMode": "dcr", "prerequisite": "A Candid account with access to the MCP connector.", "riskTier": "S2" },
{ "slug": "cloudflare", "name": "Cloudflare", "wave": 1, "status": "self_serve", "docsUrl": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/", "serverUrl": "https://mcp.cloudflare.com/mcp", "authMode": "dcr_or_api_key", "prerequisite": "A Cloudflare account with access to the resources being connected.", "riskTier": "S3" },
{ "slug": "cloudinary", "name": "Cloudinary", "wave": 1, "status": "self_serve", "docsUrl": "https://cloudinary.com/documentation/cloudinary_llm_mcp", "serverUrl": "https://asset-management.mcp.cloudinary.com/mcp", "authMode": "dcr", "prerequisite": "A Cloudinary account; authorization is limited by the signed-in user's roles.", "riskTier": "S3" },
{ "slug": "coda", "name": "Coda", "wave": 1, "status": "self_serve", "docsUrl": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP", "serverUrl": "https://coda.io/apis/mcp", "authMode": "dcr_or_api_key", "prerequisite": "A Coda account; the hosted MCP service is currently beta.", "riskTier": "S3" },
{ "slug": "hugging-face", "name": "Hugging Face", "wave": 1, "status": "self_serve", "docsUrl": "https://huggingface.co/docs/hub/agents-mcp", "serverUrl": "https://huggingface.co/mcp?login&gradio=none", "authMode": "dcr_cimd", "prerequisite": "A Hugging Face account.", "riskTier": "S2" },
{ "slug": "kernel", "name": "Kernel", "wave": 1, "status": "self_serve", "docsUrl": "https://www.kernel.sh/docs/reference/mcp-server/", "serverUrl": "https://mcp.onkernel.com/mcp", "authMode": "dcr_or_api_key", "prerequisite": "A Kernel account with either browser authorization or an API key.", "riskTier": "S3" },
{ "slug": "local-falcon", "name": "Local Falcon", "wave": 1, "status": "self_serve", "docsUrl": "https://docs.localfalcon.com/", "serverUrl": "https://mcp.localfalcon.com", "authMode": "dcr", "prerequisite": "A Local Falcon account with MCP access.", "riskTier": "S2" },
{ "slug": "make", "name": "Make", "wave": 1, "status": "self_serve", "docsUrl": "https://developers.make.com/mcp-server", "serverUrl": "https://mcp.make.com", "authMode": "dcr", "prerequisite": "A Make account and access to the scenarios exposed to MCP.", "riskTier": "S3" },
{ "slug": "manufact", "name": "Manufact", "wave": 1, "status": "self_serve", "docsUrl": "https://docs.manufact.com/mcp", "serverUrl": "https://mcp.manufact.com/mcp", "authMode": "dcr", "prerequisite": "A Manufact account with MCP access.", "riskTier": "S3" },
{ "slug": "miro", "name": "Miro", "wave": 1, "status": "self_serve", "docsUrl": "https://help.miro.com/hc/en-us/articles/31625301583890-How-to-enable-Miro-s-MCP-Server-user-guide", "serverUrl": "https://mcp.miro.com/", "authMode": "dcr", "prerequisite": "A Miro account; enterprise administrators may restrict third-party MCP clients.", "riskTier": "S3" },
{ "slug": "netlify", "name": "Netlify", "wave": 1, "status": "self_serve", "docsUrl": "https://docs.netlify.com/build/build-with-ai/agent-setup-guides/agent-setup-overview/", "serverUrl": "https://netlify-mcp.netlify.app/mcp", "authMode": "dcr", "prerequisite": "A Netlify account with access to the relevant team and sites.", "riskTier": "S3" },
{ "slug": "notion", "name": "Notion", "wave": 1, "status": "self_serve", "docsUrl": "https://developers.notion.com/guides/mcp/build-mcp-client", "serverUrl": "https://mcp.notion.com/mcp", "authMode": "dcr", "prerequisite": "A Notion account and access to the pages or databases being shared.", "riskTier": "S3" },
{ "slug": "oreilly", "name": "O'Reilly", "wave": 1, "status": "self_serve", "docsUrl": "https://learning.oreilly.com/apidocs/mcp/content/", "serverUrl": "https://api.oreilly.com/api/content-discovery/v1/mcp/", "authMode": "dcr_or_api_key", "prerequisite": "An O'Reilly Learning subscription with MCP or API access.", "riskTier": "S2" },
{ "slug": "planetscale", "name": "PlanetScale", "wave": 1, "status": "self_serve", "docsUrl": "https://planetscale.com/docs/connect/mcp", "serverUrl": "https://mcp.pscale.dev/mcp/planetscale", "authMode": "dcr", "prerequisite": "A PlanetScale account; database and branch access are chosen during authorization.", "riskTier": "S4" },
{ "slug": "posthog", "name": "PostHog", "wave": 1, "status": "self_serve", "docsUrl": "https://posthog.com/docs/model-context-protocol", "serverUrl": "https://mcp.posthog.com/mcp", "authMode": "dcr_or_api_key", "prerequisite": "A PostHog account; personal API keys and optional organization or project pinning are supported as alternatives and advanced controls.", "riskTier": "S3" },
{ "slug": "resend", "name": "Resend", "wave": 1, "status": "self_serve", "docsUrl": "https://resend.com/changelog/remote-mcp-server", "serverUrl": "https://mcp.resend.com/mcp", "authMode": "dcr", "prerequisite": "A Resend account with access to the relevant domains.", "riskTier": "S3" },
{ "slug": "sentry", "name": "Sentry", "wave": 1, "status": "self_serve", "docsUrl": "https://mcp.sentry.dev/.well-known/oauth-authorization-server", "serverUrl": "https://mcp.sentry.dev/mcp", "authMode": "dcr_cimd", "prerequisite": "A Sentry account with access to the relevant organizations and projects.", "riskTier": "S3" },
{ "slug": "ticktick", "name": "TickTick", "wave": 1, "status": "self_serve", "docsUrl": "https://help.ticktick.com/articles/7438129581631995904", "serverUrl": "https://mcp.ticktick.com", "authMode": "dcr", "prerequisite": "A TickTick account with MCP access.", "riskTier": "S3" },
{ "slug": "todoist", "name": "Todoist", "wave": 1, "status": "self_serve", "docsUrl": "https://developer.todoist.com/", "serverUrl": "https://ai.todoist.net/mcp", "authMode": "dcr", "prerequisite": "A Todoist account.", "riskTier": "S3" },
{ "slug": "webflow", "name": "Webflow", "wave": 1, "status": "self_serve", "docsUrl": "https://developers.webflow.com/mcp/reference/getting-started", "serverUrl": "https://mcp.webflow.com/mcp", "authMode": "dcr", "prerequisite": "A Webflow account; workspace and site roles constrain accessible sites.", "riskTier": "S3" },
{ "slug": "wix", "name": "Wix", "wave": 1, "status": "self_serve", "docsUrl": "https://www.wix.com/studio/developers/mcp-server", "serverUrl": "https://mcp.wix.com/mcp", "authMode": "dcr", "prerequisite": "A Wix account with access to the relevant sites.", "riskTier": "S3" },
{ "slug": "brex", "name": "Brex", "wave": 2, "status": "self_serve", "docsUrl": "https://www.brex.com/support/using-brex-in-ai-apps", "serverUrl": "https://api.brex.com/mcp", "authMode": "dcr", "prerequisite": "Brex early access and an administrator enabling the integration; financial actions require explicit approval.", "riskTier": "S4" },
{ "slug": "clickhouse", "name": "ClickHouse", "wave": 2, "status": "self_serve", "docsUrl": "https://clickhouse.com/blog/announcing-managed-clickstack-mcp-server", "serverUrl": "https://mcp.clickhouse.cloud/clickstack", "authMode": "dcr", "prerequisite": "A ClickHouse Cloud ClickStack service and its service ID.", "riskTier": "S4" },
{ "slug": "egnyte", "name": "Egnyte", "wave": 2, "status": "self_serve", "docsUrl": "https://developers.egnyte.com/docs/Remote_MCP_Server", "serverUrl": "https://mcp-server.egnyte.com/mcp", "authMode": "dcr", "prerequisite": "An eligible Egnyte plan and administrator approval for external LLM access.", "riskTier": "S3" },
{ "slug": "embat", "name": "Embat", "wave": 2, "status": "self_serve", "docsUrl": "https://tellme.embat.io/.well-known/oauth-protected-resource/mcp", "serverUrl": "https://tellme.embat.io/mcp", "authMode": "dcr_cimd", "prerequisite": "An Embat account; pilot the connection because provider setup documentation is sparse.", "riskTier": "S4" },
{ "slug": "mixpanel", "name": "Mixpanel", "wave": 2, "status": "self_serve", "docsUrl": "https://mixpanel.com/blog/mixpanel-mcp-server/", "serverUrl": "https://mcp.mixpanel.com/mcp", "authMode": "dcr", "prerequisite": "A Mixpanel account; the hosted MCP server is currently beta.", "riskTier": "S3" },
{ "slug": "postman", "name": "Postman", "wave": 2, "status": "self_serve", "docsUrl": "https://learning.postman.com/latest-v-12/docs/reference/postman-api/postman-mcp-server/postman-mcp-remote-server", "serverUrl": "https://mcp.postman.com/minimal", "authMode": "dcr_or_api_key", "prerequisite": "A Postman account; OAuth is available for US endpoints and API keys are required for EU endpoints.", "riskTier": "S3" },
{ "slug": "razorpay", "name": "Razorpay", "wave": 2, "status": "self_serve", "docsUrl": "https://razorpay.com/docs/mcp-server/oauth/", "serverUrl": "https://mcp.razorpay.com/mcp", "authMode": "dcr_or_api_key", "prerequisite": "A Razorpay account; financial or destructive actions always require explicit approval.", "riskTier": "S4" },
{ "slug": "sanity", "name": "Sanity", "wave": 2, "status": "self_serve", "docsUrl": "https://www.sanity.io/docs/ai/mcp-server", "serverUrl": "https://mcp.sanity.io", "authMode": "dcr_or_api_key", "prerequisite": "A Sanity account with access to the relevant projects and datasets.", "riskTier": "S3" },
{ "slug": "stripe", "name": "Stripe", "wave": 2, "status": "self_serve", "docsUrl": "https://docs.stripe.com/mcp", "serverUrl": "https://mcp.stripe.com", "authMode": "dcr_or_api_key", "prerequisite": "A Stripe account; the server is public preview and payment actions require explicit approval.", "riskTier": "S4" },
{ "slug": "supabase", "name": "Supabase", "wave": 2, "status": "self_serve", "docsUrl": "https://supabase.com/docs/guides/ai-tools/mcp", "serverUrl": "https://mcp.supabase.com/mcp", "authMode": "dcr_or_api_key", "prerequisite": "A Supabase account; use a development project and review write actions before connecting production data.", "riskTier": "S4" },
{ "slug": "ticket-tailor", "name": "Ticket Tailor", "wave": 2, "status": "self_serve", "docsUrl": "https://developers.tickettailor.com/docs/mcp/authentication/", "serverUrl": "https://mcp.tickettailor.ai/mcp", "authMode": "dcr", "prerequisite": "A Ticket Tailor account; the provider may request an API key during its hosted authorization prompt.", "riskTier": "S3" },
{ "slug": "asana", "name": "Asana", "wave": 3, "status": "self_serve", "docsUrl": "https://developers.asana.com/docs/integrating-with-asanas-mcp-server", "serverUrl": "https://mcp.asana.com/v2/mcp", "authMode": "customer_oauth", "prerequisite": "Create an Asana MCP OAuth app and register Paperclip's callback URI; DCR is not supported.", "riskTier": "S3" },
{ "slug": "box", "name": "Box", "wave": 3, "status": "self_serve", "docsUrl": "https://support.box.com/hc/en-us/articles/43847256139923-Managing-Box-MCP-Servers", "serverUrl": "https://mcp.box.com", "authMode": "customer_oauth", "prerequisite": "A Box administrator creates the OAuth integration and enables AI access.", "riskTier": "S3" },
{ "slug": "mem0", "name": "Mem0", "wave": 3, "status": "self_serve", "docsUrl": "https://docs.mem0.ai/platform/mem0-mcp", "serverUrl": "https://mcp.mem0.ai/mcp/", "authMode": "api_key", "prerequisite": "A Mem0 API key; the live server currently requires the slash-normalized endpoint.", "riskTier": "S3" },
{ "slug": "pagerduty", "name": "PagerDuty", "wave": 3, "status": "self_serve", "docsUrl": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server", "serverUrl": "https://mcp.pagerduty.com/mcp", "authMode": "api_key", "prerequisite": "A PagerDuty API token; choose the regional endpoint that hosts the account.", "riskTier": "S4" },
{ "slug": "similarweb", "name": "Similarweb", "wave": 3, "status": "self_serve", "docsUrl": "https://developers.similarweb.com/docs/similarweb-mcp", "serverUrl": "https://mcp.similarweb.com", "authMode": "api_key", "prerequisite": "A Similarweb subscription with API access and an API key.", "riskTier": "S2" },
{ "slug": "xero", "name": "Xero", "wave": 3, "status": "self_serve", "docsUrl": "https://developer.xero.com/ai", "serverUrl": "https://mcp.xero.com/mcp", "authMode": "customer_oauth", "prerequisite": "Create a Xero OAuth app and confirm the applicable AI and data-use terms before connecting.", "riskTier": "S4" },
{ "slug": "zapier", "name": "Zapier", "wave": 3, "status": "self_serve", "docsUrl": "https://docs.zapier.com/mcp/quickstart", "serverUrl": "https://mcp.zapier.com/", "authMode": "generated_url", "prerequisite": "Create a Zapier MCP server, choose the actions it exposes, and paste its generated connection URL.", "riskTier": "S3" },
{ "slug": "g2", "name": "G2", "wave": "blocked", "status": "blocked", "docsUrl": "https://documentation.g2.com/docs/g2-mcp-server", "serverUrl": "https://mcp.g2.com/mcp", "authMode": "provider_approval", "prerequisite": "G2 must enable cross-application token introspection before an independently registered client can work.", "riskTier": "S3" },
{ "slug": "vercel", "name": "Vercel", "wave": "blocked", "status": "blocked", "docsUrl": "https://vercel.com/docs/agent-resources/vercel-mcp", "serverUrl": "https://mcp.vercel.com", "authMode": "provider_approval", "prerequisite": "Vercel currently reviews and approves MCP clients.", "riskTier": "S3" },
{ "slug": "zomato", "name": "Zomato", "wave": "blocked", "status": "blocked", "docsUrl": "https://github.com/Zomato/mcp-server-manifest", "serverUrl": "https://mcp-server.zomato.com/mcp", "authMode": "provider_approval", "prerequisite": "Zomato currently limits third-party clients and requires redirect-URI allowlisting.", "riskTier": "S3" }
]
}

View File

@ -0,0 +1,10 @@
import manifest from "./self-serve-mcp-research.json" with { type: "json" };
import type { SelfServeMcpResearchManifest } from "./types/app-definition.js";
export const SELF_SERVE_MCP_RESEARCH = manifest as SelfServeMcpResearchManifest;
export const SELF_SERVE_MCP_CANDIDATES = SELF_SERVE_MCP_RESEARCH.entries.filter(
(entry) => entry.status === "self_serve",
);
export const BLOCKED_MCP_PROVIDERS = SELF_SERVE_MCP_RESEARCH.entries.filter(
(entry) => entry.status === "blocked",
);

View File

@ -1,6 +1,33 @@
import type { ConnectionGrantKind, ToolConnectionOwnership, ToolConnectionTransport } from "./tool-access.js";
import type { ConnectionGrantKind, ToolConnectionOwnership, ToolConnectionTransport, VercelConnectPrincipalMode } from "./tool-access.js";
export type AppCategory = "ai"|"analytics"|"commerce"|"communication"|"content"|"data"|"developer"|"productivity"|"other";
export type OAuthRedirectConstraints = "https-or-loopback-http";
export interface FieldDef { key:string; label:string; type:"text"|"password"|"textarea"|"datetime"|"select"|"checkbox"; required?:boolean; advanced?:boolean; placeholder?:string; helperMd?:string; secret?:boolean; prefix?:string; defaultValue?:string|boolean; validation?:{pattern?:string;maxLength?:number}; options?:Array<{value:string;label:string}>; transport?:{location:"query"|"header";name:string;format?:"string"|"csv"|"boolean";omitFalse?:boolean} }
export interface ConnectionMethodDef { key:string; label?:string; transport:ToolConnectionTransport; auth:"oauth"|"api_key"|"none"; oauthStrategy?:"paperclip_id_connector"; grantKinds?:ConnectionGrantKind[]; ownershipModes:ToolConnectionOwnership[]; whenToUse:string; defaults?:{serverUrl?:string;discoveryUrl?:string|null;serviceHost?:string;templateKey?:string;authorizationEndpoint?:string;tokenEndpoint?:string;metadataUrl?:string;scopesHint?:string[]}; tenantFields?:FieldDef[]; extensionFields?:FieldDef[]; configRequirements?:{atLeastOneOf?:string[]}; credentialFields?:FieldDef[]; keyPlacement?:{location:"header"|"query"|"body_json"|"env";name:string;prefix?:string|null}; guidanceMd:string; consoleLinks?:{register?:string;keys?:string;settings?:string;docs?:string}; warnings?:string[]; variants?:Array<{key:string;label:string;whenToUse:string;tenantFields?:FieldDef[]}>; riskTier:"S1"|"S2"|"S3"|"S4"; requiredResourceFilters?:string[] }
export interface AppDefinition { schemaVersion:1; slug:string; name:string; description:string; categories:AppCategory[]; featured?:boolean; branding:{logoUrl:string;darkLogoUrl?:string;backgroundColor?:string;accentColor?:string}; urlPatterns:string[]; docsUrl?:string; redirectConstraints?:OAuthRedirectConstraints; methods:ConnectionMethodDef[]; suggestable?:boolean; availability?:{available:boolean;reason?:string;robotEmail?:string}; ownershipAvailability?:Partial<Record<ToolConnectionOwnership,boolean>> }
export interface FieldDef { key:string; label:string; type:"text"|"password"|"textarea"|"datetime"|"select"|"checkbox"; required?:boolean; advanced?:boolean; hidden?:boolean; placeholder?:string; helperMd?:string; secret?:boolean; prefix?:string; defaultValue?:string|boolean; validation?:{pattern?:string;maxLength?:number}; options?:Array<{value:string;label:string}>; transport?:{location:"query"|"header";name:string;format?:"string"|"csv"|"boolean";omitFalse?:boolean} }
export interface ConnectionMethodDef { key:string; label?:string; transport:ToolConnectionTransport; auth:"oauth"|"api_key"|"none"; oauthStrategy?:"paperclip_id_connector"; connectorProfile?:string; capabilityProfile?:{key:string;label:string;description?:string}; grantKinds?:ConnectionGrantKind[]; ownershipModes:ToolConnectionOwnership[]; whenToUse:string; defaults?:{serverUrl?:string;serverUrlTemplate?:string;discoveryUrl?:string|null;serviceHost?:string;templateKey?:string;authorizationEndpoint?:string;tokenEndpoint?:string;metadataUrl?:string;scopesHint?:string[];oauthAuthorizationParams?:{access_type?:"offline";prompt?:"consent"}}; tenantFields?:FieldDef[]; extensionFields?:FieldDef[]; configRequirements?:{atLeastOneOf?:string[]}; credentialFields?:FieldDef[]; keyPlacement?:{location:"header"|"query"|"body_json"|"env";name:string;prefix?:string|null}; credentialSources?:{vercelConnect?:{services:string[];principalModes:VercelConnectPrincipalMode[];scopes:string[];header:{name:string;prefix?:string|null}}}; guidanceMd:string; consoleLinks?:{register?:string;keys?:string;settings?:string;docs?:string}; warnings?:string[]; variants?:Array<{key:string;label:string;whenToUse:string;tenantFields?:FieldDef[]}>; riskTier:"S1"|"S2"|"S3"|"S4"; requiredResourceFilters?:string[] }
export interface AppDefinition { schemaVersion:1; slug:string; name:string; description:string; categories:AppCategory[]; featured?:boolean; branding:{logoUrl:string;darkLogoUrl?:string;backgroundColor?:string;accentColor?:string}; urlPatterns:string[]; docsUrl?:string; setupPrerequisite?:{title:string;description:string;steps?:string[];actionLabel:string;actionUrl:string}; redirectConstraints?:OAuthRedirectConstraints; methods:ConnectionMethodDef[]; suggestable?:boolean; availability?:{available:boolean;reason?:string;robotEmail?:string}; ownershipAvailability?:Partial<Record<ToolConnectionOwnership,boolean>> }
export type SelfServeMcpAuthMode =
| "dcr"
| "dcr_cimd"
| "dcr_or_api_key"
| "customer_oauth"
| "api_key"
| "generated_url"
| "provider_approval";
export interface SelfServeMcpResearchEntry {
slug: string;
name: string;
wave: 1 | 2 | 3 | "blocked";
status: "self_serve" | "blocked";
docsUrl: string;
serverUrl: string;
authMode: SelfServeMcpAuthMode;
prerequisite: string;
riskTier: "S1" | "S2" | "S3" | "S4";
}
export interface SelfServeMcpResearchManifest {
schemaVersion: 1;
verifiedAt: string;
entries: SelfServeMcpResearchEntry[];
}

View File

@ -70,6 +70,7 @@ export type ToolActorType = "agent" | "user" | "system" | "plugin";
export type ToolConnectionTransport = "mcp_remote" | "rest_api" | "local_stdio";
export type ToolConnectionAuthKind = "oauth" | "api_key" | "none";
export type ToolConnectionOwnership = "platform_shared" | "platform_provisioned" | "customer" | "dcr";
export type VercelConnectPrincipalMode = "app" | "user";
export type ToolConnectionStatus = "draft" | "active" | "disabled" | "archived";
export type ToolConnectionInstallTargetType = "company" | "agent";
export type ConnectionGrantKind = "organization" | "user";

View File

@ -1,6 +1,10 @@
import { z } from "zod";
import { connectionGrantKindSchema, toolConnectionOwnershipSchema, toolConnectionTransportSchema } from "./tool-access.js";
const field=z.object({key:z.string().min(1),label:z.string().min(1),type:z.enum(["text","password","textarea","datetime","select","checkbox"]),required:z.boolean().optional(),advanced:z.boolean().optional(),placeholder:z.string().optional(),helperMd:z.string().optional(),secret:z.boolean().optional(),prefix:z.string().optional(),defaultValue:z.union([z.string(),z.boolean()]).optional(),validation:z.object({pattern:z.string().optional(),maxLength:z.number().int().positive().optional()}).optional(),options:z.array(z.object({value:z.string(),label:z.string()})).optional(),transport:z.object({location:z.enum(["query","header"]),name:z.string().min(1),format:z.enum(["string","csv","boolean"]).optional(),omitFalse:z.boolean().optional()}).optional()}).superRefine((v,c)=>{if(v.required&&v.type!=="checkbox"&&!v.placeholder)c.addIssue({code:"custom",message:"Required fields need placeholders",path:["placeholder"]});if(v.type==="select"&&(!v.options||v.options.length===0))c.addIssue({code:"custom",message:"Select fields need options",path:["options"]})});
export const connectionMethodDefSchema=z.object({key:z.string().min(1),label:z.string().min(1).optional(),transport:toolConnectionTransportSchema,auth:z.enum(["oauth","api_key","none"]),oauthStrategy:z.enum(["paperclip_id_connector"]).optional(),grantKinds:z.array(connectionGrantKindSchema).min(1).optional(),ownershipModes:z.array(toolConnectionOwnershipSchema).min(1),whenToUse:z.string().min(1),defaults:z.object({serverUrl:z.string().url().optional(),discoveryUrl:z.string().url().nullable().optional(),serviceHost:z.string().optional(),templateKey:z.string().optional(),authorizationEndpoint:z.string().url().optional(),tokenEndpoint:z.string().url().optional(),metadataUrl:z.string().url().optional(),scopesHint:z.array(z.string()).optional()}).optional(),tenantFields:z.array(field).optional(),extensionFields:z.array(field).optional(),configRequirements:z.object({atLeastOneOf:z.array(z.string().min(1)).min(1).optional()}).optional(),credentialFields:z.array(field).optional(),keyPlacement:z.object({location:z.enum(["header","query","body_json","env"]),name:z.string().min(1),prefix:z.string().nullable().optional()}).optional(),guidanceMd:z.string().min(1),consoleLinks:z.object({register:z.string().url().optional(),keys:z.string().url().optional(),settings:z.string().url().optional(),docs:z.string().url().optional()}).optional(),warnings:z.array(z.string()).optional(),variants:z.array(z.object({key:z.string(),label:z.string(),whenToUse:z.string(),tenantFields:z.array(field).optional()})).optional(),riskTier:z.enum(["S1","S2","S3","S4"]),requiredResourceFilters:z.array(z.string()).optional()}).superRefine((v,c)=>{if(v.auth==="api_key"&&!v.keyPlacement)c.addIssue({code:"custom",message:"API-key methods require keyPlacement",path:["keyPlacement"]});if(v.oauthStrategy&&v.auth!=="oauth")c.addIssue({code:"custom",message:"OAuth strategies require OAuth auth",path:["oauthStrategy"]});const keys=new Set([...(v.tenantFields??[]),...(v.extensionFields??[])].map((entry)=>entry.key));for(const key of v.configRequirements?.atLeastOneOf??[])if(!keys.has(key))c.addIssue({code:"custom",message:"Config requirement references an unknown field",path:["configRequirements","atLeastOneOf"]})});
export const appDefinitionSchema=z.object({schemaVersion:z.literal(1),slug:z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),name:z.string().min(1),description:z.string().min(1),categories:z.array(z.enum(["ai","analytics","commerce","communication","content","data","developer","productivity","other"])).min(1),featured:z.boolean().optional(),branding:z.object({logoUrl:z.string().url(),darkLogoUrl:z.string().url().optional(),backgroundColor:z.string().optional(),accentColor:z.string().optional()}),urlPatterns:z.array(z.string()),docsUrl:z.string().url().optional(),redirectConstraints:z.enum(["https-or-loopback-http"]).optional(),methods:z.array(connectionMethodDefSchema).min(1),suggestable:z.boolean().optional(),availability:z.object({available:z.boolean(),reason:z.string().optional(),robotEmail:z.string().optional()}).optional(),ownershipAvailability:z.object({platform_shared:z.boolean().optional(),platform_provisioned:z.boolean().optional(),customer:z.boolean().optional(),dcr:z.boolean().optional()}).optional()});
const appBrandAssetUrlSchema=z.string().refine((value)=>{
if(/^\/brands\/apps\/[a-z0-9][a-z0-9._-]*\.(?:svg|png)$/i.test(value))return true;
try{return new URL(value).protocol==="https:";}catch{return false;}
},{message:"Brand assets must be HTTPS URLs or local /brands/apps SVG/PNG paths"});
const field=z.object({key:z.string().min(1),label:z.string().min(1),type:z.enum(["text","password","textarea","datetime","select","checkbox"]),required:z.boolean().optional(),advanced:z.boolean().optional(),hidden:z.boolean().optional(),placeholder:z.string().optional(),helperMd:z.string().optional(),secret:z.boolean().optional(),prefix:z.string().optional(),defaultValue:z.union([z.string(),z.boolean()]).optional(),validation:z.object({pattern:z.string().optional(),maxLength:z.number().int().positive().optional()}).optional(),options:z.array(z.object({value:z.string(),label:z.string()})).optional(),transport:z.object({location:z.enum(["query","header"]),name:z.string().min(1),format:z.enum(["string","csv","boolean"]).optional(),omitFalse:z.boolean().optional()}).optional()}).superRefine((v,c)=>{if(v.required&&v.type!=="checkbox"&&!v.placeholder)c.addIssue({code:"custom",message:"Required fields need placeholders",path:["placeholder"]});if(v.type==="select"&&(!v.options||v.options.length===0))c.addIssue({code:"custom",message:"Select fields need options",path:["options"]});if(v.hidden&&v.defaultValue===undefined)c.addIssue({code:"custom",message:"Hidden fields need defaults",path:["defaultValue"]})});
export const connectionMethodDefSchema=z.object({key:z.string().min(1),label:z.string().min(1).optional(),transport:toolConnectionTransportSchema,auth:z.enum(["oauth","api_key","none"]),oauthStrategy:z.enum(["paperclip_id_connector"]).optional(),connectorProfile:z.string().regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/).optional(),capabilityProfile:z.object({key:z.string().min(1),label:z.string().min(1),description:z.string().min(1).optional()}).optional(),grantKinds:z.array(connectionGrantKindSchema).min(1).optional(),ownershipModes:z.array(toolConnectionOwnershipSchema).min(1),whenToUse:z.string().min(1),defaults:z.object({serverUrl:z.string().url().optional(),serverUrlTemplate:z.string().regex(/^https:\/\//).optional(),discoveryUrl:z.string().url().nullable().optional(),serviceHost:z.string().optional(),templateKey:z.string().optional(),authorizationEndpoint:z.string().url().optional(),tokenEndpoint:z.string().url().optional(),metadataUrl:z.string().url().optional(),scopesHint:z.array(z.string()).optional(),oauthAuthorizationParams:z.object({access_type:z.literal("offline").optional(),prompt:z.literal("consent").optional()}).optional()}).optional(),tenantFields:z.array(field).optional(),extensionFields:z.array(field).optional(),configRequirements:z.object({atLeastOneOf:z.array(z.string().min(1)).min(1).optional()}).optional(),credentialFields:z.array(field).optional(),keyPlacement:z.object({location:z.enum(["header","query","body_json","env"]),name:z.string().min(1),prefix:z.string().nullable().optional()}).optional(),credentialSources:z.object({vercelConnect:z.object({services:z.array(z.string().min(1)).min(1),principalModes:z.array(z.enum(["app","user"])).min(1),scopes:z.array(z.string().min(1)).min(1),header:z.object({name:z.string().min(1),prefix:z.string().nullable().optional()})}).optional()}).optional(),guidanceMd:z.string().min(1),consoleLinks:z.object({register:z.string().url().optional(),keys:z.string().url().optional(),settings:z.string().url().optional(),docs:z.string().url().optional()}).optional(),warnings:z.array(z.string()).optional(),variants:z.array(z.object({key:z.string(),label:z.string(),whenToUse:z.string(),tenantFields:z.array(field).optional()})).optional(),riskTier:z.enum(["S1","S2","S3","S4"]),requiredResourceFilters:z.array(z.string()).optional()}).superRefine((v,c)=>{if(v.auth==="api_key"&&!v.keyPlacement)c.addIssue({code:"custom",message:"API-key methods require keyPlacement",path:["keyPlacement"]});if(v.oauthStrategy&&v.auth!=="oauth")c.addIssue({code:"custom",message:"OAuth strategies require OAuth auth",path:["oauthStrategy"]});if(v.oauthStrategy==="paperclip_id_connector"&&!v.connectorProfile)c.addIssue({code:"custom",message:"Paperclip ID connector methods require connectorProfile",path:["connectorProfile"]});if(v.connectorProfile&&v.oauthStrategy!=="paperclip_id_connector")c.addIssue({code:"custom",message:"connectorProfile requires the Paperclip ID OAuth strategy",path:["connectorProfile"]});if(v.credentialSources?.vercelConnect&&(v.transport!=="mcp_remote"||v.auth==="none"))c.addIssue({code:"custom",message:"Vercel Connect requires an authenticated remote MCP method",path:["credentialSources","vercelConnect"]});const keys=new Set([...(v.tenantFields??[]),...(v.extensionFields??[])].map((entry)=>entry.key));for(const key of v.configRequirements?.atLeastOneOf??[])if(!keys.has(key))c.addIssue({code:"custom",message:"Config requirement references an unknown field",path:["configRequirements","atLeastOneOf"]});if(v.defaults?.serverUrl&&v.defaults.serverUrlTemplate)c.addIssue({code:"custom",message:"Use either serverUrl or serverUrlTemplate",path:["defaults"]});for(const placeholder of v.defaults?.serverUrlTemplate?.matchAll(/\{([a-zA-Z0-9_-]+)\}/g)??[])if(!keys.has(placeholder[1]))c.addIssue({code:"custom",message:"Server URL template references an unknown field",path:["defaults","serverUrlTemplate"]})});
export const appDefinitionSchema=z.object({schemaVersion:z.literal(1),slug:z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),name:z.string().min(1),description:z.string().min(1),categories:z.array(z.enum(["ai","analytics","commerce","communication","content","data","developer","productivity","other"])).min(1),featured:z.boolean().optional(),branding:z.object({logoUrl:appBrandAssetUrlSchema,darkLogoUrl:appBrandAssetUrlSchema.optional(),backgroundColor:z.string().optional(),accentColor:z.string().optional()}),urlPatterns:z.array(z.string()),docsUrl:z.string().url().optional(),setupPrerequisite:z.object({title:z.string().min(1),description:z.string().min(1),steps:z.array(z.string().min(1)).min(1).optional(),actionLabel:z.string().min(1),actionUrl:z.string().url()} ).optional(),redirectConstraints:z.enum(["https-or-loopback-http"]).optional(),methods:z.array(connectionMethodDefSchema).min(1),suggestable:z.boolean().optional(),availability:z.object({available:z.boolean(),reason:z.string().optional(),robotEmail:z.string().optional()}).optional(),ownershipAvailability:z.object({platform_shared:z.boolean().optional(),platform_provisioned:z.boolean().optional(),customer:z.boolean().optional(),dcr:z.boolean().optional()}).optional()});
export const appDefinitionsSchema=z.array(appDefinitionSchema).superRefine((v,c)=>{const s=new Set<string>();v.forEach((a,i)=>{if(s.has(a.slug))c.addIssue({code:"custom",message:"Duplicate slug",path:[i,"slug"]});s.add(a.slug)})});

View File

@ -1,33 +1,149 @@
import fs from "node:fs"; import path from "node:path";
const root=process.cwd(); const corpus=process.env.PAPERCLIP_CONTENT_TEMPLATES??path.resolve(root,"../../../paperclip-content/research/connections/vercel/templates");
const out=path.join(root,"packages/shared/src/app-definitions"); const favicon=d=>`https://www.google.com/s2/favicons?domain=${d}&sz=128`;
const root=process.cwd(); const corpus=process.env.PAPERCLIP_CONTENT_TEMPLATES??path.resolve(root,"../../paperclip-content/research/connections/vercel/templates");
const out=path.join(root,"packages/shared/src/app-definitions");
const brandingManifest=JSON.parse(fs.readFileSync(path.join(root,"ui/public/brands/apps/manifest.json"),"utf8"));
const brandingBySlug=new Map(brandingManifest.providers.map((entry)=>[entry.slug,entry]));
const brandingFor=(slug)=>{
const entry=brandingBySlug.get(slug);
if(entry) return {logoUrl:entry.localAsset,...(entry.darkAsset?{darkLogoUrl:entry.darkAsset}:{})};
if(slug==="oauth-generic"||slug==="api-key-generic") return {logoUrl:`/brands/apps/${slug}.svg`};
throw new Error(`${slug}: missing local branding provenance`);
};
const field=(key,label,placeholder)=>({key,label,type:"password",required:true,placeholder,secret:true});
const method=(key,transport,auth,defaults,riskTier,guidanceMd,extra={})=>({key,transport,auth,ownershipModes:auth==="oauth"?["customer","dcr"]:["customer"],whenToUse:transport==="mcp_remote"?"Use the provider-hosted connection for the quickest setup.":"Use credentials from your provider account.",defaults,guidanceMd,riskTier,...extra});
const vercelConnect=(serviceOrServices,principalMode,scopes,header={name:"Authorization",prefix:"Bearer "})=>({credentialSources:{vercelConnect:{services:Array.isArray(serviceOrServices)?serviceOrServices:[serviceOrServices],principalModes:[principalMode],scopes,header}}});
const posthogConfigFields=()=>[
{key:"projectId",label:"Project ID",type:"text",required:true,placeholder:"12345",helperMd:"Find the numeric project ID in PostHog project settings.",validation:{pattern:"^[0-9]+$",maxLength:32},transport:{location:"header",name:"x-posthog-project-id"}},
{key:"readOnly",label:"Read-only mode",type:"checkbox",defaultValue:false,helperMd:"Turn on to hide tools that can change PostHog data.",transport:{location:"query",name:"readonly",format:"boolean",omitFalse:true}},
{key:"projectId",label:"Pin to project ID",type:"text",advanced:true,placeholder:"Optional numeric project ID",helperMd:"Optional. Pin this connection to one project and remove PostHog's project-switching tool.",validation:{pattern:"^[0-9]+$",maxLength:32},transport:{location:"header",name:"x-posthog-project-id"}},
{key:"readOnly",label:"Read-only mode",type:"checkbox",advanced:true,defaultValue:false,helperMd:"Turn on to hide tools that can change PostHog data.",transport:{location:"query",name:"readonly",format:"boolean",omitFalse:true}},
{key:"features",label:"Feature groups",type:"textarea",advanced:true,placeholder:"Optional comma-separated feature groups",helperMd:"Leave blank to expose every feature group, or enter a comma-separated list to narrow access.",validation:{maxLength:500},transport:{location:"query",name:"features",format:"csv"}},
{key:"tools",label:"Individual tools",type:"textarea",advanced:true,placeholder:"Optional comma-separated tool names",helperMd:"Leave blank to expose all tools. Exact names here are combined with any feature groups.",validation:{maxLength:2000},transport:{location:"query",name:"tools",format:"csv"}},
{key:"mode",label:"Tool response mode",type:"select",advanced:true,required:true,placeholder:"Individual tools",defaultValue:"tools",options:[{value:"tools",label:"Individual tools"}],helperMd:"Paperclip uses individual tools so every action can be governed. CLI mode remains unavailable until nested execution is governed.",transport:{location:"query",name:"mode"}},
{key:"mode",label:"Tool response mode",type:"select",hidden:true,required:true,placeholder:"Individual tools",defaultValue:"tools",options:[{value:"tools",label:"Individual tools"}],helperMd:"Paperclip uses individual tools so every action can be governed. CLI mode remains unavailable until nested execution is governed.",transport:{location:"query",name:"mode"}},
];
const posthogMethod=(key,auth,extra={})=>method(key,"mcp_remote",auth,{serverUrl:"https://mcp.posthog.com/mcp"},"S3","Pin the connection to one PostHog project and expose the full tool catalog by default. Narrow feature groups or tools only when needed.",{tenantFields:posthogConfigFields(),requiredResourceFilters:["project"],...extra});
const posthogMethod=(key,auth,extra={})=>method(key,"mcp_remote",auth,{serverUrl:"https://mcp.posthog.com/mcp"},"S3","Connect with PostHog's recommended defaults. Project pinning, read-only access, and catalog filters are optional advanced controls.",{tenantFields:posthogConfigFields(),...extra});
const apps=[
["zapier","Zapier","Reach thousands of apps through your Zapier account.","productivity","zapier.com",["https://mcp.zapier.com/*"],method("mcp-key","mcp_remote","api_key",{serverUrl:"https://mcp.zapier.com/api/mcp"},"S3","Create a Zapier MCP connection, then paste its token here.",{credentialFields:[field("authorization","Zapier MCP token","Paste your Zapier token")],keyPlacement:{location:"header",name:"Authorization",prefix:"Bearer "}})],
["zapier","Zapier","Reach thousands of apps through your Zapier account.","productivity","zapier.com",["https://mcp.zapier.com/*"],method("generated-url","mcp_remote","none",{},"S3","Create a Zapier MCP server, then paste the complete generated connection URL. The token remains embedded in that URL.",{label:"Paste generated MCP URL",whenToUse:"Use the complete provider-generated MCP URL from Zapier."})],
["github","GitHub","Read code and pull requests, and coordinate repository work.","developer","github.com",["https://api.githubcopilot.com/mcp/*"],method("mcp-key","mcp_remote","api_key",{serverUrl:"https://api.githubcopilot.com/mcp/"},"S3","Create a fine-grained token limited to the repositories agents should use.",{credentialFields:[field("authorization","GitHub token","github_pat_...")],keyPlacement:{location:"header",name:"Authorization",prefix:"Bearer "},requiredResourceFilters:["organization","repository"]})],
["slack","Slack","Search channels and coordinate team communication.","communication","slack.com",["https://mcp.slack.com/*"],method("mcp-oauth","mcp_remote","oauth",{serverUrl:"https://mcp.slack.com/mcp",authorizationEndpoint:"https://slack.com/oauth/v2/authorize",tokenEndpoint:"https://slack.com/api/oauth.v2.access",scopesHint:["channels:read","chat:write","search:read"]},"S3","Connect a Slack workspace and limit access to the channels agents need.",{requiredResourceFilters:["workspace","channel"]})],
["notion","Notion","Read and update pages in your Notion workspace.","content","notion.so",["https://mcp.notion.com/*"],method("mcp-oauth","mcp_remote","oauth",{serverUrl:"https://mcp.notion.com/mcp"},"S3","Connect Notion for workspace content. Share only the pages and databases agents should use.",{requiredResourceFilters:["workspace","page","database"]}),{redirectConstraints:"https-or-loopback-http"}],
["posthog","PostHog","Analyze product usage, errors, feature flags, and experiments in a pinned PostHog project.","analytics","posthog.com",["https://mcp.posthog.com/*"],[posthogMethod("mcp-oauth","oauth",{label:"Sign in with PostHog",ownershipModes:["customer","dcr"],whenToUse:"Sign in with PostHog in the browser. Recommended for hosted PostHog accounts.",consoleLinks:{docs:"https://posthog.com/docs/model-context-protocol"}}),posthogMethod("mcp-api-key","api_key",{label:"Use a personal API key",whenToUse:"Use a PostHog personal API key when browser sign-in is not suitable.",credentialFields:[field("authorization","PostHog personal API key","phx_...")],keyPlacement:{location:"header",name:"Authorization",prefix:"Bearer "},consoleLinks:{keys:"https://posthog.com/docs/model-context-protocol/faq",docs:"https://posthog.com/docs/model-context-protocol/faq"}})],{featured:true}],
["linear","Linear","Create, update, and read Linear issues.","productivity","linear.app",["https://mcp.linear.app/*"],method("mcp-oauth","mcp_remote","oauth",{serverUrl:"https://mcp.linear.app/mcp",authorizationEndpoint:"https://linear.app/oauth/authorize",tokenEndpoint:"https://api.linear.app/oauth/token",scopesHint:["read","write"]},"S2","Register a Linear OAuth app and add Paperclip's redirect URI before connecting.",{requiredResourceFilters:["workspace","team","project"]})],
["slack","Slack","Search channels and coordinate team communication.","communication","slack.com",["https://mcp.slack.com/*"],method("mcp-oauth","mcp_remote","oauth",{serverUrl:"https://mcp.slack.com/mcp",authorizationEndpoint:"https://slack.com/oauth/v2/authorize",tokenEndpoint:"https://slack.com/api/oauth.v2.access",scopesHint:["channels:read","chat:write","search:read"]},"S3","Connect a Slack workspace and limit access to the channels agents need.",{ownershipModes:["customer"],requiredResourceFilters:["workspace","channel"]})],
["notion","Notion","Read and update pages in your Notion workspace.","content","notion.so",["https://mcp.notion.com/*"],method("mcp-oauth","mcp_remote","oauth",{serverUrl:"https://mcp.notion.com/mcp"},"S3","Connect Notion for workspace content. Share only the pages and databases agents should use.",{requiredResourceFilters:["workspace","page","database"],...vercelConnect("notion","user",["*"])}),{redirectConstraints:"https-or-loopback-http"}],
["posthog","PostHog","Analyze product usage, errors, feature flags, and experiments with PostHog's hosted MCP server.","analytics","posthog.com",["https://mcp.posthog.com/*"],[posthogMethod("mcp-oauth","oauth",{label:"Sign in with PostHog",ownershipModes:["customer","dcr"],whenToUse:"Sign in with PostHog in the browser. Recommended for hosted PostHog accounts.",consoleLinks:{docs:"https://posthog.com/docs/model-context-protocol"},...vercelConnect(["posthog","mcp.posthog.com/mcp"],"user",["*"])}),posthogMethod("mcp-api-key","api_key",{label:"Use a personal API key",whenToUse:"Use a PostHog personal API key when browser sign-in is not suitable.",credentialFields:[field("authorization","PostHog personal API key","phx_...")],keyPlacement:{location:"header",name:"Authorization",prefix:"Bearer "},consoleLinks:{keys:"https://posthog.com/docs/model-context-protocol/faq",docs:"https://posthog.com/docs/model-context-protocol/faq"},...vercelConnect(["posthog","mcp.posthog.com/mcp"],"app",["*"])})],{featured:true}],
["linear","Linear","Create, update, and read Linear issues.","productivity","linear.app",["https://mcp.linear.app/*"],method("mcp-oauth","mcp_remote","oauth",{serverUrl:"https://mcp.linear.app/mcp",authorizationEndpoint:"https://linear.app/oauth/authorize",tokenEndpoint:"https://api.linear.app/oauth/token",scopesHint:["read","write"]},"S2","Register a Linear OAuth app and add Paperclip's redirect URI before connecting.",{ownershipModes:["customer"],requiredResourceFilters:["workspace","team","project"],...vercelConnect("linear","user",["read","write"])})],
["google-sheets","Google Sheets","Read and update selected spreadsheets.","data","sheets.google.com",["https://docs.google.com/spreadsheets/*","https://sheets.google.com/*"],method("local","local_stdio","none",{templateKey:"paperclip.google-sheets"},"S3","Share each spreadsheet with the Paperclip robot email, then paste the sheet links.",{requiredResourceFilters:["spreadsheet"]})],
["context7","Context7","Look up current documentation for software libraries.","developer","context7.com",["https://mcp.context7.com/*"],method("mcp","mcp_remote","none",{serverUrl:"https://mcp.context7.com/mcp"},"S1","Connect Context7 to give agents current library documentation.")],
["shopify","Shopify","Search a store's products and policies, and manage shopping carts.","commerce","shopify.com",["https://*.myshopify.com/api/mcp"],method("storefront-mcp","mcp_remote","none",{serverUrlTemplate:"https://{storeDomain}/api/mcp"},"S3","Connect Shopify's official Storefront MCP server for shopper-facing catalog, policy, and cart tools.",{label:"Connect a Shopify storefront",whenToUse:"Use a store's public myshopify.com domain. No Shopify app or OAuth registration is required.",tenantFields:[{key:"storeDomain",label:"Store domain",type:"text",required:true,placeholder:"your-store.myshopify.com",helperMd:"Enter the full myshopify.com domain without https://.",validation:{pattern:"^[A-Za-z0-9][A-Za-z0-9-]*\\.myshopify\\.com$",maxLength:255}}],consoleLinks:{docs:"https://shopify.dev/docs/apps/build/storefront-mcp/servers/storefront"},warnings:["This is Shopify's Storefront MCP, not Admin API access. It does not manage merchant products, orders, or customers.","The storefront must be publicly reachable. Password-protected or restricted trial stores can return HTTP 401."],requiredResourceFilters:["store"]})],
["composio","Composio","Connect Composio so Paperclip can discover and manage the toolkits in your project.","productivity","composio.dev",["https://backend.composio.dev/*"],method("api-key","rest_api","api_key",{serviceHost:"backend.composio.dev"},"S3","Create a scoped project API key in Composio. It needs read access to toolkits and auth configs; later service-connection phases also need connected-account and session access.",{whenToUse:"Use a project API key from the Composio project that owns the toolkits and connected accounts.",credentialFields:[field("apiKey","Composio project API key","Paste the Composio API key")],keyPlacement:{location:"header",name:"x-api-key"},consoleLinks:{keys:"https://app.composio.dev/",settings:"https://app.composio.dev/",docs:"https://docs.composio.dev/reference/authenticating-to-composio/project-api-key-permissions"}}),{featured:true}],
["oauth-generic","OAuth app","Connect a provider using your own OAuth client.","other","oauth.net",[],method("oauth","rest_api","oauth",{},"S3","Register an OAuth client with the provider and add Paperclip's redirect URI.",{credentialFields:[{...field("clientId","Client ID","Paste the client ID"),type:"text",secret:false},field("clientSecret","Client secret","Paste the client secret")]})],
["api-key-generic","API key app","Connect an API using a key from your provider.","other","openapis.org",[],method("api-key","rest_api","api_key",{},"S3","Create a restricted API key and paste it here.",{credentialFields:[field("apiKey","API key","Paste the API key")],keyPlacement:{location:"header",name:"Authorization",prefix:"Bearer "}})],
["sentry","Sentry","Investigate errors, releases, and production issues.","developer","sentry.io",["https://mcp.sentry.dev/*"],method("mcp-oauth","mcp_remote","oauth",{serverUrl:"https://mcp.sentry.dev/mcp",discoveryUrl:"https://sentry.io/.well-known/oauth-authorization-server"},"S2","Connect the Sentry organization and projects agents need for incident work.",{requiredResourceFilters:["organization","project","environment"]})],
["vercel","Vercel","Inspect projects, deployments, and runtime logs.","developer","vercel.com",["https://mcp.vercel.com/*"],method("mcp-oauth","mcp_remote","oauth",{serverUrl:"https://mcp.vercel.com/mcp"},"S3","Connect the Vercel team and projects agents should operate.",{requiredResourceFilters:["team","project","environment"]})],
["anthropic","Anthropic","Use Anthropic APIs with a restricted key.","ai","anthropic.com",["https://api.anthropic.com/*"],method("api-key","rest_api","api_key",{serviceHost:"api.anthropic.com"},"S3","Create a key in the Anthropic Console and rotate it if it has been exposed.",{credentialFields:[field("apiKey","API key","sk-ant-api03-...")],keyPlacement:{location:"header",name:"x-api-key"}})],
].map(([slug,name,description,category,domain,urlPatterns,m,extra={}])=>({schemaVersion:1,slug,name,description,categories:[category],featured:["zapier","github","slack","notion","posthog","linear"].includes(slug),branding:{logoUrl:favicon(domain)},urlPatterns,methods:Array.isArray(m)?m:[m],...extra}));
apps.push({schemaVersion:1,slug:"gmail",name:"Gmail",description:"Search and read Gmail messages and create drafts without enabling mail sending.",categories:["communication","productivity"],featured:true,branding:{logoUrl:favicon("gmail.com")},urlPatterns:["https://gmailmcp.googleapis.com/*"],docsUrl:"https://developers.google.com/workspace/guides/configure-mcp-servers",redirectConstraints:"https-or-loopback-http",methods:[{key:"paperclip-id-oauth",label:"Connect Gmail",transport:"mcp_remote",auth:"oauth",oauthStrategy:"paperclip_id_connector",grantKinds:["user"],ownershipModes:["customer"],whenToUse:"Use Paperclip ID for a personal Gmail connection with centrally registered Google OAuth.",defaults:{serverUrl:"https://gmailmcp.googleapis.com/mcp/v1",scopesHint:["https://www.googleapis.com/auth/gmail.readonly","https://www.googleapis.com/auth/gmail.compose"]},guidanceMd:"Connect your Gmail identity. Paperclip can search and read mail and create drafts. Sending mail is not enabled.",warnings:["This connection is personal. Agents need an explicit install, profile, and delegation before they can use it."],riskTier:"S3"}]});
].map(([slug,name,description,category,_domain,urlPatterns,m,extra={}])=>({schemaVersion:1,slug,name,description,categories:[category],featured:["zapier","github","slack","notion","posthog","linear"].includes(slug),branding:brandingFor(slug),urlPatterns,methods:Array.isArray(m)?m:[m],...extra}));
apps.push({schemaVersion:1,slug:"gmail",name:"Gmail",description:"Search and read Gmail messages and create drafts without enabling mail sending.",categories:["communication","productivity"],featured:true,branding:brandingFor("gmail"),urlPatterns:["https://gmailmcp.googleapis.com/*"],docsUrl:"https://developers.google.com/workspace/guides/configure-mcp-servers",redirectConstraints:"https-or-loopback-http",methods:[{key:"paperclip-id-oauth",label:"Connect Gmail",transport:"mcp_remote",auth:"oauth",oauthStrategy:"paperclip_id_connector",grantKinds:["user"],ownershipModes:["customer"],whenToUse:"Use Paperclip ID for a personal Gmail connection with centrally registered Google OAuth.",defaults:{serverUrl:"https://gmailmcp.googleapis.com/mcp/v1",scopesHint:["https://www.googleapis.com/auth/gmail.readonly","https://www.googleapis.com/auth/gmail.compose"]},guidanceMd:"Connect your Gmail identity. Paperclip can search and read mail and create drafts. Sending mail is not enabled.",warnings:["This connection is personal. Agents need an explicit install, profile, and delegation before they can use it."],riskTier:"S3"}]});
// The reviewed MCP program is a durable input, not another hand-maintained
// allowlist. Runtime definitions are generated from the same 46-row evidence
// ledger that the tests and implementation checklist validate.
const researchManifest=JSON.parse(fs.readFileSync(path.join(root,"packages/shared/src/self-serve-mcp-research.json"),"utf8"));
const categoryBySlug={
airtable:"data",asana:"productivity",beehiiv:"content",bitly:"analytics",box:"content",brex:"commerce",candid:"data",clickhouse:"data",cloudflare:"developer",cloudinary:"content",coda:"productivity",egnyte:"content",embat:"commerce","hugging-face":"ai",jira:"productivity",kernel:"developer","local-falcon":"analytics",make:"productivity",manufact:"productivity",mem0:"ai",miro:"productivity",mixpanel:"analytics",netlify:"developer",notion:"content",oreilly:"content",pagerduty:"developer",planetscale:"data",posthog:"analytics",postman:"developer",razorpay:"commerce",resend:"communication",sanity:"content",sentry:"developer",similarweb:"analytics",stripe:"commerce",supabase:"data","ticket-tailor":"commerce",ticktick:"productivity",todoist:"productivity",webflow:"content",wix:"content",xero:"commerce",zapier:"productivity",
};
const oauthMethodFor=(entry,key="mcp-oauth",serverUrl=entry.serverUrl,extra={})=>method(key,"mcp_remote","oauth",{serverUrl},entry.riskTier,`Connect ${entry.name} in the browser. ${entry.prerequisite}`,{label:`Sign in with ${entry.name}`,ownershipModes:["dcr"],whenToUse:"Use browser sign-in for the provider-hosted MCP server.",consoleLinks:{docs:entry.docsUrl},warnings:[entry.prerequisite],...extra});
const customerOAuthMethodFor=(entry)=>oauthMethodFor(entry,"mcp-own-oauth",entry.serverUrl,{label:"Use your own OAuth app",ownershipModes:["customer"],whenToUse:`Register an OAuth app with ${entry.name}, then enter its client ID and secret.`,consoleLinks:{register:entry.docsUrl,docs:entry.docsUrl}});
const apiKeySpec={
bitly:{name:"Authorization",prefix:"Bearer ",placeholder:"Paste your Bitly API token"},
cloudflare:{name:"Authorization",prefix:"Bearer ",placeholder:"Paste your Cloudflare API token"},
coda:{name:"Authorization",prefix:"Bearer ",placeholder:"Paste your Coda API token"},
kernel:{name:"X-API-Key",prefix:null,placeholder:"Paste your Kernel API key"},
mem0:{name:"Authorization",prefix:"Bearer ",placeholder:"m0sk_..."},
oreilly:{name:"Authorization",prefix:"Bearer ",placeholder:"Paste your O'Reilly API token"},
pagerduty:{name:"Authorization",prefix:"Token token=",placeholder:"Paste your PagerDuty user API token"},
postman:{name:"X-API-Key",prefix:null,placeholder:"PMAK-..."},
razorpay:{name:"Authorization",prefix:"Basic ",placeholder:"Paste the base64-encoded key ID and secret"},
sanity:{name:"Authorization",prefix:"Bearer ",placeholder:"sk..."},
similarweb:{name:"api-key",prefix:null,placeholder:"Paste your Similarweb API key"},
stripe:{name:"Authorization",prefix:"Bearer ",placeholder:"sk_..."},
supabase:{name:"Authorization",prefix:"Bearer ",placeholder:"sbp_..."},
};
const apiKeyMethodFor=(entry,key="mcp-api-key",serverUrl=entry.serverUrl,extra={})=>{
const spec=apiKeySpec[entry.slug]??{name:"Authorization",prefix:"Bearer ",placeholder:`Paste your ${entry.name} API key`};
return method(key,"mcp_remote","api_key",{serverUrl},entry.riskTier,`Use a customer-created ${entry.name} key. ${entry.prerequisite}`,{label:"Use an API key",whenToUse:"Use a restricted customer-owned key when browser sign-in is not suitable.",credentialFields:[field("authorization",`${entry.name} API key`,spec.placeholder)],keyPlacement:{location:"header",name:spec.name,prefix:spec.prefix},consoleLinks:{keys:entry.docsUrl,docs:entry.docsUrl},warnings:[entry.prerequisite],...extra});
};
const specialMethodsFor=(entry)=>{
// Atlassian's /authv2 rollout only issues GA-tool-compatible tokens when the
// authorization request includes this reviewed protected-resource scope set.
// Omitting scope currently yields agent-interface scopes that its own Jira
// tools reject with HTTP 401. Users can still deselect write toolsets in the
// provider consent screen; never replace this allowlist with live discovery.
if(entry.slug==="jira") return [oauthMethodFor(entry,"mcp-oauth",entry.serverUrl,{defaults:{serverUrl:entry.serverUrl,scopesHint:["read:me","read:account","offline_access","email","read:jira-work","write:jira-work","search:confluence","read:confluence-user","read:page:confluence","write:page:confluence","read:comment:confluence","write:comment:confluence","read:space:confluence","read:hierarchical-content:confluence","write:component:compass","read:component:compass","read:scorecard:compass","write:scorecard:compass","read:event:compass","read:metric:compass","read:all:twg","write:all:twg"]}})];
if(entry.slug==="hugging-face") return [oauthMethodFor(entry,"mcp-oauth",entry.serverUrl,{defaults:{serverUrl:entry.serverUrl,scopesHint:["read-mcp"]}})];
if(entry.slug==="xero") return [oauthMethodFor(entry,"mcp-own-oauth",entry.serverUrl,{label:"Use your own OAuth app",ownershipModes:["customer"],whenToUse:`Register an OAuth app with ${entry.name}, then enter its client ID and secret.`,consoleLinks:{register:entry.docsUrl,docs:entry.docsUrl},defaults:{serverUrl:entry.serverUrl,scopesHint:["openid","profile","email","offline_access","accounting.settings","accounting.invoices.read","accounting.reports.aged.read","accounting.reports.balancesheet.read","accounting.reports.profitandloss.read"]}})];
if(entry.slug==="clickhouse") return [oauthMethodFor(entry,"mcp-oauth",entry.serverUrl,{tenantFields:[{key:"serviceId",label:"ClickHouse Cloud service ID",type:"text",required:true,placeholder:"11e1031f-9a13-4cac-9bc7-d4ec9286ec17",helperMd:"Copy the service ID from ClickStack → Team Settings → API & Agents.",transport:{location:"header",name:"x-service-id"}}],requiredResourceFilters:["service"]})];
if(entry.slug==="planetscale") return [
oauthMethodFor(entry,"mcp-oauth",entry.serverUrl,{label:"Database access",tenantFields:[{key:"project",label:"Project or database",type:"text",advanced:true,placeholder:"Optional project or database name",helperMd:"Records the intended database boundary; final access is selected during PlanetScale authorization."},{key:"branch",label:"Branch",type:"text",advanced:true,placeholder:"Optional branch name",helperMd:"Records the intended branch boundary; final access is selected during PlanetScale authorization."}],requiredResourceFilters:["organization","database","branch"]}),
oauthMethodFor(entry,"mcp-insights-only","https://mcp.pscale.dev/mcp/planetscale-insights-only",{label:"Insights only",whenToUse:"Use query insights and schema recommendations without query execution tools.",requiredResourceFilters:["organization","database","branch"]}),
];
if(entry.slug==="postman") return [
oauthMethodFor(entry,"mcp-oauth-minimal","https://mcp.postman.com/minimal",{label:"US · Minimal"}),
oauthMethodFor(entry,"mcp-oauth-code","https://mcp.postman.com/code",{label:"US · Code"}),
oauthMethodFor(entry,"mcp-oauth-full","https://mcp.postman.com/mcp",{label:"US · Full"}),
apiKeyMethodFor(entry,"mcp-eu-key-minimal","https://mcp.eu.postman.com/minimal",{label:"EU · Minimal"}),
apiKeyMethodFor(entry,"mcp-eu-key-code","https://mcp.eu.postman.com/code",{label:"EU · Code"}),
apiKeyMethodFor(entry,"mcp-eu-key-full","https://mcp.eu.postman.com/mcp",{label:"EU · Full"}),
];
if(entry.slug==="pagerduty") return [
apiKeyMethodFor(entry,"mcp-api-key-us","https://mcp.pagerduty.com/mcp",{label:"US service region"}),
apiKeyMethodFor(entry,"mcp-api-key-eu","https://mcp.eu.pagerduty.com/mcp",{label:"EU service region"}),
];
if(entry.slug==="supabase") {
const tenantFields=[
{key:"projectRef",label:"Project reference",type:"text",required:true,placeholder:"abcdefghijklmnopqrst",helperMd:"Scope the connection to one development project.",transport:{location:"query",name:"project_ref"}},
{key:"readOnly",label:"Read-only mode",type:"checkbox",defaultValue:false,helperMd:"Enable this to prevent the connection from changing the database.",transport:{location:"query",name:"read_only",format:"boolean"}},
{key:"features",label:"Feature groups",type:"textarea",advanced:true,placeholder:"database,docs",helperMd:"Optional comma-separated feature groups.",transport:{location:"query",name:"features",format:"csv"}},
];
const warning="Do not connect production data unless you have reviewed Supabase's MCP security guidance.";
return [
oauthMethodFor(entry,"mcp-oauth",entry.serverUrl,{guidanceMd:"Connect Supabase in the browser and scope the connection to one development project. Write tools start enabled and remain governed by Paperclip's action policies.",tenantFields,warnings:[entry.prerequisite,warning],requiredResourceFilters:["project"]}),
apiKeyMethodFor(entry,"mcp-api-key",entry.serverUrl,{guidanceMd:"Use a customer-created Supabase key scoped to one development project. Write tools start enabled and remain governed by Paperclip's action policies.",tenantFields,warnings:[entry.prerequisite,warning],requiredResourceFilters:["project"]}),
];
}
return null;
};
for(const entry of researchManifest.entries){
const existing=apps.find((app)=>app.slug===entry.slug);
if(entry.status==="blocked"){
if(existing) existing.availability={available:false,reason:entry.prerequisite};
continue;
}
if(existing){
existing.docsUrl=entry.docsUrl;
existing.redirectConstraints=existing.methods.some((entryMethod)=>entryMethod.auth==="oauth")?"https-or-loopback-http":existing.redirectConstraints;
if(entry.slug!=="zapier") for(const entryMethod of existing.methods) if(entryMethod.transport==="mcp_remote"&&entryMethod.defaults?.serverUrl) entryMethod.defaults.serverUrl=entry.serverUrl;
continue;
}
let methods=specialMethodsFor(entry);
if(!methods){
if(entry.authMode==="customer_oauth") methods=[customerOAuthMethodFor(entry)];
else if(entry.authMode==="api_key") methods=[apiKeyMethodFor(entry)];
else {
methods=[oauthMethodFor(entry)];
if(entry.authMode==="dcr_or_api_key") methods.push(apiKeyMethodFor(entry));
}
}
const warnings=[];
if(["coda","mixpanel"].includes(entry.slug)) warnings.push("This provider's hosted MCP server is currently beta or preview.");
if(["brex","razorpay","stripe"].includes(entry.slug)) warnings.push("Financial or destructive actions must be explicitly approved before execution.");
apps.push({schemaVersion:1,slug:entry.slug,name:entry.name,description:`Connect ${entry.name}'s provider-hosted MCP server.`,categories:[categoryBySlug[entry.slug]??"other"],featured:entry.slug==="jira",branding:brandingFor(entry.slug),urlPatterns:[`${new URL(entry.serverUrl).origin}/*`],docsUrl:entry.docsUrl,redirectConstraints:methods.some((entryMethod)=>entryMethod.auth==="oauth")?"https-or-loopback-http":undefined,methods:methods.map((entryMethod)=>warnings.length>0?{...entryMethod,warnings:[...(entryMethod.warnings??[]),...warnings]}:entryMethod)});
}
// Google Workspace definitions are reviewed, first-class app entries rather
// than rows synthesized from the generic connection corpus. Keep each product
// independent in the generated manifest while sharing only backend OAuth
// infrastructure.
const reviewedGoogleSlugs=["gmail","google-drive","google-docs","google-sheets","google-slides","google-calendar","google-chat","google-people","google-workspace-search"];
for(const slug of reviewedGoogleSlugs){
const existingIndex=apps.findIndex((app)=>app.slug===slug);
if(existingIndex>=0) apps.splice(existingIndex,1);
apps.push(JSON.parse(fs.readFileSync(path.join(out,`${slug}.json`),"utf8")));
}
const parseTableRow=(line)=>line.slice(1,-1).split("|").map((cell)=>cell.trim());
const parseCapture=(fileName)=>{
const markdown=fs.readFileSync(path.join(corpus,fileName),"utf8");
@ -49,10 +165,14 @@ const inferState=(slug,state)=>{
const transport=slug==="oauth-generic"||slug==="api-key-generic"||label.includes("path: api")||label.includes("api key form")?"rest_api":"mcp_remote";
const auth=slug==="oauth-generic"||label.includes("oauth")||fieldText.includes("client id")?"oauth":slug==="api-key-generic"||label.includes("api key")||fieldText.includes("api key")?"api_key":null;
const ownershipModes=[];
if(label.includes("managed")) ownershipModes.push("platform_shared");
// A "Managed" state in Vercel describes credential custody, not ownership of
// a Paperclip connection. Keep those concepts separate: importing this review
// evidence must never silently turn an operator-owned connector into
// `platform_shared`.
const externalCredentialCustody=label.includes("managed")&&!label.includes("no managed")?"vercel_connect":null;
if(label.includes("your own credentials")||label.includes("manual")||label.includes("api key")) ownershipModes.push("customer");
if(slug==="oauth-generic"&&!label.includes("manually")) ownershipModes.push("dcr");
return {label:state.label,transport,auth,ownershipModes:[...new Set(ownershipModes)],fieldCount:state.fields.length,linkCount:state.links.length};
return {label:state.label,transport,auth,ownershipModes:[...new Set(ownershipModes)],externalCredentialCustody,fieldCount:state.fields.length,linkCount:state.links.length};
};
const validateApp=(app)=>{
if(app.schemaVersion!==1||!app.slug||!app.name||!Array.isArray(app.methods)||app.methods.length===0) throw new Error(`${app.slug||"unknown"}: invalid AppDefinition`);

View File

@ -3403,23 +3403,32 @@ describeEmbeddedPostgres("tool access service", () => {
canSetCompanyInstall: true,
companyInstallReason: null,
});
expect(res.body.apps.map((app: { slug: string }) => app.slug)).toEqual([
"zapier",
"github",
"slack",
expect(res.body.apps.map((app: { slug: string }) => app.slug)).toEqual(expect.arrayContaining([
"jira",
"airtable",
"asana",
"notion",
"posthog",
"sentry",
"zapier",
"linear",
"google-sheets",
"context7",
"composio",
"gmail",
]);
expect(res.body.apps.find((app: { slug: string }) => app.slug === "gmail").availability).toEqual({
available: false,
reason: "Gmail is not available on this Paperclip instance yet.",
"google-drive",
"google-docs",
"google-sheets",
"google-slides",
"google-calendar",
"google-chat",
"google-people",
"google-workspace-search",
]));
expect(res.body.apps).toHaveLength(58);
expect(res.body.apps.find((app: { slug: string }) => app.slug === "gmail").ownershipAvailability).toEqual({
platform_shared: false,
platform_provisioned: false,
customer: true,
dcr: true,
});
expect(res.body.apps.map((app: { slug: string }) => app.slug)).not.toContain("google-drive");
expect(res.body.apps).toEqual(
expect.arrayContaining([
expect.objectContaining({
@ -3448,14 +3457,16 @@ describeEmbeddedPostgres("tool access service", () => {
slug: "zapier",
methods: expect.arrayContaining([
expect.objectContaining({
credentialFields: [expect.objectContaining({ key: "authorization" })],
keyPlacement: expect.objectContaining({ location: "header", name: "Authorization" }),
key: "generated-url",
auth: "none",
}),
]),
}),
expect.objectContaining({
slug: "google-sheets",
availability: expect.objectContaining({ available: false }),
methods: expect.arrayContaining([
expect.objectContaining({ key: "local", transport: "local_stdio" }),
]),
}),
]),
);
@ -3908,12 +3919,14 @@ describeEmbeddedPostgres("tool access service", () => {
await service.connectGalleryApp(companyB.id, {
galleryKey: "google-sheets",
connectionMethodKey: "local",
name: "Company B sheets",
configValues: { allowedSpreadsheetIds: ["shared-sheet"] },
}, { actorType: "user", actorId: "board-b" });
await expect(service.connectGalleryApp(companyA.id, {
galleryKey: "google-sheets",
connectionMethodKey: "local",
name: "Company A sheets",
configValues: { allowedSpreadsheetIds: ["shared-sheet"] },
}, { actorType: "user", actorId: "board-a" })).rejects.toMatchObject({
@ -3936,6 +3949,7 @@ describeEmbeddedPostgres("tool access service", () => {
const connect = await service.connectGalleryApp(company.id, {
galleryKey: "google-sheets",
connectionMethodKey: "local",
name: "Company sheets",
configValues: { allowedSpreadsheetIds: ["sheet-with-inputs"] },
}, { actorType: "user", actorId: "board" });
@ -4003,11 +4017,13 @@ describeEmbeddedPostgres("tool access service", () => {
await service.connectGalleryApp(companyB.id, {
galleryKey: "google-sheets",
connectionMethodKey: "local",
name: "Company B sheets",
configValues: { allowedSpreadsheetIds: ["company-b-sheet"] },
}, { actorType: "user", actorId: "board-b" });
const companyAConnection = await service.connectGalleryApp(companyA.id, {
galleryKey: "google-sheets",
connectionMethodKey: "local",
name: "Company A sheets",
configValues: { allowedSpreadsheetIds: ["company-a-sheet"] },
}, { actorType: "user", actorId: "board-a" });
@ -4088,11 +4104,13 @@ describeEmbeddedPostgres("tool access service", () => {
const first = await service.connectGalleryApp(company.id, {
galleryKey: "google-sheets",
connectionMethodKey: "local",
name: "First sheets",
configValues: { allowedSpreadsheetIds: ["same-company-sheet"] },
}, { actorType: "user", actorId: "board" });
const second = await service.connectGalleryApp(company.id, {
galleryKey: "google-sheets",
connectionMethodKey: "local",
name: "Second sheets",
configValues: { allowedSpreadsheetIds: ["same-company-sheet"] },
}, { actorType: "user", actorId: "board" });
@ -6264,10 +6282,10 @@ describeEmbeddedPostgres("tool access service", () => {
runtimeConfig: {},
}).returning();
const connect = await withGalleryServerUrl("zapier", PUBLIC_MCP_FIXTURE_URL, () =>
const connect = await withGalleryServerUrl("github", PUBLIC_MCP_FIXTURE_URL, () =>
service.connectGalleryApp(company.id, {
galleryKey: "zapier",
name: "Zapier workspace",
galleryKey: "github",
name: "GitHub workspace",
credentialValues: { "credentials.authorization": "zap-secret" },
}, { actorType: "user", actorId: "board" }));
@ -6281,11 +6299,11 @@ describeEmbeddedPostgres("tool access service", () => {
expect(connect.connection).toMatchObject({
status: "draft",
enabled: false,
config: expect.objectContaining({ sourceTemplateKey: "zapier", quarantineNewEntries: false }),
config: expect.objectContaining({ sourceTemplateKey: "github", quarantineNewEntries: false }),
credentialSecretRefs: [
expect.objectContaining({
configPath: "credentials.authorization",
label: "Zapier MCP token",
label: "GitHub token",
}),
],
});
@ -6655,11 +6673,12 @@ describeEmbeddedPostgres("tool access service", () => {
runtimeConfig: {},
}).returning();
const connect = await service.connectGalleryApp(company.id, {
galleryKey: "zapier",
name: "Zapier rollback",
credentialValues: { "credentials.authorization": "zap-secret" },
}, { actorType: "user", actorId: "board" });
const connect = await withGalleryServerUrl("github", PUBLIC_MCP_FIXTURE_URL, () =>
service.connectGalleryApp(company.id, {
galleryKey: "github",
name: "GitHub rollback",
credentialValues: { "credentials.authorization": "github-secret" },
}, { actorType: "user", actorId: "board" }));
const listEntry = connect.catalog.find((entry) => entry.toolName === "list_zaps")!;
const updateEntry = connect.catalog.find((entry) => entry.toolName === "update_zap")!;
const firstFinish = await service.finishGalleryAppConnection(company.id, connect.connectionId, {
@ -6729,10 +6748,10 @@ describeEmbeddedPostgres("tool access service", () => {
{ name: "update_zap", description: "Update", inputSchema: { type: "object", properties: {} }, annotations: { readOnlyHint: false } },
]);
const connect = await withGalleryServerUrl("zapier", PUBLIC_MCP_FIXTURE_URL, () =>
const connect = await withGalleryServerUrl("github", PUBLIC_MCP_FIXTURE_URL, () =>
service.connectGalleryApp(company.id, {
galleryKey: "zapier",
name: "Zapier reconnect",
galleryKey: "github",
name: "GitHub reconnect",
credentialValues: { "credentials.authorization": "old-secret" },
}, { actorType: "user", actorId: "board" }));
@ -8637,6 +8656,16 @@ describe("normalizeConnectionMethodConfig", () => {
const posthog = getConnectableAppDefinition("posthog")!;
const apiKeyMethod = posthog.methods.find((method) => method.key === "mcp-api-key")!;
it("builds a concrete Shopify endpoint from the validated store domain", () => {
const shopifyMethod = getConnectableAppDefinition("shopify")!.methods[0]!;
expect(normalizeConnectionMethodConfig(shopifyMethod, {
storeDomain: "paperclip-demo.myshopify.com",
})).toEqual({
values: { storeDomain: "paperclip-demo.myshopify.com" },
url: "https://paperclip-demo.myshopify.com/api/mcp",
});
});
it("uses the broad PostHog catalog when optional advanced filters are untouched", () => {
expect(normalizeConnectionMethodConfig(apiKeyMethod, {
projectId: "12345",
@ -8672,7 +8701,7 @@ describe("normalizeConnectionMethodConfig", () => {
expect(() => normalizeConnectionMethodConfig(apiKeyMethod, {
projectId: "not-a-project",
features: "insights",
})).toThrow("Project ID has an invalid value");
})).toThrow("Pin to project ID has an invalid value");
expect(() => normalizeConnectionMethodConfig(apiKeyMethod, {
projectId: "12345",
features: "insights",

View File

@ -117,7 +117,7 @@ import type {
UpdateToolProfileWithEntries,
UnbindToolProfileBinding,
} from "@paperclipai/shared";
import { CLASS3_STATIC_LEASE_ALLOWLIST, credentialConfigPath, getAvailableConnectionMethod, getAvailableConnectionMethods, getConnectableAppDefinition, isToolConnectionAttentionHealth, recommendedDefaultsForApp } from "@paperclipai/shared";
import { CLASS3_STATIC_LEASE_ALLOWLIST, credentialConfigPath, getAvailableConnectionMethod, getAvailableConnectionMethods, getConnectableAppDefinition, isToolConnectionAttentionHealth, recommendedDefaultsForApp, resolveConnectionMethodServerUrl } from "@paperclipai/shared";
import {
checkMcpRemoteHeaderName,
checkMcpRemoteHeaderValue,
@ -854,7 +854,11 @@ export function normalizeConnectionMethodConfig(
}
}
const endpoint = method.defaults?.serverUrl ? new URL(method.defaults.serverUrl) : null;
const resolvedServerUrl = resolveConnectionMethodServerUrl(method, values);
if (method.defaults?.serverUrlTemplate && !resolvedServerUrl) {
throw badRequest("Missing or invalid connection settings for the server URL");
}
const endpoint = resolvedServerUrl ? new URL(resolvedServerUrl) : null;
const headers: Record<string, string> = {};
for (const field of fields) {
const transport = field.transport;

View File

@ -1088,11 +1088,10 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
const deny = () => {
throw new DOMException("The operation is insecure.", "SecurityError");
};
const getItem = vi.spyOn(Storage.prototype, "getItem").mockImplementation(deny);
const removeItem = vi
.spyOn(Storage.prototype, "removeItem")
.mockImplementation(deny);
const setItem = vi.spyOn(Storage.prototype, "setItem").mockImplementation(deny);
// Deny the browser boundary itself. Spying on a Storage object or
// prototype is not portable: DOM implementations may return a fresh
// wrapper and Node exposes a separate experimental Storage global.
const localStorage = vi.spyOn(window, "localStorage", "get").mockImplementation(deny);
mockCompany.companies = [{ id: "c1", name: "My Co", issuePrefix: "MC" }];
mockCompany.loading = false;
@ -1106,14 +1105,12 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
});
await flushReact();
expect(getItem).toHaveBeenCalled();
expect(localStorage).toHaveBeenCalled();
// It mounted: the wizard is open with no draft, rather than the render
// throwing on the way in.
expect(document.body.textContent).not.toBe("");
getItem.mockRestore();
removeItem.mockRestore();
setItem.mockRestore();
localStorage.mockRestore();
await act(async () => {
root.unmount();
});
@ -1171,9 +1168,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
const deny = () => {
throw new DOMException("The operation is insecure.", "SecurityError");
};
const getItem = vi.spyOn(Storage.prototype, "getItem").mockImplementation(deny);
const removeItem = vi.spyOn(Storage.prototype, "removeItem").mockImplementation(deny);
const setItem = vi.spyOn(Storage.prototype, "setItem").mockImplementation(deny);
const localStorage = vi.spyOn(window, "localStorage", "get").mockImplementation(deny);
mockCompany.companies = [{ id: "c1", name: "My Co", issuePrefix: "MC" }];
mockCompany.loading = false;
@ -1198,9 +1193,7 @@ describe("OnboardingWizard restore-gate (stale localStorage across accounts)", (
expect(mockDialog.closeOnboarding).toHaveBeenCalled();
getItem.mockRestore();
removeItem.mockRestore();
setItem.mockRestore();
localStorage.mockRestore();
await act(async () => {
root.unmount();
});

View File

@ -211,21 +211,21 @@ Propose, don't decide. Keep it conversational.`;
const onboardingDraftStorage = {
read(): string | null {
try {
return localStorage.getItem(ONBOARDING_STORAGE_KEY);
return window.localStorage.getItem(ONBOARDING_STORAGE_KEY);
} catch {
return null;
}
},
write(value: string): void {
try {
localStorage.setItem(ONBOARDING_STORAGE_KEY, value);
window.localStorage.setItem(ONBOARDING_STORAGE_KEY, value);
} catch {
// Storage unavailable: the draft is simply not resumable this session.
}
},
clear(): void {
try {
localStorage.removeItem(ONBOARDING_STORAGE_KEY);
window.localStorage.removeItem(ONBOARDING_STORAGE_KEY);
} catch {
// Nothing to do. A draft that cannot be cleared is re-rejected on the
// next load by the same ownership check that rejected it here.

View File

@ -1,7 +1,7 @@
// @vitest-environment jsdom
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { CONNECTABLE_APP_DEFINITIONS } from "@paperclipai/shared";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@ -23,6 +23,7 @@ const mockSearch = vi.hoisted(() => ({ value: "" }));
const mockParams = vi.hoisted(() => ({ appKey: undefined as string | undefined }));
const ZAPIER = CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === "zapier")!;
const MEM0 = CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === "mem0")!;
const NOTION = CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === "notion")!;
const POSTHOG = CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === "posthog")!;
const GOOGLE_SHEETS = CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === "google-sheets")!;
@ -74,14 +75,6 @@ vi.mock("@/context/ToastContext", () => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
async function act(callback: () => void | Promise<void>) {
let result: void | Promise<void> = undefined;
flushSync(() => {
result = callback();
});
await result;
}
async function flushReact() {
await act(async () => {
await Promise.resolve();
@ -157,12 +150,14 @@ async function gotoLinkFrame(container: HTMLDivElement, url: string) {
describe("AppsConnect — Connect with a link (M4 frame)", () => {
let container: HTMLDivElement;
let mountedRoot: Root | null;
beforeEach(() => {
mockSearch.value = "";
mockParams.appKey = undefined;
container = document.createElement("div");
document.body.appendChild(container);
mountedRoot = null;
listGalleryMock.mockResolvedValue({
apps: [
ZAPIER,
@ -195,7 +190,10 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
]);
});
afterEach(() => {
afterEach(async () => {
if (mountedRoot) {
await act(async () => mountedRoot?.unmount());
}
document.body.removeChild(container);
document.body.innerHTML = "";
vi.clearAllMocks();
@ -203,6 +201,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
async function render(queryClient?: QueryClient, byoOnly = false) {
const root = createRoot(container);
mountedRoot = root;
const client = queryClient ?? new QueryClient({ defaultOptions: { queries: { retry: false } } });
await act(async () => {
root.render(
@ -259,14 +258,15 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
// -------------------------------------------------------------------------
it("asks both access questions before the credential and defaults per auth kind", async () => {
mockParams.appKey = "zapier";
mockParams.appKey = "mem0";
listGalleryMock.mockResolvedValueOnce({ apps: [MEM0] });
await render();
expect(container.textContent).toContain("Access");
expect(container.textContent).toContain("Who is this credential for?");
expect(container.textContent).toContain("Which agents can use this connection?");
// Nothing about the credential is on screen yet.
expect(container.textContent).not.toContain("Connect Zapier");
expect(container.textContent).not.toContain("Connect Mem0");
const radios = Array.from(document.body.querySelectorAll('[role="radio"]'));
const justMe = radios.find((r) => r.textContent?.includes("Just me"));
@ -282,7 +282,8 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
});
it("blocks Continue until Agents I pick has at least one agent", async () => {
mockParams.appKey = "zapier";
mockParams.appKey = "mem0";
listGalleryMock.mockResolvedValueOnce({ apps: [MEM0] });
await render();
// "Agents I pick" with nothing picked is not a usable connection, so the
@ -300,7 +301,8 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
});
it("keeps the access selections when the wizard moves backward", async () => {
mockParams.appKey = "zapier";
mockParams.appKey = "mem0";
listGalleryMock.mockResolvedValueOnce({ apps: [MEM0] });
await render();
await act(async () => {
@ -320,7 +322,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
buttonByText("Save and continue")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flushReact();
expect(container.textContent).toContain("Connect Zapier");
expect(container.textContent).toContain("Connect Mem0");
await act(async () => {
buttonByText("Back")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
@ -340,7 +342,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
*
* A homogeneous fixture cannot tell those two apart: if every app on screen
* is API-key-only, a blanket rule and a per-method predicate produce exactly
* the same DOM. So both live in one gallery here Zapier, whose only method
* the same DOM. So both live in one gallery here Mem0, whose only method
* is an API key, and PostHog, whose methods are identity-bearing sign-in plus
* a key its own label calls *personal*. The two mounts must disagree about
* the default, which no blanket rule can do.
@ -350,7 +352,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
* grant, so disabling it would describe the product wrongly.
*/
it("decides the identity default per method, and keeps a personal key submittable", async () => {
listGalleryMock.mockResolvedValue({ apps: [ZAPIER, POSTHOG] });
listGalleryMock.mockResolvedValue({ apps: [MEM0, POSTHOG] });
const identityChoices = () => {
const radios = Array.from(
@ -365,22 +367,22 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
// --- API-key-only method: shared by default, personal still offered ------
let root = await render();
await act(async () => {
buttonContaining("Zapier")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
buttonContaining("Mem0")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flushReact();
const zapier = identityChoices();
expect(zapier.wholeOrg?.getAttribute("aria-checked")).toBe("true");
expect(zapier.justMe?.getAttribute("aria-checked")).toBe("false");
const mem0 = identityChoices();
expect(mem0.wholeOrg?.getAttribute("aria-checked")).toBe("true");
expect(mem0.justMe?.getAttribute("aria-checked")).toBe("false");
// Present, and genuinely selectable — not the disabled-with-reason state.
expect(zapier.justMe).toBeTruthy();
expect(zapier.justMe?.disabled).toBe(false);
expect(mem0.justMe).toBeTruthy();
expect(mem0.justMe?.disabled).toBe(false);
expect(document.body.textContent).not.toContain(
"This connection method supports a shared organization credential only.",
);
await act(async () => {
zapier.justMe?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
mem0.justMe?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flushReact();
await act(async () => {
@ -395,7 +397,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
await flushReact();
const keyField = container.querySelector<HTMLInputElement>("input[type=password]");
await act(async () => setInputValue(keyField!, "zapier-personal-token"));
await act(async () => setInputValue(keyField!, "mem0-personal-token"));
await flushReact();
await act(async () => {
buttonByText("Connect")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
@ -406,7 +408,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
// a personal grant. A disabled "Just me" would make this unreachable.
expect(connectAppMock).toHaveBeenCalledTimes(1);
expect(connectAppMock.mock.calls[0]?.[1]).toMatchObject({
galleryKey: "zapier",
galleryKey: "mem0",
grantKind: "user",
});
@ -522,12 +524,12 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
});
await flushReact();
const projectInput = container.querySelector<HTMLInputElement>('input[placeholder="12345"]');
const keyInput = container.querySelector<HTMLInputElement>('input[type="password"]');
const advanced = buttonByText("Advanced");
expect(projectInput).toBeTruthy();
expect(keyInput).toBeTruthy();
expect(container.querySelector('[role="switch"]')?.getAttribute("aria-checked")).toBe("false");
expect(container.querySelector<HTMLInputElement>('input[placeholder="Optional numeric project ID"]'))
.toBeNull();
expect(container.querySelector('[role="switch"]')).toBeNull();
expect(advanced?.getAttribute("aria-expanded")).toBe("false");
expect(container.textContent).not.toContain("Feature groups");
expect(container.textContent).not.toContain("Individual tools");
@ -541,7 +543,12 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
expect(advanced?.getAttribute("aria-expanded")).toBe("true");
expect(container.textContent).toContain("Feature groups");
expect(container.textContent).toContain("Individual tools");
expect(container.textContent).toContain("Tool response mode");
expect(container.textContent).not.toContain("Tool response mode");
const projectInput = container.querySelector<HTMLInputElement>(
'input[placeholder="Optional numeric project ID"]',
);
expect(projectInput).toBeTruthy();
expect(container.querySelector('[role="switch"]')?.getAttribute("aria-checked")).toBe("false");
await act(async () => {
setInputValue(projectInput!, "12345");
@ -1222,10 +1229,11 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
});
it("leaving the default name connects with the app name", async () => {
listGalleryMock.mockResolvedValueOnce({ apps: [MEM0] });
await render();
await act(async () => {
buttonContaining("Zapier")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
buttonContaining("Mem0")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flushReact();
await passAccessStep();
@ -1239,21 +1247,22 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
await flushReact();
expect(connectAppMock).toHaveBeenCalledTimes(1);
expect(mockNavigate).toHaveBeenCalledWith("/apps/connect?byo=1&appKey=zapier&stage=access");
expect(mockNavigate).toHaveBeenCalledWith("/apps/connect?byo=1&appKey=mem0&stage=access");
const [, input] = connectAppMock.mock.calls[0];
expect(input).toMatchObject({ galleryKey: "zapier", name: "Zapier" });
expect(input).toMatchObject({ galleryKey: "mem0", name: "Mem0" });
});
it("a custom name in the gallery step is sent to the connect mutation", async () => {
listGalleryMock.mockResolvedValueOnce({ apps: [MEM0] });
await render();
await act(async () => {
buttonContaining("Zapier")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
buttonContaining("Mem0")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flushReact();
await passAccessStep();
await act(async () => setInputValue(nameInputFrom(container)!, "Zapier (stdio smoke)"));
await act(async () => setInputValue(nameInputFrom(container)!, "Mem0 (stdio smoke)"));
const keyField = container.querySelector<HTMLInputElement>("input[type=password]");
await act(async () => setInputValue(keyField!, "secret-key"));
await flushReact();
@ -1264,7 +1273,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
expect(connectAppMock).toHaveBeenCalledTimes(1);
const [, input] = connectAppMock.mock.calls[0];
expect(input).toMatchObject({ galleryKey: "zapier", name: "Zapier (stdio smoke)" });
expect(input).toMatchObject({ galleryKey: "mem0", name: "Mem0 (stdio smoke)" });
});
it("a custom name on the Google Sheets step is sent to the connect mutation", async () => {
@ -1346,12 +1355,14 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
*/
describe("AppsConnect — guided generic MCP flow (PAP-17087)", () => {
let container: HTMLDivElement;
let mountedRoot: Root | null;
beforeEach(() => {
mockSearch.value = "";
mockParams.appKey = undefined;
container = document.createElement("div");
document.body.appendChild(container);
mountedRoot = null;
listGalleryMock.mockResolvedValue({ apps: [ZAPIER] });
listApplicationsMock.mockResolvedValue({ applications: [] });
listConnectionsMock.mockResolvedValue({ connections: [] });
@ -1373,7 +1384,10 @@ describe("AppsConnect — guided generic MCP flow (PAP-17087)", () => {
});
});
afterEach(() => {
afterEach(async () => {
if (mountedRoot) {
await act(async () => mountedRoot?.unmount());
}
document.body.removeChild(container);
document.body.innerHTML = "";
vi.clearAllMocks();
@ -1381,6 +1395,7 @@ describe("AppsConnect — guided generic MCP flow (PAP-17087)", () => {
async function render() {
const root = createRoot(container);
mountedRoot = root;
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
await act(async () => {
root.render(

View File

@ -1903,8 +1903,12 @@ function KeyStep({
(f) => f.required === false || (values[f.configPath]?.trim().length ?? 0) > 0,
);
const configFields = [...(method?.tenantFields ?? []), ...(method?.extensionFields ?? [])];
const standardConfigFields = configFields.filter((field) => field.advanced !== true);
const advancedConfigFields = configFields.filter((field) => field.advanced === true);
const standardConfigFields = configFields.filter(
(field) => field.hidden !== true && field.advanced !== true,
);
const advancedConfigFields = configFields.filter(
(field) => field.hidden !== true && field.advanced === true,
);
const [advancedOpen, setAdvancedOpen] = useState(false);
const configFilled = configFields.every((field) => {
if (!field.required) return true;