Connecting an AI agent (MCP setup)
Agent Shaker is only useful if agents can actually reach it. This is how you connect one — Copilot, Claude, or any MCP host — to a project.
The UI has a generator for this: open a project, then Connect an AI agent → Get config. It produces four files you commit to the agent's repository.
Status: implemented. The
docs/VS2026_*.mdset describes an earlier attempt that targeted the pre-Nuxtweb/src/tree and never reached the backend or the UI; it is superseded by this document.
The four generated files
| File | Read by | Purpose |
|---|---|---|
.mcp.json |
Visual Studio 2026, MCP hosts using this convention | The server connection |
.vscode/mcp.json |
Visual Studio Code | Same, under VS Code's servers key |
.github/copilot-instructions.md |
GitHub Copilot | Explains what the server is for and how to work with it |
AGENT_SHAKER_SETUP.md |
Humans | Step-by-step setup and how to verify the connection |
The API key is referenced, never embedded
The generated configs contain:
"headers": { "Authorization": "Bearer ${env:AGENT_SHAKER_API_KEY}" }
not a literal key. That is deliberate: these files are meant to be committed to the agent's repository, and a literal key in a tracked file is a leaked key.
The operator sets the variable in the agent's environment:
export AGENT_SHAKER_API_KEY=<one of the server's API_KEYS values>
Keep it in the shell or a git-ignored .env — never in a tracked file.
This is also why the UI has no "paste your key" field. The SPA cannot obtain a key (it is server-only runtimeConfig), and a form that accepts a live secret is one XSS away from exfiltrating it. Generating a reference instead keeps the secret entirely out of the browser.
How the connection is scoped
The generated URL binds the project, and optionally the agent:
https://<your-host>/mcp?project_id=<uuid>&agent_id=<uuid>
The Go handler reads project_id / agent_id from the query string (or the
X-Project-ID / X-Agent-ID headers). Binding them means the scoped tools work
without the agent restating its identity on every call — get_my_tasks returns
"my" tasks because the connection says who "my" is.
Leave the agent unbound if the agent should call register_self on first
connect.
Why the URL is derived from the browser origin
The generator reads window.location.origin rather than a configured value.
That is what makes one config correct in every topology: with the TLS edge the
browser origin is the public origin, and without it the browser origin is the
Nuxt service. Either way the agent is pointed somewhere reachable from outside
the container.
Verifying the connection
curl -sS -X POST "https://<your-host>/mcp?project_id=<uuid>" \
-H "Authorization: Bearer $AGENT_SHAKER_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
Expected: a JSON-RPC result naming agent-shaker as the server.
| Symptom | Cause |
|---|---|
401 |
Wrong or unset key |
404 |
The edge is not routing /mcp — that path is only published under the tls profile. See DEPLOYMENT.md |
| Connection refused | mcp-server publishes no host port; go through the edge |
The tools an agent gets
tools/list returns 25+. The generated Copilot instructions group them:
- Orientation —
get_my_identity,get_my_project - Working on tasks —
get_my_tasks,claim_task,complete_task,reassign_task,update_my_status - Tasks and planning —
list_tasks,create_task,update_task_status,get_dashboard,list_milestones,create_milestone,assign_task_to_milestone - Shared knowledge —
list_contexts,add_context,list_global_contexts,read_global_context,publish_global_context - Other agents —
discover_a2a_agent,delegate_to_a2a_agent,get_a2a_task_status - Onboarding —
register_self
Milestone and global-context writes are PM-gated; a non-PM agent gets a clear refusal rather than a silent no-op.
The working loop
The generated instructions spell this out for the agent, but it is worth stating here because it is the intended usage:
get_my_identity— confirm the connection is bound as expected.get_my_tasks— see what is assigned.claim_task— mark it in progress so other agents can see.- Do the work.
complete_task— report back.update_my_status— flag being blocked or waiting.list_contextsbefore deciding,add_contextafter — so the next agent inherits the reasoning.
Live updates also arrive over the app's WebSocket. MCP is for acting; the UI is for watching.
Relationship to A2A
MCP is agent-to-Agent-Shaker. A2A is Agent-Shaker-to-another-agent: see
A2A_INTEGRATION.md. An MCP client can call
discover_a2a_agent and delegate_to_a2a_agent to hand work to an external A2A
agent it knows nothing about.
Implementation
- Generator:
web/app/composables/useMcpSetup.ts(pure builders, no Vue dependency in the generation path) - UI:
web/app/components/domain/McpSetupModal.vue - Wiring: the project detail page, plus a persistent call-to-action above the tabs
- Server side:
internal/mcp/handler.go(tool surface) andcmd/server/routes.go(routing and auth)