
Security News
Ruby's Bundler 4.0.18 Extends Cooldown to bundle lock and bundle cache
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.
pincushion-mcp
Advanced tools
Visual feedback as agent work packets. Stakeholders drop pins on your live app; your AI coding agent reads each pin (selector, screenshot, DOM, thread, acceptance criteria) via MCP and ships the fix.
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.
npx pincushion-mcp setup
One command: signs you in, registers your app's URLs, writes the MCP config into your editor, and opens the browser extension. ~1 minute.
# 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/mcp.json
{
"servers": {
"pincushion": {
"command": "npx",
"args": ["pincushion-mcp", "--project-dir", "."]
}
}
}
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
Pincushion pins live on web pages — mobile-wall brings native iOS apps into the
loop by turning a booted simulator into a static, pinnable crit wall: one page,
every screen, with semantic hotspot overlays generated from the app's accessibility
hierarchy so pins anchor to real element labels (data-ax="Send message") instead
of raw pixels.
Headless (one command, CI-able):
npx pincushion-mcp mobile-wall run # first run scaffolds crit-wall/wall.plan.json
wall.plan.json declares the whole session: the app (bundleId, optional
appPath to install, optional boot device to cold-boot a simulator with no
Simulator.app window) and the screens, each with the maestro navigation that
reaches it — steps written as JSON ({"tapOn": "Get started"}; YAML is a JSON
superset, so they pass into the generated flow verbatim), or flow pointing at
a hand-authored maestro flow file. Re-running run boots/installs/launches,
drives every screen, captures, and builds the wall — no human at the simulator.
Generated flows persist under crit-wall/flows/ so a failing step can be
replayed with maestro test directly. maestro's JVM is resolved automatically
(/usr/libexec/java_home, then keg-only Homebrew JDKs) — headless shells and
CI don't need JAVA_HOME exported.
Manual (navigate the simulator yourself):
# 1. Boot your app in the iOS simulator, navigate to a screen, then:
npx pincushion-mcp mobile-wall capture 01-first-launch
npx pincushion-mcp mobile-wall capture 02-connect # …repeat per screen
# 2. Generate the wall:
npx pincushion-mcp mobile-wall build --name "My App"
Publish after the app PR merges:
PINCUSHION_LICENSE_KEY=... npx pincushion-mcp mobile-wall publish \
--snapshot crit-wall-snapshot.jpg \
--deploy-command "vercel deploy --prod" \
--resolve-implemented \
--verify verified
publish is the no-plugin maintenance path for public/native critiques. It
reruns the headless capture plan by default, rebuilds wall/, optionally deploys
the static wall, fetches cloud pins for projectId + pageUrl, recomputes public
report marker positions from snapshot.rects + current AX hotspots, uploads the
latest JPEG/WebP page snapshot, and can mark implemented pins resolved with a
deploy URL and verification status. Use --no-run when CI already has fresh
captures and only needs to rebuild/upload. It writes
crit-wall/wall/publish-manifest.json and crit-wall/wall/publish-report.md for
the release artifact. Existing deployed walls are static HTML, so run
mobile-wall publish once after this version lands to republish their
index.html; after that, the wall canvas behavior is loaded from the centrally
hosted https://pincushion.io/widget/mobile-wall-canvas.js script and can be
updated by deploying Pincushion rather than regenerating every wall.
Both modes capture a screenshot (xcrun simctl io booted screenshot) plus the
accessibility hierarchy (maestro or idb, auto-detected on your PATH) into
crit-wall/screens/ + crit-wall/ax/. build emits crit-wall/wall/index.html —
a brandable gallery page (edit crit-wall/wall.config.json for the title, copy,
accent color, and per-screen captions, then rebuild) — and prints the next steps:
deploy the wall to any static host, register it with configure_project, and mint
a public crit link with create_share_report. Once the config carries the
projectId + deployed pageUrl, a rebuild turns on zero-install click-to-pin:
visitors drop pins straight on the screens with just a name + email. Pins dropped
on a hotspot prefix the comment with [Flow: ...] [Route: ...] [Build: ...] [Screen: ...] [Element: ...] when that metadata exists, and every
build also writes crit-wall/wall/agent-context.json with screen selectors,
image dimensions, and accessibility hotspot selectors. Builds also emit
crit-wall/wall/agent-work-packet.json and
crit-wall/wall/agent-work-packet.md: a reusable app-agent handoff with
headless capture provenance, wall.plan.json reproduction commands, native
source hints, screenshot-only warnings, and per-hotspot source search commands.
That packet is the handoff for a coding agent: use it to map critique back to
native UI labels instead of treating the report as screenshots.
For stronger native-source context, edit wall.config.json before rebuilding:
set agent.targetRepo, agent.sourceRoots, optional agent.implementationNotes,
and sourceHints.screens / sourceHints.labels entries for known SwiftUI/UIKit
files, routes, and screen identifiers. mobile-wall still derives useful rg
searches from accessibility labels by default, but explicit source hints make
pin-to-PR routing much less ambiguous for a future agent.
For social sharing, set ogImage in wall.config.json (a file inside wall/,
e.g. og.png). To make click-to-pin markers land exactly on the annotated share
report, run upload_page_snapshot and record the mapping in the config
(snapshot: { w, h, rects: { "<screen>": { x, y, w, h } } } — each exhibit
image's rect within the snapshot); screens without a rect fall back to
document-relative pin coordinates.
The OpenClaw iOS pilot exposed the important boundary: mobile-wall can make a
native-app critique actionable, but it should not pretend to fix the native app
by editing the wall. Use the wall PR to improve capture, pin context, snapshot
mapping, and reviewer ergonomics; use the app repo PR to change copy, buttons,
navigation, empty states, and other product behavior.
Options: --out <dir> (default crit-wall), --plan <file> (run),
--backend maestro|idb, --udid <udid> (default booted). Requires Xcode
command line tools; maestro is required for run navigation (idb works for
capture-only AX). Android (adb/uiautomator) is not supported yet.
| Tool | Plan | Summary |
|---|---|---|
add_agent_reply | Add a reply to an annotation thread (e.g. to ask a clarifying question or note a finding). | |
add_bot_reply | Post a Pincushion AI reply to a pin's thread. Hardcodes author="Pincushion AI" and… | |
add_member | Pro | Add a collaborator to a Pincushion project. Developers consume a paid seat and can implement… |
approve_pin | Mark a pin as approved for implementation. Only approved pins should be implemented by agents.… | |
assign_pin_to_agent | Assign a pin directly to your local coding agent. Promotes the pin to "ready" (if not already),… | |
claim_pending_slack_install | LEGACY FALLBACK. Since May 2026, Slack installs auto-link to a Pincushion license when the… | |
claim_pin | Claim an actionable pin before starting work on it. Transitions the pin from… | |
complete_critique_request | Mark a critique_queue request as completed after the critic subagent has run on its page URLs.… | |
configure_collaboration_integration | Connect a Pincushion project to Slack, Microsoft Teams, or Discord using an incoming webhook.… | |
configure_project | Register a Pincushion project and associate it with your app's URLs. Once registered, anyone… | |
create_agent_pin | File a pin as a THIRD-PARTY agent — the write half of the browser-agent → coding-agent handoff.… | |
create_critique_pin | Create a pin authored by Pincushion AI. ONLY call this from the pincushion-critic subagent or… | |
create_invite_link | Pro | Generate a Figma-style shareable invite URL for a project. The recipient opens it, enters their… |
create_share_report | Mint a public read-only crit report link (pincushion.io/r/) for a project: numbered pins… | |
create_slack_install_link | Generate an Add-to-Slack OAuth URL pre-bound to a project. Most users should prefer the public… | |
fix_and_resolve | Resolve a pin after applying a code fix. Transitions the pin directly to "resolved" status so it… | |
generate_critique_report | Canonical critique generation command. Uploads only the supplied real captures, then creates or… | |
get_actionable_pins | Get all pins waiting for developer attention. Returns three categories: (1) "auto-agent" — pins… | |
get_annotations | Retrieve annotation pins from the .feedback/ directory. Filter by page URL, LWC component name,… | |
get_component_feedback | Get all feedback pins targeting a specific LWC component, with a plain-language summary ready… | |
get_feedback_summary | Get a high-level rollup of all open feedback: counts by status, page, and component. Use this to… | |
get_implementation_packet | Get a single implementation packet for one page URL. Useful when you want to batch-fix one page… | |
get_pending_critiques | Used by /critique-latest-deploy. Lists pending critique requests queued by the deploy-hook for… | |
get_project_context | Read-only lookup of a project's context (name, URLs, brand context, autoCritique flag,… | |
get_reply_candidates | Used by /pincushion-replies. Returns pins where Pincushion AI should respond, with each… | |
get_selected_pins | Get pins that the developer has selected for implementation from the dashboard or PINS.md… | |
get_setup_instructions | Get instructions for setting up and connecting the Pincushion browser extension to this MCP… | |
get_time_to_fix_metrics | Pro* | Compute median + p25/p75 time-to-fix from resolved pins. Returns sample size + threshold flag so… |
implement_approved_pins | CALL THIS FIRST when approved pins exist. Returns all stakeholder-approved pins grouped into… | |
link_pin_deploy | Link a deploy URL to a resolved pin. Typically called by the deploy-hook edge function once a… | |
list_collaboration_integrations | List Slack and Microsoft Teams webhook subscriptions for a Pincushion project. Webhook URLs are… | |
list_members | Pro | List all members of a Pincushion project with their roles, plus seat usage info. |
preview_collaboration_notification | Preview the Slack, Teams, or Discord notification shape and recommended event routing before… | |
record_pin_verification | Record the outcome of Pincushion AI's post-deploy verification on a resolved pin. Called by the… | |
remove_collaboration_integration | Remove a Slack, Microsoft Teams, or Discord webhook subscription from a Pincushion project. | |
remove_member | Pro | Remove a collaborator from a Pincushion project. Frees up the seat if they were an editor. |
resolve_annotation | Mark an annotation as resolved after addressing the feedback. Optionally add a resolution… | |
resolve_quickstart_demo | Finish the quickstart demo loop after editing the demo copy. Confirms the change, returns the… | |
search_annotations | Full-text search across all annotation comments, selectors, component names, and tags. | |
set_slack_preferences | Read or update the caller's Slack DM preferences. Resolves the user via license_key → email,… | |
start_quickstart_demo | Start the 60-second Pincushion quickstart: returns one sample stakeholder feedback pin (element… | |
update_critique_context | Lightweight write-only path for the layered critique-context system. Use this from /setup and… | |
upload_page_snapshot | Upload a full-page screenshot that turns the public share report into an annotated page: viewers… |
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_instructionsFAQs
Visual feedback as agent work packets. Stakeholders drop pins on your live app; your AI coding agent reads each pin (selector, screenshot, DOM, thread, acceptance criteria) via MCP and ships the fix.
We found that pincushion-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.

Company News
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.