Connects Cursor to Pincushion's visual feedback system, where stakeholders drop annotation pins directly on live web pages via Chrome extension. Exposes eight MCP tools including get_actionable_pins, search_annotations, claim_pin, and fix_and_resolve. Ships with four slash commands that let you view all open pins, filter by mentions, resolve issues, and generate priority summaries. Useful when you're working with non-technical stakeholders who review staging or production sites and need a tighter loop than screenshots in Slack. The plugin handles MCP setup automatically, or you can configure it manually with npx and your project directory.
The implementation-context layer for AI-native development. Stakeholders drop visual pins on any page of your live app; your AI coding agent reads each pin through MCP and ships the fix — in Claude Code, Cursor, VS Code, Windsurf, or any MCP client.
A pin isn't a feedback item — it's an agent work packet. Each one carries everything an agent needs to implement the change without a back-and-forth:
The loop closes itself: a stakeholder pins it → your agent reads it via MCP and fixes it in your IDE → the resolve records the commit, branch, and PR → an optional post-deploy critique verifies the fix actually landed.
This server is also how Pincushion AI runs design/copy/a11y critiques on a live page and writes the pins straight back onto it.
# npm
npm install -g pincushion-mcp
# pnpm
pnpm add -g pincushion-mcp
# yarn
yarn global add pincushion-mcp
Or run directly without installing:
# npm
npx pincushion-mcp --project-dir .
# pnpm
pnpm dlx pincushion-mcp --project-dir .
# yarn
yarn dlx pincushion-mcp --project-dir .
Download the Pincushion Chrome extension from pincushion.io/install/chrome.
Pick your AI agent below and follow the configuration for your setup.
Once configured, your agent can:
get_feedback_summarysearch_annotationsfix_and_resolveFile: .cursor/mcp.json
{
"mcpServers": {
"pincushion": {
"command": "npx",
"args": ["pincushion-mcp", "--project-dir", "."]
}
}
}
pnpm / yarn users: replace
"command": "npx"with"command": "pnpm"and add"dlx"as the first arg, or use"command": "yarn"with"dlx"likewise.
With Supabase sync:
{
"mcpServers": {
"pincushion": {
"command": "npx",
"args": [
"pincushion-mcp",
"--project-dir", ".",
"--sync-url", "https://your-supabase.com/api",
"--api-key", "YOUR_API_KEY"
]
}
}
}
File: ~/.config/Claude/claude_desktop_config.json (Linux/Windows)
or ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
{
"mcpServers": {
"pincushion": {
"command": "npx",
"args": ["pincushion-mcp", "--project-dir", "/path/to/your/project"]
}
}
}
pnpm users:
{
"mcpServers": {
"pincushion": {
"command": "pnpm",
"args": ["dlx", "pincushion-mcp", "--project-dir", "/path/to/your/project"]
}
}
}
yarn users:
{
"mcpServers": {
"pincushion": {
"command": "yarn",
"args": ["dlx", "pincushion-mcp", "--project-dir", "/path/to/your/project"]
}
}
}
With Supabase sync:
{
"mcpServers": {
"pincushion": {
"command": "npx",
"args": [
"pincushion-mcp",
"--project-dir", "/path/to/your/project",
"--sync-url", "https://your-supabase.com/api",
"--api-key", "YOUR_API_KEY"
]
}
}
}
Run this command to add Pincushion to Claude Code:
claude mcp add pincushion -- npx pincushion-mcp --project-dir .
Or with Supabase sync:
claude mcp add pincushion -- npx pincushion-mcp --project-dir . --sync-url https://your-supabase.com/api --api-key YOUR_API_KEY
File: .vscode/settings.json
{
"mcp.servers": {
"pincushion": {
"command": "npx",
"args": ["pincushion-mcp", "--project-dir", "${workspaceFolder}"]
}
}
}
File: ~/.windsurf/mcp.json or ~/.config/windsurf/mcp.json
{
"mcpServers": {
"pincushion": {
"command": "npx",
"args": ["pincushion-mcp", "--project-dir", "."]
}
}
}
File: ~/.antigravity/mcp.json
{
"mcpServers": {
"pincushion": {
"command": "npx",
"args": ["pincushion-mcp", "--project-dir", "."]
}
}
}
For tools that don't support MCP directly, use the REST API wrapper:
npx pincushion-mcp --rest --port 3456
This starts an HTTP server on localhost:3456. Endpoints:
GET /health — Check server statusPOST /call-tool — Invoke a tool
{ "toolName": "get_feedback_summary", "args": {} }Example using curl:
curl -X POST http://localhost:3456/call-tool \
-H "Content-Type: application/json" \
-d '{"toolName": "get_feedback_summary", "args": {}}'
npx pincushion-mcp [flags]
| Flag | Description | Default |
|---|---|---|
--project-dir PATH | Root directory containing .feedback/ | Current working directory |
--sync-url URL | Supabase API endpoint for remote sync | None (local only) |
--api-key KEY | API key for Supabase authentication | None |
--license-key KEY | Pro license key (optional) | None |
--rest | Enable REST API mode | Disabled (uses MCP/stdio) |
--port PORT | Port for REST API server | 3456 |
Local project:
npx pincushion-mcp --project-dir /path/to/project
With Supabase sync:
npx pincushion-mcp \
--project-dir /path/to/project \
--sync-url https://abcd1234.supabase.co/api \
--api-key sb_project_key_abc123...
REST API server:
npx pincushion-mcp --rest --port 8080
get_annotationsRetrieve annotations from .feedback/. Filter by page, component, or status.
Parameters:
pageUrl (string, optional) — Filter by page URL (partial match)componentName (string, optional) — Filter by LWC component namestatus (string, optional) — Filter by open, in-progress, or resolvedExample:
await mcp.callTool('get_annotations', {
componentName: 'wmlHomePage',
status: 'open'
});
search_annotationsFull-text search across all annotations, comments, selectors, and tags.
Parameters:
query (string, required) — Search termExample:
await mcp.callTool('search_annotations', {
query: 'button label'
});
get_feedback_summaryHigh-level rollup of all feedback: counts by status, priority, page, and component.
Example:
await mcp.callTool('get_feedback_summary', {});
get_component_feedbackGet all feedback for a specific LWC component with a plain-language summary.
Parameters:
componentName (string, required) — LWC component nameExample:
await mcp.callTool('get_component_feedback', {
componentName: 'wmlHomePage'
});
resolve_annotationMark an annotation as resolved after fixing the issue.
Parameters:
annotationId (string, required) — Annotation IDcomment (string, optional) — Resolution messageresolvedBy (string, optional) — Name to attribute resolution (default: "AI Agent")Example:
await mcp.callTool('resolve_annotation', {
annotationId: 'ann_abc123',
comment: 'Updated button label in line 42 of wmlHomePage.js'
});
add_agent_replyAdd a reply to an annotation thread (e.g., ask clarifying questions).
Parameters:
annotationId (string, required) — Annotation IDbody (string, required) — Reply messageauthor (string, optional) — Author name (default: "AI Agent")Example:
await mcp.callTool('add_agent_reply', {
annotationId: 'ann_abc123',
body: 'Is this button in the main navigation or sidebar?'
});
fix_and_resolveCombine fixing code and marking an annotation as resolved in one call. Optionally records commit / branch / PR metadata so the dashboard can backlink to what shipped.
Parameters:
annotationId (string, required) — Annotation IDfixDescription (string, required) — Description of the fixfilePath (string, optional) — File where fix was appliedlineNumber (number, optional) — Line number of the fixcommitSha (string, optional) — Commit SHA that landed the changebranchName (string, optional) — Branch the commit was made onprUrl (string, optional) — Pull request URL (GitHub/GitLab/Bitbucket; shape-validated)Example:
await mcp.callTool('fix_and_resolve', {
annotationId: 'ann_abc123',
fixDescription: 'Updated button label to match design spec',
filePath: 'src/components/wmlHomePage.js',
lineNumber: 42,
commitSha: 'abc123def456',
branchName: 'pincushion/checkout-fix',
prUrl: 'https://github.com/acme/app/pull/142'
});
get_implementation_packetFetch a single implementation packet for one page URL — selector list, full pin payloads, suggested branch name, and traceability config. Use when an agent wants to batch-fix one page in a single branch.
await mcp.callTool('get_implementation_packet', { pageUrl: '/checkout' });
assign_pin_to_agentDispatch a pin straight to your local coding agent. Promotes the pin to ready if not already, marks pending_implementation, and writes a .feedback/.agent-queue/<id>.json trigger file that agent-loop.mjs picks up and shells out to Cursor / Claude Code / Codex.
await mcp.callTool('assign_pin_to_agent', { annotationId: 'ann_abc123' });
link_pin_deployAttach a deploy URL to a resolved pin. Typically called by the deploy-hook edge function once production includes the fix, but available manually too.
await mcp.callTool('link_pin_deploy', {
annotationId: 'ann_abc123',
deployUrl: 'https://acme-app.vercel.app'
});
record_pin_verificationWrite Pincushion AI's post-deploy verdict back to the pin. Called by the critic agent after /critique-latest-deploy runs against a fresh deploy.
await mcp.callTool('record_pin_verification', {
annotationId: 'ann_abc123',
status: 'verified', // or 'regressed' or 'inconclusive'
notes: 'Button matches the primary token. No regression on adjacent CTAs.'
});
get_time_to_fix_metricsPro/Team feature — Free callers get sample size + upgrade hint. Median + p25/p75 of pin-to-resolve duration, with a 5-pin minimum so the metric is never noise.
await mcp.callTool('get_time_to_fix_metrics', { scope: 'project', projectId: 'pc_proj_abc' });
// → { sampleSize, thresholdMet, median, p25, p75, medianHuman, ... }
get_setup_instructions (NEW)Get setup and configuration instructions for all supported agents.
Example:
await mcp.callTool('get_setup_instructions', {});
Pincushion can notify Slack or Microsoft Teams through project-scoped incoming webhooks. The defaults are intentionally quiet and Figma-inspired: notify when a pin is ready for implementation, when someone is @mentioned, and when a collaborator adds follow-up on work already being handled. Every newly dropped pin and every resolution are opt-in events.
Recommended use cases:
pin_ready and follow_upmention and optionally resolvedpageUrlPatterns plus pin_ready, follow_up, and resolvedExample:
await mcp.callTool('configure_collaboration_integration', {
projectId: 'my-project',
provider: 'slack',
webhookUrl: 'https://hooks.slack.com/services/...',
targetLabel: '#product-feedback',
events: ['pin_ready', 'mention', 'follow_up'],
pageUrlPatterns: ['staging.example.com/checkout'],
sendTest: true
});
For Slack, use create_slack_install_link when the hosted Slack app secrets are configured. It returns an Add-to-Slack URL; after approval, Slack returns the incoming webhook and Pincushion stores it automatically.
Use list_collaboration_integrations to audit configured destinations, remove_collaboration_integration to disconnect one, and preview_collaboration_notification to see the payload shape before adding a real webhook. Webhook URLs are stored server-side and returned only as masked values.
For agents that don't watch the file system (Claude Code, Cursor, generic),
agent-loop.mjs polls .feedback/.agent-queue/ and dispatches new pins
to the configured agent automatically.
# from inside the pincushion-mcp directory
npm run agent-loop -- --project-dir /path/to/your/project
# or directly
node agent-loop.mjs --project-dir /path/to/your/project [--agent claude-code|cursor|generic] [--interval 3000]
The bridge (server.js) writes one trigger file per approved pin into
.feedback/.agent-queue/. The loop reads them, builds a prompt with
the pin's thread + element selector, and shells out to the chosen agent.
The agent uses MCP tools (claim_pin → fix → fix_and_resolve) and
the queue file is removed when the pin closes.
detectAgent() auto-detects claude or cursor on the PATH; falls
back to generic (writes the prompt to .feedback/.agent-prompt and
stdout). Run with --interval 3000 to control poll cadence.
The server reads annotations from .feedback/ in your project:
.feedback/
├── annotations/
│ ├── example-com-login.json
│ ├── example-com-dashboard.json
│ └── ...
└── index.json
Each annotation file contains:
{
"pageUrl": "https://example.com/login",
"pageTitle": "Login",
"annotations": [
{
"id": "ann_abc123",
"status": "open",
"priority": "high",
"tags": ["design", "accessibility"],
"createdAt": "2026-03-19T10:30:00Z",
"element": {
"lwcComponent": "wmlLoginForm",
"selector": ".login-button",
"textContent": "Sign In"
},
"thread": [
{
"author": "Design Team",
"timestamp": "2026-03-19T10:30:00Z",
"body": "Button label should say 'Sign In' not 'Login'",
"type": "comment"
}
]
}
]
}
To sync annotations with a remote Supabase database:
annotations table with columns matching the annotation schema--sync-url and --api-keyExample:
npx pincushion-mcp \
--project-dir . \
--sync-url https://your-project.supabase.co/rest/v1 \
--api-key sb_project_key_abc123...
The server merges local .feedback/ files with remote data, with remote taking precedence on newer updates.
Pincushion Pro includes additional features. Activate with --license-key:
npx pincushion-mcp --project-dir . --license-key YOUR_PRO_KEY
Make sure you have Node.js 18+ installed:
node --version
Install dependencies:
npm install @modelcontextprotocol/sdk
Check that .feedback/ exists in your project directory:
ls -la .feedback/
If it doesn't exist, create it and add some test annotations, or the extension will create it when you pin your first feedback.
Verify your credentials:
curl -H "x-api-key: YOUR_API_KEY" \
https://your-project.supabase.co/rest/v1/annotations
In your agent config, use the full path to pincushion-mcp:
which pincushion-mcp
# Use the output path in your config
Or use npx to let it find the package:
{
"command": "npx",
"args": ["pincushion-mcp", "--project-dir", "."]
}
Clone the repository and install dependencies:
git clone https://github.com/jcooley8/pincushion-plugin.git
cd pincushion-plugin
npm install
Run the server:
npm start
Or with test data:
npm start -- --project-dir ./test-feedback
MIT License. See LICENSE file for details.
.feedback/ file supportfix_and_resolve, get_setup_instructionsio.github.ericm1018/skillfm-llm-cost-optimizer-openai-anthropic-usage
io.github.mikerawsonnz/llm-orchestration-agent
io.github.mikerawsonnz/authenticated-llm-agent
labforgedev/copilot-memory-mcp
csoai-org/agent-prompt-injection-firewall-mcp
io.github.mikerawsonnz/authenticated-multi-llm-agent