feat(apps): simplify PostHog connection setup

Default PostHog to broad tool access, move optional filters and response mode behind a reusable Advanced disclosure, and codify the connection UX rule in the bundled integration skill and playbook.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-20 21:20:01 +00:00
1 parent cbd6c2b261
commit 860eb96c1e
12 files changed
+139 -65

No files matched your search

+12 -1
View File
@@ -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.
+14 -11
View File
@@ -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).
+1 -1
View File
@@ -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()}});
});
@@ -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": [
+1 -1
View File
@@ -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<Record<ToolConnectionOwnership,boolean>> }
@@ -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<string>();v.forEach((a,i)=>{if(s.has(a.slug))c.addIssue({code:"custom",message:"Duplicate slug",path:[i,"slug"]});s.add(a.slug)})});
@@ -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,
@@ -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"
}
]
}
+5 -5
View File
@@ -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"]})],
@@ -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",
+18 -2
View File
@@ -229,8 +229,25 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => {
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(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,
+27 -1
View File
@@ -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({
<ConnectionNameField name={name} onNameChange={onNameChange} />
{configFields.map((field) => (
{standardConfigFields.map((field) => (
<MethodConfigField
key={field.key}
field={field}
@@ -1546,6 +1551,27 @@ function KeyStep({
/>
))}
{advancedConfigFields.length > 0 && (
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<CollapsibleTrigger className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground">
<ChevronDown className={cn("h-4 w-4 transition-transform", advancedOpen && "rotate-180")} />
Advanced
</CollapsibleTrigger>
<CollapsibleContent className="pt-4">
<div className="space-y-6">
{advancedConfigFields.map((field) => (
<MethodConfigField
key={field.key}
field={field}
value={configValues[field.key]}
onChange={(value) => onConfigChange({ ...configValues, [field.key]: value })}
/>
))}
</div>
</CollapsibleContent>
</Collapsible>
)}
{method && fields.length === 0 ? (
<p className="text-sm text-muted-foreground">
{method.auth === "oauth"