diff --git a/doc/connections/CONNECTOR-PLAYBOOK.md b/doc/connections/CONNECTOR-PLAYBOOK.md index 28b57f519da..06b048c6941 100644 --- a/doc/connections/CONNECTOR-PLAYBOOK.md +++ b/doc/connections/CONNECTOR-PLAYBOOK.md @@ -91,6 +91,11 @@ Capture: - `methods`: explicit combinations of `transport` (`mcp_remote`, `rest_api`, `local_stdio`), `authKind` (`oauth`, `api_key`, `none`), and `ownership` (`platform_shared`, `platform_provisioned`, `customer`, `dcr`). - Stable connection UID namespace used to form `{namespace}/{slug}` addresses. - `credentialFields`: labels, vendor-call placement, header key, prefix, help URL, and required state. User-facing labels should be sanitized by the Apps UI copy layer. The saved value is always a `company_secrets` ref, not an env entry. +- `tenantFields` and `extensionFields`: keep unavoidable identity and resource + boundaries in the default flow. Mark optional scope reduction, feature/tool + filters, response modes, and transport tuning with `advanced: true`, and give + advanced fields working defaults that do not require the operator to expand + the disclosure. - `oauth`: provider key, scopes, authorization URL, token URL, metadata URL if applicable. - `urlPatterns`: URLs that can identify this app during paste/import flows. - `recommendedDefaults`: access and risk defaults, especially ask-first risk levels. @@ -151,6 +156,13 @@ The wizard path comes from auth mode and transport: The operator should see Apps, Connections, and Review language. Keep protocol language behind Developer/Advanced copy. +Default to the broadest vendor permissions and scopes the reviewed connection +can support. Operators should not have to predict every future tool during +setup. Keep the default view to the minimum inputs needed for a working +connection, fold optional expert controls under one collapsed **Advanced** +disclosure, and enforce safe execution afterward through Paperclip's resource +boundaries, catalog review, ask-first policies, quarantine, and audit. + ## Step 8: Apply Governance Defaults Governance is automatic because every catalog entry becomes a normal tool-access object: @@ -750,4 +762,3 @@ End-to-end evidence belongs to PAP-16654 (P6) and the PAP-12373 matrix: - Revocation removes Notion tools and blocks execution. - Audit rows prove actor, run/issue context, connection, tool, decision, reason code, and outcome. - diff --git a/doc/connections/POSTHOG.md b/doc/connections/POSTHOG.md index 8545775ab2b..02a3cf9a7c4 100644 --- a/doc/connections/POSTHOG.md +++ b/doc/connections/POSTHOG.md @@ -24,7 +24,7 @@ sequenceDiagram participant M as mcp.posthog.com participant O as oauth.posthog.com - A->>P: Choose PostHog sign-in and project filters + A->>P: Choose PostHog sign-in and project P->>M: Discover protected-resource metadata M-->>P: Authorization server metadata URL P->>O: Discover endpoints and register OAuth client @@ -34,8 +34,8 @@ sequenceDiagram O-->>P: Redirect to /api/tools/oauth/callback P->>O: Exchange authorization code O-->>P: Access and refresh tokens - P->>M: tools/list with project and safety filters - M-->>P: Filtered PostHog tool catalog + P->>M: tools/list with project and configured options + M-->>P: PostHog tool catalog ``` The current hosted endpoints are: @@ -60,11 +60,13 @@ values for either. 1. In **Apps → Browse**, choose **PostHog**. 2. Explicitly choose **Sign in with PostHog** or **Use a personal API key**. 3. Enter the numeric PostHog project ID. -4. Keep **Read-only mode** enabled unless agents have an approved need to make - changes. -5. Limit the catalog with one or both of **Feature groups** and **Individual - tools**. Paperclip currently fixes response mode to individual tools; CLI - mode is unavailable until nested execution is governed. +4. Leave **Read-only mode** off to expose the full PostHog action catalog. + Turn it on when the connection should never offer actions that change data. +5. The default setup requests all feature groups and tools. Open **Advanced** + only to narrow the catalog with **Feature groups** or **Individual tools**. + Paperclip fixes the advanced response mode to individual tools so each + action can be governed; CLI mode is unavailable until nested execution is + governed. 6. For OAuth, continue through browser consent. For API-key setup, create a personal API key using PostHog's **MCP Server** preset and paste it into Paperclip. Never put the key in connection configuration or a URL. @@ -73,9 +75,10 @@ values for either. write risk until reviewed. Paperclip sends the project scope as the `x-posthog-project-id` managed header. -It sends `readonly`, `features`, `tools`, and `mode` as query parameters. The -managed header is identical during catalog discovery and tool execution, and a -caller cannot override it. PostHog documents these options in its [MCP +It sends configured `readonly`, `features`, `tools`, and `mode` values as query +parameters. Leaving the optional feature and tool filters blank exposes the +full catalog. The managed header is identical during catalog discovery and tool +execution, and a caller cannot override it. PostHog documents these options in its [MCP overview](https://posthog.com/docs/model-context-protocol) and [MCP FAQ](https://posthog.com/docs/model-context-protocol/faq). diff --git a/packages/shared/src/app-definitions.test.ts b/packages/shared/src/app-definitions.test.ts index 38cf9e53e08..b31b2e60bfc 100644 --- a/packages/shared/src/app-definitions.test.ts +++ b/packages/shared/src/app-definitions.test.ts @@ -10,6 +10,6 @@ describe("AppDefinition catalog",()=>{ 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("offers PostHog OAuth and API-key methods with pinned safe defaults",()=>{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(true);expect(method.tenantFields?.find((field)=>field.key==="projectId")?.transport).toEqual({location:"header",name:"x-posthog-project-id"});expect(method.configRequirements?.atLeastOneOf).toEqual(["features","tools"])}}); + 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("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()}}); }); diff --git a/packages/shared/src/app-definitions/posthog.json b/packages/shared/src/app-definitions/posthog.json index 64f77e0ceca..033af16cd55 100644 --- a/packages/shared/src/app-definitions/posthog.json +++ b/packages/shared/src/app-definitions/posthog.json @@ -26,7 +26,7 @@ "defaults": { "serverUrl": "https://mcp.posthog.com/mcp" }, - "guidanceMd": "Pin the connection to one PostHog project, keep read-only mode enabled by default, and expose only the feature groups or tools agents need.", + "guidanceMd": "Pin the connection to one PostHog project and expose the full tool catalog by default. Narrow feature groups or tools only when needed.", "riskTier": "S3", "tenantFields": [ { @@ -49,8 +49,8 @@ "key": "readOnly", "label": "Read-only mode", "type": "checkbox", - "defaultValue": true, - "helperMd": "Keep enabled unless agents explicitly need to change PostHog data.", + "defaultValue": false, + "helperMd": "Turn on to hide tools that can change PostHog data.", "transport": { "location": "query", "name": "readonly", @@ -62,9 +62,9 @@ "key": "features", "label": "Feature groups", "type": "textarea", - "placeholder": "insights, error_tracking, flags, experiments", - "defaultValue": "insights,error_tracking,flags,experiments", - "helperMd": "Comma-separated PostHog feature groups to expose.", + "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 }, @@ -78,8 +78,9 @@ "key": "tools", "label": "Individual tools", "type": "textarea", + "advanced": true, "placeholder": "Optional comma-separated tool names", - "helperMd": "Optional exact tool names. These are combined with feature groups.", + "helperMd": "Leave blank to expose all tools. Exact names here are combined with any feature groups.", "validation": { "maxLength": 2000 }, @@ -93,6 +94,7 @@ "key": "mode", "label": "Tool response mode", "type": "select", + "advanced": true, "required": true, "placeholder": "Individual tools", "defaultValue": "tools", @@ -102,25 +104,15 @@ "label": "Individual tools" } ], - "helperMd": "CLI mode remains unavailable until nested execution is governed.", + "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" } } ], - "configRequirements": { - "atLeastOneOf": [ - "features", - "tools" - ] - }, "requiredResourceFilters": [ - "project", - "read_only", - "features", - "tools", - "mode" + "project" ], "label": "Sign in with PostHog", "consoleLinks": { @@ -138,7 +130,7 @@ "defaults": { "serverUrl": "https://mcp.posthog.com/mcp" }, - "guidanceMd": "Pin the connection to one PostHog project, keep read-only mode enabled by default, and expose only the feature groups or tools agents need.", + "guidanceMd": "Pin the connection to one PostHog project and expose the full tool catalog by default. Narrow feature groups or tools only when needed.", "riskTier": "S3", "tenantFields": [ { @@ -161,8 +153,8 @@ "key": "readOnly", "label": "Read-only mode", "type": "checkbox", - "defaultValue": true, - "helperMd": "Keep enabled unless agents explicitly need to change PostHog data.", + "defaultValue": false, + "helperMd": "Turn on to hide tools that can change PostHog data.", "transport": { "location": "query", "name": "readonly", @@ -174,9 +166,9 @@ "key": "features", "label": "Feature groups", "type": "textarea", - "placeholder": "insights, error_tracking, flags, experiments", - "defaultValue": "insights,error_tracking,flags,experiments", - "helperMd": "Comma-separated PostHog feature groups to expose.", + "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 }, @@ -190,8 +182,9 @@ "key": "tools", "label": "Individual tools", "type": "textarea", + "advanced": true, "placeholder": "Optional comma-separated tool names", - "helperMd": "Optional exact tool names. These are combined with feature groups.", + "helperMd": "Leave blank to expose all tools. Exact names here are combined with any feature groups.", "validation": { "maxLength": 2000 }, @@ -205,6 +198,7 @@ "key": "mode", "label": "Tool response mode", "type": "select", + "advanced": true, "required": true, "placeholder": "Individual tools", "defaultValue": "tools", @@ -214,25 +208,15 @@ "label": "Individual tools" } ], - "helperMd": "CLI mode remains unavailable until nested execution is governed.", + "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" } } ], - "configRequirements": { - "atLeastOneOf": [ - "features", - "tools" - ] - }, "requiredResourceFilters": [ - "project", - "read_only", - "features", - "tools", - "mode" + "project" ], "label": "Use a personal API key", "credentialFields": [ diff --git a/packages/shared/src/types/app-definition.ts b/packages/shared/src/types/app-definition.ts index 134458cb49c..b1db31ef1db 100644 --- a/packages/shared/src/types/app-definition.ts +++ b/packages/shared/src/types/app-definition.ts @@ -1,6 +1,6 @@ import type { ToolConnectionOwnership, ToolConnectionTransport } 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; 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 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"; 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> } diff --git a/packages/shared/src/validators/app-definition.ts b/packages/shared/src/validators/app-definition.ts index 79b2ee2f59d..7a005f48e82 100644 --- a/packages/shared/src/validators/app-definition.ts +++ b/packages/shared/src/validators/app-definition.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { 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(),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"]})}); +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"]),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"]});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()}); export const appDefinitionsSchema=z.array(appDefinitionSchema).superRefine((v,c)=>{const s=new Set();v.forEach((a,i)=>{if(s.has(a.slug))c.addIssue({code:"custom",message:"Duplicate slug",path:[i,"slug"]});s.add(a.slug)})}); diff --git a/packages/skills-catalog/catalog/optional/software-development/prepare-mcp-integration/SKILL.md b/packages/skills-catalog/catalog/optional/software-development/prepare-mcp-integration/SKILL.md index a7ba10b3e02..231ef11249f 100644 --- a/packages/skills-catalog/catalog/optional/software-development/prepare-mcp-integration/SKILL.md +++ b/packages/skills-catalog/catalog/optional/software-development/prepare-mcp-integration/SKILL.md @@ -49,6 +49,26 @@ App only after a human accepts the exact research revision and connection set. briefs, catalog files, issue text, plans, fixtures, screenshots, logs, branch names, commits, or PRs. +## Default Connection Experience + +- Request the broadest vendor permissions and scopes the connection can + support by default. Operators should not have to predict every future tool + they may need during setup. Enforce safe use after connection through + Paperclip's action catalog, resource boundaries, ask-first policies, + quarantine, and audit controls. +- Keep the default wizard limited to the minimum information needed to create + a working connection: connection identity, authentication, and any + unavoidable tenant or resource boundary. Put optional scope reduction, + feature groups, individual tool filters, response modes, transport tuning, + and other expert controls behind one collapsed **Advanced** disclosure. +- Give advanced controls working broad defaults so an operator can finish + setup without opening them. When a provider truly requires an explicit + advanced choice, document the exception and explain it in plain language + instead of exposing protocol details by default. +- Treat vendor permission breadth and Paperclip execution governance as + separate layers. Do not reduce requested vendor permissions merely to stand + in for missing action review, approval, quarantine, or audit policy. + ## Preflight 1. Load the current Paperclip skill for checkout, comments, interactions, diff --git a/packages/skills-catalog/generated/catalog.json b/packages/skills-catalog/generated/catalog.json index d126606ea16..3913615c549 100644 --- a/packages/skills-catalog/generated/catalog.json +++ b/packages/skills-catalog/generated/catalog.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "packageName": "@paperclipai/skills-catalog", "packageVersion": "0.3.1", - "generatedAt": "2026-08-07T20:30:29.973Z", + "generatedAt": "2026-08-11T15:05:34.526Z", "skills": [ { "id": "paperclipai:bundled:docs:doc-maintenance", @@ -1149,8 +1149,8 @@ { "path": "SKILL.md", "kind": "skill", - "sizeBytes": 11183, - "sha256": "452386be3aa5e2988dc9d72f288f469975da766626a25c48017877963c533781" + "sizeBytes": 12409, + "sha256": "5278d288a0cbfe10fdda4ef3d99cf61e6ed068f4b6c1282406b0e001c9f45253" }, { "path": "examples/notion-mcp-research-gate.md", @@ -1159,7 +1159,7 @@ "sha256": "944b13ac67c06c65643eb236e856f1479106749861535648d29334f84cf1b50b" } ], - "contentHash": "sha256:4dbb76624e0fd5502fa10a9168f77214c5020899838af887e77a1617a0a00c31" + "contentHash": "sha256:84f2a28d7d004393719684f2b435e5ee01ddc58591e0dfeb73d72c7605db7f49" } ] } diff --git a/scripts/ingest-app-definitions.mjs b/scripts/ingest-app-definitions.mjs index 7c085ee1deb..80871430175 100644 --- a/scripts/ingest-app-definitions.mjs +++ b/scripts/ingest-app-definitions.mjs @@ -5,12 +5,12 @@ const field=(key,label,placeholder)=>({key,label,type:"password",required:true,p 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 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:true,helperMd:"Keep enabled unless agents explicitly need to change PostHog data.",transport:{location:"query",name:"readonly",format:"boolean",omitFalse:true}}, - {key:"features",label:"Feature groups",type:"textarea",placeholder:"insights, error_tracking, flags, experiments",defaultValue:"insights,error_tracking,flags,experiments",helperMd:"Comma-separated PostHog feature groups to expose.",validation:{maxLength:500},transport:{location:"query",name:"features",format:"csv"}}, - {key:"tools",label:"Individual tools",type:"textarea",placeholder:"Optional comma-separated tool names",helperMd:"Optional exact tool names. These are combined with feature groups.",validation:{maxLength:2000},transport:{location:"query",name:"tools",format:"csv"}}, - {key:"mode",label:"Tool response mode",type:"select",required:true,placeholder:"Individual tools",defaultValue:"tools",options:[{value:"tools",label:"Individual tools"}],helperMd:"CLI mode remains unavailable until nested execution is governed.",transport:{location:"query",name:"mode"}}, + {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:"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"}}, ]; -const posthogMethod=(key,auth,extra={})=>method(key,"mcp_remote",auth,{serverUrl:"https://mcp.posthog.com/mcp"},"S3","Pin the connection to one PostHog project, keep read-only mode enabled by default, and expose only the feature groups or tools agents need.",{tenantFields:posthogConfigFields(),configRequirements:{atLeastOneOf:["features","tools"]},requiredResourceFilters:["project","read_only","features","tools","mode"],...extra}); +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 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 "}})], ["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"]})], diff --git a/server/src/__tests__/tool-access-service.test.ts b/server/src/__tests__/tool-access-service.test.ts index 80386172179..45a9419ed10 100644 --- a/server/src/__tests__/tool-access-service.test.ts +++ b/server/src/__tests__/tool-access-service.test.ts @@ -7172,6 +7172,20 @@ describe("normalizeConnectionMethodConfig", () => { const posthog = getConnectableAppDefinition("posthog")!; const apiKeyMethod = posthog.methods.find((method) => method.key === "mcp-api-key")!; + it("uses the broad PostHog catalog when optional advanced filters are untouched", () => { + expect(normalizeConnectionMethodConfig(apiKeyMethod, { + projectId: "12345", + })).toEqual({ + values: { + projectId: "12345", + readOnly: false, + mode: "tools", + }, + url: "https://mcp.posthog.com/mcp?mode=tools", + headers: { "x-posthog-project-id": "12345" }, + }); + }); + it("normalizes and projects PostHog scope without accepting arbitrary config", () => { expect(normalizeConnectionMethodConfig(apiKeyMethod, { projectId: "12345", diff --git a/ui/src/pages/apps/AppsConnect.test.tsx b/ui/src/pages/apps/AppsConnect.test.tsx index e8781750b59..dec18e332f8 100644 --- a/ui/src/pages/apps/AppsConnect.test.tsx +++ b/ui/src/pages/apps/AppsConnect.test.tsx @@ -229,8 +229,25 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { const projectInput = container.querySelector('input[placeholder="12345"]'); const keyInput = container.querySelector('input[type="password"]'); + const advanced = buttonByText("Advanced"); expect(projectInput).toBeTruthy(); expect(keyInput).toBeTruthy(); + expect(container.querySelector('[role="switch"]')?.getAttribute("aria-checked")).toBe("false"); + expect(advanced?.getAttribute("aria-expanded")).toBe("false"); + expect(container.textContent).not.toContain("Feature groups"); + expect(container.textContent).not.toContain("Individual tools"); + expect(container.textContent).not.toContain("Tool response mode"); + + await act(async () => { + advanced?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + 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"); + await act(async () => { setInputValue(projectInput!, "12345"); setInputValue(keyInput!, "phx_test-key"); @@ -248,8 +265,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { credentialValues: { "credentials.authorization": "phx_test-key" }, configValues: { projectId: "12345", - readOnly: true, - features: "insights,error_tracking,flags,experiments", + readOnly: false, mode: "tools", }, applicationId: undefined, diff --git a/ui/src/pages/apps/AppsConnect.tsx b/ui/src/pages/apps/AppsConnect.tsx index 7a116d64f3c..c638f943c16 100644 --- a/ui/src/pages/apps/AppsConnect.tsx +++ b/ui/src/pages/apps/AppsConnect.tsx @@ -3,6 +3,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { ArrowUpRight, Check, + ChevronDown, ChevronRight, Copy, ClipboardPaste, @@ -38,6 +39,7 @@ import { AgentIcon } from "@/components/AgentIconPicker"; import { AgentMultiSelect } from "@/components/AgentMultiSelect"; import { InlineBanner } from "@/components/InlineBanner"; import { Button } from "@/components/ui/button"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { ToggleSwitch } from "@/components/ui/toggle-switch"; @@ -1417,6 +1419,9 @@ 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 [advancedOpen, setAdvancedOpen] = useState(false); const configFilled = configFields.every((field) => { if (!field.required) return true; const value = configValues[field.key]; @@ -1537,7 +1542,7 @@ function KeyStep({ - {configFields.map((field) => ( + {standardConfigFields.map((field) => ( ))} + {advancedConfigFields.length > 0 && ( + + + + Advanced + + +
+ {advancedConfigFields.map((field) => ( + onConfigChange({ ...configValues, [field.key]: value })} + /> + ))} +
+
+
+ )} + {method && fields.length === 0 ? (

{method.auth === "oauth"