loopback
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@loopbackwork the feedback queue for acme-demo"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Loopback
Pin feedback on your live app. Any coding agent fixes it. The pin turns green.
Loopback is the interactive feedback layer between real product usage and your coding agents: one script tag makes any web app commentable (Vercel-toolbar-style toolbar, element-anchored pins), every pin auto-captures the functional context — failing requests with response bodies, console trail, the route journey that led there, LLM run metadata, typed repro steps — and lands in one project-tagged queue that Claude Code, Codex, and Gemini CLI all work over MCP. When an agent's fix is verified, the pin turns green on the page, live.

Real screenshot: the contact form's backend bug was pinned, claimed by claude-code, fixed with a PR, verified — pin and badge are green. The wrong AI answer is still amber/open.
Why
Coding agents can fix anything you can describe — but the loop back from real usage is missing. You notice a broken flow, screenshot it, re-describe it in a prompt, paste console output, explain which project it belongs to. Every time, for every agent. Vercel's comments have no public API; Claude Design's anchored comments are artifact-scoped; error trackers don't know your queue.
Loopback is that missing loop, built as a hub:
One instance, all projects. Every item is tagged with a
projectslug in one shared SQLite DB (~/.loopback/loopback.db). Agents registered once per machine; consuming repos add only a widget tag and a slug.One queue, all agents. MCP is the interface, so Claude Code, Codex, and Gemini CLI are peers — same tools, same playbook, same audit trail.
A pin is an anchor, not a scope. Pin a "broken" contact form and the agent gets the failing
POSTwith its 500 response body — a frontend pin carries the backend root cause. Pin an AI answer and the run metadata (run_id,model,trace_url) rides along.
CAPTURE THE HUB AGENTS
┌───────────────────────────┐ ┌─────────────────────────────┐ ┌─────────────────────────┐
│ widget pin on any app │──►│ loopback-mcp-server │◄──│ Claude Code │
│ · console + network ride │ │ one shared SQLite DB │ │ Codex (peers) │
│ · 500 bodies captured │ │ ~/.loopback/loopback.db │ │ Gemini CLI │
│ · AI run context │ │ │ └────────────┬────────────┘
├───────────────────────────┤ │ stdio (per-agent spawn) │ │
│ POST /ingest │──►│ --http on 127.0.0.1:7077 │ list → claim → fix →
│ · CI hooks, cron, │ │ (required for widgets) │ link change → fixed →
│ Sentry/PostHog pollers │ │ │ verify → resolve
└───────────────────────────┘ └──────────────┬──────────────┘ │
│ │
pins turn green on the page ◄─────┴───── status write-back ◄──────┘Run it per-invocation over stdio (each agent spawns it; same DB = same
queue) or as one long-running --http service on 127.0.0.1:7077
(required for widgets — keep it alive with pm2/launchd/systemd:
integrations/keep-alive.md).
Related MCP server: Lens
Quickstart (see the whole loop in 2 minutes)
Requires Node ≥ 22.13 (built-in node:sqlite — zero native deps).
git clone https://github.com/joshidikshant/loopback && cd loopback
npm install # prepare script builds dist/
node dist/index.js --http # the hub, on 127.0.0.1:7077
node demo/serve.mjs # demo app on 127.0.0.1:5173 (broken backend + wrong AI answer)Open http://127.0.0.1:5173 → submit the form (it fails politely) → ✦ Loopback → Pin feedback on an element → click the submit button → Send. The form shows the captured failed request. Then tell any connected agent "work the feedback queue for acme-demo" — or watch the item at http://127.0.0.1:7077/queue and be the agent yourself over MCP. When it's resolved, the open page announces it and the pin goes green.
Install once per machine
Register the MCP server + instructions once per agent; after this, new projects
are a two-minute init. All three are equal citizens — full per-agent pages in
integrations/:
Agent | MCP registration | Instructions/skill channel |
Claude Code |
|
|
Codex |
| AGENTS.md read natively + native SKILL.md at |
Gemini CLI |
| AGENTS.md via |
All three also accept the long-running instance over streamable HTTP
(http://127.0.0.1:7077/mcp) instead of spawning — see the per-agent pages.
Integrate a new project (2 minutes)
Central instance running (once per machine):
loopback-mcp-server --http, kept alive per keep-alive.md.Paste the widget tag into the app, with your slug (template):
<script src="http://127.0.0.1:7077/widget.js" data-project="my-app" data-endpoint="http://127.0.0.1:7077"></script>From the repo root:
npx loopback-mcp-server init --project my-app --writeOne canonical playbook (integrations/instructions-src.md) is rendered into every agent's native mechanism: the AGENTS.md queue section (canonical; Codex + Gemini read it natively),
@AGENTS.mdimports in CLAUDE.md and GEMINI.md, the same SKILL.md installed for Claude (.claude/skills/) and Codex (.agents/skills/), MCP registration for all three (.mcp.json,.gemini/settings.json,.codex/config.toml), and a/loopbackGemini command. Merges are non-destructive and idempotent — re-run it anytime.In any of the three agents, say: "work the feedback queue for my-app" — or say nothing: the skill descriptions and AGENTS.md section make feedback-ish requests trigger the loop on their own.
Using it day to day
With the hub running (loopback-mcp-server --http), everything happens on two
surfaces and one sentence to an agent:
I want to… | Do this |
Report something on a page with the widget | Click ✦ Loopback → Pin feedback on an element, click the thing, describe it. Failing requests, console, and AI run context attach themselves. |
See the queue |
|
Read everything on one item | Click its id → |
Comment or change status myself | On the item view — plain forms, no agent needed |
Get it fixed | In the repo, tell any agent: "work the feedback queue for |
Watch it close | The pin on your page turns green and announces itself; the item shows the commit/PR |
File from a script or CI |
|
Writes that change an item (comment, status) are same-origin only — see Security below.
Giving feedback about Loopback
Loopback is its own reference integration — it eats its own dog food, and so can you. Four ways in, from most to least convenient:
Pin it on the queue page. With the hub running, open
http://127.0.0.1:7077/queue— the capture widget is embedded there withdata-project=loopback. Click ✦ Loopback → Pin feedback on an element, click whatever is wrong, describe it. Same loop as any other project.Tell an agent. In this repo (self-onboarded with its own
init), say "file feedback for loopback: <what's wrong>" — the skill and AGENTS.md section are already installed for Claude, Codex, and Gemini.curlit from anywhere:curl -X POST http://127.0.0.1:7077/ingest -H 'Content-Type: application/json' \ -d '{"project":"loopback","type":"ux","severity":"p2", "title":"…","body":"what happened / what you expected"}'GitHub issues for anything a stranger should see: https://github.com/joshidikshant/loopback/issues.
Then work it like any queue: "work the feedback queue for loopback". Every Loopback defect in this repo's history was filed and closed exactly this way.
The dashboard
/queue is a React + shadcn app (dashboard/, built with the real shadcn
CLI). Filter by clicking status tiles or any project / severity / type cell —
filters compose and live in the URL, so every view is linkable. Open an item to
read everything captured, edit it (title, body, severity, type — every change
lands on the audit trail), comment, change status, and attach files.
Attachments declare why they exist, because that decides what an agent does:
Intent | Meaning |
| Context for the fix — a screenshot, a spec, a "make it look like this". Read it, then leave it. It never ships. |
| A deliverable. The blob store is a transfer buffer: the item carries a |
Blobs live in ~/.loopback/blobs/<item>/, beside the DB rather than inside it,
and agents get an absolute local path so they copy the file instead of
decoding it out of the protocol.
The build output is committed to public/dashboard/, so
npx loopback-mcp-server still needs no React, no Tailwind and no build step —
the hub just serves files. npm run dashboard:build after changing
dashboard/src; dashboard-gate fails CI if the committed build has drifted.
Design system (shadcn-compatible, zero dependencies)
Both surfaces — widget and /queue — are built from one token set in vanilla
CSS that speaks shadcn/ui's contract (oklch variables, .dark, the
multiplicative radius scale). No React, no Tailwind, no build step; drop
design/tokens.css into any shadcn/v0 project and it themes from that
project's palette. Full rationale and the shadow-DOM isolation rules:
design/README.md.
Loopback also publishes a shadcn registry, so React projects can install its pieces and the shadcn MCP can discover them:
npx shadcn@latest add https://raw.githubusercontent.com/joshidikshant/loopback/main/public/r/loopback-theme.json
npx shadcn@latest add https://raw.githubusercontent.com/joshidikshant/loopback/main/public/r/loopback-widget.jsonloopback-theme adds the --lb-* feedback status/severity tokens to your
loopback-theme ships the FULL shadcn theme contract (background, foreground,
primary, muted, ring, radius…) alongside the --lb-* domain tokens, so
installing it REPLACES your palette. If you only want the Loopback-specific
tokens, copy the --lb-* block out of design/tokens.css instead.
free). loopback-widget drops the capture widget into public/. Register
"@loopback" in your components.json to install by name and let an agent
with the shadcn MCP browse the registry.
Where it works (surfaces)
The queue is transport-agnostic — the widget is just its richest producer. Full matrix, native snippets (Swift/Kotlin/C#/shell), and the honest edges: docs/05-surface-compatibility.md.
Surface | Status |
Web apps in local dev (any framework) · browser extensions (bundle the widget file — MV3 forbids remote scripts) · Electron/Tauri · WebViews | ✅ widget: pins + auto-context + green write-back |
Deployed public sites | ✖ Chrome 142+ blocks a public page from reaching |
Native macOS/Windows apps · CLIs · CI/cron · agents/automations | ✅ |
iOS/Android simulators & emulators | ✅ shared loopback / |
iOS/Android physical devices on LAN | ✅ |
iOS/Android production | ✅ via Sentry/PostHog rails (their SDKs capture; bridge to the queue) |
The widget
~46KB (15KB gzipped) of dependency-free vanilla JS in a shadow-DOM host — it never fights your app's CSS or framework.
Capture: pin mode highlights elements on hover; a click opens a viewport-clamped form (title / what happened / what you expected / type / severity). Type is pre-guessed:
backendwhen failed requests exist,usagewhen AI context is present.Functional context, always on: ring buffers from page load — last 30 console lines (log/warn/error + window errors + unhandled rejections), all fetch/XHR calls (url/method/status/ms), and for failures (status ≥ 400 or network error) up to 2KB of response body into
extra.failed_responses. Calls to Loopback itself are never recorded.AI/automation context: the nearest ancestor with
data-loopback-context='{"run_id":...}'is parsed intoextra.context.Selector + element: stable CSS selector (
#id/[data-testid]preferred,nth-of-typefallback, depth-capped) + outerHTML snippet + viewport + UA.SPA-aware: client-side route changes (
pushState/popstate) refresh pins immediately — no stale pins from the previous route.Live status pins: hydrate from
GET /feedbackon load and every 10s — amberopen, deeper ambertriaged, bluein_progress, pale greenfixed(an agent says it is done), full greenverified(confirmed against the running app), graywontfix. The two greens are deliberately different: the pin earning its full colour only atverifiedis the whole point, sofixedreads as provisional. Click a pin for id/status/assignee/PR.The loop closes visibly: when a status changes under an open page, the widget announces it — toast ("… open → verified by claude-code · PR linked"), pulsing pin, and a 🔔 tab-title flash if you're on another tab (adapted from make-pages-interactive's reload walkthrough, MIT).
Page API:
window.__loopback={ pins, refresh(), project, endpoint, version }(adapted from DOM-Review's__domReviewAPI, MIT) — used by the E2E suite, usable by any agent driving a browser.
The MCP bus — 10 tools
Tool | What it does |
| File an item: project, type |
| Filter (project/route/status/type/severity/source/assignee) + paginate ( |
| Full item: all context + linked change + comment trail |
| Atomic claim; a conflict names the holder; |
|
|
| Root-cause notes, questions, reasoning trail |
| Merge repo/branch/commit/pr_url/diff_summary onto the item |
| Close as |
| project × status counts |
Responses are markdown (default) or JSON via response_format, always with
structuredContent; long output truncates at 25k chars with guidance.
HTTP surface (--http, port 7077 / LOOPBACK_HTTP_PORT / --port)
Endpoint | Purpose |
| Stateless MCP streamable HTTP (fresh server per request; GET/DELETE → 405) |
| Plain-JSON submit — widgets, CI hooks, cron ingestors (201 + item; 400 with field-level issues) |
| List/filter (widget pin hydration) |
| Full item view — all captured context + comment/status actions |
| Human triage writes (same-origin only) |
| One item with its full trail, as JSON |
| The embeddable widget |
| Liveness |
Security: the trust boundary is not "localhost" — a loopback port is reachable from every page open in your browser. So:
CORS is granted to
POST /ingest,GET /feedbackandGET /widget.jsonly — the three things the widget genuinely needs from a foreign origin.Cross-origin reads get the pin projection (id, status, title, assignee, selector, PR link).
extra— captured response bodies, which routinely contain auth headers — is never served cross-origin.Everything that reads full context or changes state, including
/mcp, requires an origin pinned at startup from the bind config. Local tooling that sends noOrigin(curl, MCP clients) still works.URLs on items are rendered as links only when they are
http(s)— enforced bysafeHref()in the dashboard, which resolves the URL and reads its protocol.data:,vbscript:andfile:render as plain text.
A non-loopback bind requires a token. On 127.0.0.1 there is none — it
would protect nothing the OS does not already protect. The moment --host /
LOOPBACK_HOST widens the bind, the server generates one (or takes
LOOPBACK_TOKEN) and prints a ?token=… URL; the token is accepted once from
the query string, moved into an HttpOnly cookie, and stripped from the URL so
it does not persist in history or referrers. Tools send
Authorization: Bearer <token>. Comparison is constant-time over a digest, so
neither the value nor its length leaks through timing.
Three endpoints stay open on a LAN bind, deliberately:
Open | Why |
| The widget runs on a phone against an arbitrary host page and has nowhere to keep a secret. Intake is append-only and rate-limited (60/min per IP, 429 past that): a LAN caller can file noise, not read or change anything, and not fill the disk. |
| Anything embedded in a served script is readable by anyone who can fetch it. |
| The minimum needed to draw pins and show one turn green. A strict projection — no body, console, network, repro steps, comments or attachments — of what is already visible on the page. |
Everything else — the dashboard, full reads, every write, and /mcp, which
exposes all ten tools — refuses an unauthenticated caller with 401. The split
is asserted in scripts/e2e.mjs in both directions, and
npm run canary proves that assertion fails when the check is disabled.
This is a shared secret on a trusted network, not a substitute for real auth. Before exposing Loopback beyond a LAN, put it behind a reverse proxy.
Publishing to the MCP Registry
Loopback carries the identity the official MCP Registry uses to prove package ownership:
Where | Field | Value |
|
|
|
|
|
|
The registry fetches the published npm version metadata and requires
mcpName to equal server.json's name exactly; GitHub-based authentication
additionally requires the io.github.<username>/ prefix. server.json stays in
the repo and out of the npm tarball — the package only needs to carry mcpName.
Because npm versions are immutable, a mismatch costs a version number rather
than a retry. npm run smoke therefore asserts all six coupled fields (three
versions, the name pair, the npm identifier) and npm run canary proves that
assertion fails when they drift.
To release and submit:
npm publish --otp=<code> # 2FA code from your authenticator
mcp-publisher login github # opens a browser
mcp-publisher publish # reads server.jsonTests
npm run build
npm run smoke # real MCP client over stdio: 10 tools, full loop, atomic-claim conflict
npm run e2e # Playwright: pin capture → 500-body & run-context assertions → agent over MCP-HTTP → green pins
npm run init-gate # init renderings ×3 agents, byte-level idempotence, merge safety
npm run registry-gate # the published shadcn registry still resolves and installs
npm run dashboard-gate # the committed dashboard build matches dashboard/src
npm run impeccable-gate # design anti-patterns in the shipped UI (canary-verified)
npm run widget-token-gate # the widget's inlined tokens still equal design/tokens.css
npm run a11y-gate # contrast, target size, names, landmarks, motion — measured in a browserCI runs all of them on every push (LOOPBACK_E2E_CHROMIUM overrides the
browser binary if needed).
Several of these are built to resist passing for the wrong reason, because the
underlying tools fail open: a stale dashboard build serves happily with no error
anywhere, and impeccable detect exits 0 when it scans nothing. So
dashboard-gate rebuilds and compares rather than trusting mtimes, and
impeccable-gate scans a canary fixture that is required to trip before it
will believe a clean result.
a11y-gate and widget-token-gate exist because the two things they check —
accessibility and cross-surface token parity — regressed silently more than
once. The widget inlines its own copy of the tokens (it ships as one file and
cannot @import them) and had already drifted; components.css hardcoded a
near-white pin colour that broke the moment a status token went pale. Every gate
here has had both of its failure paths verified by deliberately breaking them.
Companions (borrow, don't rebuild)
Loopback is deliberately only the bus + capture layer. Pair it with the mature MCP-native pieces — the build-vs-borrow memo is the full analysis:
chrome-devtools-mcp / playwright-mcp — the agent sees and verifies the running app (the "verify" step of the loop)
Sentry MCP — production errors (incl. mobile) →
POST /ingestwithsource: "sentry"PostHog MCP — analytics/replays/surveys →
source: "posthog",replay_urlattached
Design decisions
The full history lives in docs/ (original spec, build-vs-borrow memo, interaction-layer analysis, technical path). Calls made in this build:
node:sqlite, never better-sqlite3 — native builds fail in clean environments; zero native deps is the feature.stdio + stateless streamable HTTP only, no SSE — the exact transport intersection of Claude Code, Codex, and Gemini CLI (SSE is deprecated in Claude Code and absent in Codex).
AGENTS.md is canonical — Codex and Gemini read it natively; Claude imports it via
@AGENTS.md(imports beat symlinks for Windows safety). One playbook source renders into every native mechanism; no agent is "the default".Codex gets project-scoped
.codex/config.toml— verified supported (loads once you trust the repo);initalso prints the global block.initwrites the local checkout's absolute path when stable,npx github:otherwise — fast startup for clones, zero-setup portability for npx runs./ingestaccepts unknown extra fields (no.strict()) — older hubs must not reject newer widgets; forward compatibility beats strictness at the ingestion boundary.Widget is ~46KB / 15KB gzipped, not the ~10KB sketch — ring buffers, failure-body capture, live pins, and the walkthrough earn their bytes; still zero deps, one file.
Marker-based merges —
initre-runs are byte-idempotent; files a human has taken over (generated marker removed) are left untouched.Interaction patterns adapted with attribution (MIT): make-pages-interactive's visible-closure walkthrough; DOM-Review's page API.
Repo map
src/ the bus: server (10 tools) · store (node:sqlite) · http · init
widget/ loopback-widget.js — the embeddable capture layer
dashboard/ React + shadcn source for /queue (built output in public/dashboard)
demo/ intentionally broken playground app
design/ shared tokens.css — one design system for widget, dashboard, registry
skills/ canonical loopback SKILL.md (installed for Claude + Codex)
integrations/ canonical playbook + per-agent setup + widget embed + keep-alive
plugin/ Claude Code plugin (skill + MCP registration); repo doubles as its marketplace
scripts/ e2e.mjs · the six gates · screenshot.mjs
docs/ the decision history (spec → memo → paths → technical path)MIT © Dikshant Joshi
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceHuman-to-AI code review bridge. Annotate UI elements in the browser with review comments, and AI agents read the feedback via MCP to fix code automatically — with full element context (CSS selector, styles, DOM path, accessibility info). 10 MCP tools, framework-agnostic Web Component, zero-config install via uvx.Last updated9
- Alicense-qualityDmaintenanceMCP server for visual feedback, video direction, and QA assertions on web pages, enabling AI agents to read, reply, and resolve annotations in real time.Last updated4MIT
- AlicenseAqualityCmaintenanceVisual feedback as agent work packets: stakeholders pin on your live app and your AI coding agent reads each pin (selector, screenshot, DOM, thread, acceptance criteria) via MCP and ships the fix.Last updated396701MIT
- Flicense-qualityAmaintenanceEnables UI feedback loop by clicking elements, leaving comments, and letting AI coding agents (via MCP) resolve annotations interactively.Last updated2
Related MCP Connectors
Live browser debugging for AI assistants — DOM, console, network via MCP.
Give AI coding agents access to your Vynix visual feedback, bug reports, and AI diagnosis.
A flock of AI users tests your deployed app and reports where real people get stuck, with fixes.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/joshidikshant/loopback'
If you have feedback or need assistance with the MCP directory API, please join our Discord server