docs/reference/mcp.md

MCP Server

Vocion ships a Model Context Protocol server so you can author context, run skills, and inspect the review queue from any MCP client — Claude Code, Claude Desktop, Cursor, Zed, Continue. This is the first adapter of the Phase 2 universal interface layer.

Auto-apply is on by default. Every write_* tool writes files to workspace/<org>/, commits to git with a generated message, applies to the DB, and records a workspace_version audit row — all in one atomic call. Override per-call with autoApply: false or autoCommit: false.

Install for Claude Code

claude mcp add vocion -- npm --prefix /absolute/path/to/context-stack run mcp:serve

Or add to ~/.claude/mcp.json (or project-level .mcp.json):

{
  "mcpServers": {
    "vocion": {
      "command": "npm",
      "args": ["--prefix", "/absolute/path/to/context-stack", "run", "mcp:serve"],
      "env": {
        "VOCION_ORG_ID": "org_3B7f6cPKTKnJOExO55asDaUVAay",
        "WORKSPACE_PATH": "/absolute/path/to/context-stack/workspace/metacto"
      }
    }
  }
}

Required env:

  • VOCION_ORG_ID — org id the server acts on (or fall back to SEED_ORG_ID)
  • DATABASE_URL — Postgres connection string
  • OPENAI_API_KEY — required only for runtime_run_skill

Optional:

  • WORKSPACE_PATH — path to the workspace directory (default workspace/metacto)
  • WORKSPACE_AUTO_COMMIT=false — disable auto-git globally
  • WORKSPACE_AUTO_APPLY=false — disable auto-apply globally

Install for Claude Desktop / other clients

Point the client at tsx src/interfaces/mcp/bin.ts with the same env. Any stdio-capable MCP client works — the transport is standard.

Connect over HTTP (multi-tenant) ✓ live

The stdio server is single-tenant — one org per process, fixed by VOCION_ORG_ID. That's right for a developer's local IDE, wrong for a hosted runtime that many tenants or a fleet of agents connect to. The HTTP transport solves that: a single endpoint, multi-tenant, where the org is derived from a tenant Bearer token (vcn_live_… — the same credential the write API uses).

POST https://your-install/api/mcp
Authorization: Bearer vcn_live_...

Point any remote-capable MCP client (Claude, Cursor, Zed, …) at that URL with the Bearer header. The server instance is scoped to the token's org for the life of the request, and every tool call runs under the token's authz principal — the same permission model and review queue as a human. No per-org process, no shared secret: one endpoint, every tenant, isolated by token.

Issue, list, and revoke tokens from the CLI (v2.16):

npm run tokens:issue  -- --org <project-id-or-slug> --name "my integration"
npm run tokens:list   -- --org <project-id-or-slug>
npm run tokens:revoke -- --org <project-id-or-slug> --id <tokenId>

The plaintext is printed exactly once — only its SHA-256 hash is stored. Keep it out of any repo whose files deploy.

Transport is the standard Streamable HTTP (stateless, JSON responses) over Web Request/Response. Over HTTP the workspace-authoring tools (context_write/delete/apply) are disabled by default — HTTP is the runtime / data / search plane; authoring stays on stdio or CI where a per-org git workspace lives.

Tool reference

context_*

ToolPurpose
workspace_listEvery agent/skill/object_type (slug, name, description only)
workspace_getOne resource with full prompt text
workspace_write_skillCreate or update a skill. Writes skills/<slug>/{skill.yaml,prompt.md} → commits → applies
workspace_write_agentCreate or update an agent. Writes agents/<slug>.{yaml,system-prompt.md} → commits → applies
workspace_write_object_typeCreate or update an object type (schema + classification prompt)
workspace_deleteRemove files + apply (row deletion)
workspace_applyReconcile files → DB. Use after editing files outside MCP
workspace_diffDry-run apply. Shows created/updated/unchanged counts
workspace_version_historyRecent workspace_version audit rows — "who changed what, when"

runtime_*

ToolPurpose
runtime_run_skillExecute a skill with input. Returns run_id, output, Langfuse trace id
runtime_list_runsRecent skill runs, filterable by skill slug or status
runtime_get_runOne full skill run (not truncated)
runtime_approve_draftApprove a pending run
runtime_reject_draftReject a pending run (with optional reason)

Data access

ToolPurpose
objects_listList business object instances (filter by type slug)
objects_getOne object + type + document links
object_types_listDiscover valid type slugs
search_queryRun a retrieval query against indexed documents (pgvector + Postgres FTS hybrid). Returns ranked docs with snippets + links

Built-in tools

The general toolbelt agents get out of the box (see Tools), callable over MCP. Providers are pluggable via env.

ToolPurpose
web_searchLive web search — ranked titles, URLs, snippets
fetch_urlFetch one page; returns readable text/markdown
crawl_siteSame-origin BFS crawl; pages digest (capped)
generate_imageGenerate an image from a prompt; returns a saved artifact URL
run_codeEvaluate a math expression (builtin) or run code (sandbox provider)
create_artifactCreate a CSV, SVG chart, or doc; returns a URL

Agent domain tools (v2.16)

The domain tool registry — the tools your agents themselves run on — is served over MCP too. The same registry that feeds the in-process harness and the BYOA tool endpoint is the bridge's source, so source gates, grant gates, and per-agent tool exclusions apply identically: what a tool can reach over MCP is exactly what it could reach inside an agent run.

Bridged tools run as an agent. The default is VOCION_MCP_AGENT_SLUG, falling back to the org's workspace lead (project.leadAgentSlug); every bridged tool also accepts an optional agent_slug argument to run as another agent, re-resolved and re-gated at call time. An org with no resolvable agent simply serves the base surface above. Over HTTP the calls are attributed token:<id>; on stdio, mcp.

ToolPurpose
search_knowledgeHybrid retrieval over the agent's connected sources — "what was said". Relevance top-k; it cannot count
get_hubspot_deals / get_hubspot_contacts / get_hubspot_companiesTyped reads over the synced CRM mirror — exact total from COUNT(*), discoverable facets, explicit pagination. Built only for agents with a HubSpot source
get_zoom_transcriptVerbatim transcript of one cloud-recorded meeting by UUID or numeric id. Read-through cache: answers from the synced mirror when the transcript is already ingested (source: "cache"), else fetches live from Zoom and upserts it into the index (source: "live", upserted: created|updated). Zoom-sourced agents only
get_gmail_threadFull thread text (headers + bodies) by thread id or any message id in it. Same cache semantics with a TTL (max_age_minutes, default 15; force_refresh to bypass). Gmail-sourced agents only
freshen_sourceIncremental re-sync of a source or connector family before recency-sensitive work
lookup_objects, run_operation, learnings, briefings, run historyThe rest of the agent toolbelt, gated per agent
propose_actionPropose a connector write (gmail.send, hubspot.update). It never executes over MCP: the proposal lands PENDING in the review queue under an agent principal at working autonomy — a human approves before anything touches the outside world, no matter whose token is calling

Two notes: the six capability tools above already serve the ctx-independent registry tools under the same names, so they are not double-registered; and emit-only tools (request_human_review, recommend_action) stay off MCP, where their events would vanish silently — side-channel events from bridged calls are returned alongside the output instead.

Missions

Open-ended team work (see Missions).

ToolPurpose
mission_listList authored mission templates (starting teams)
mission_startStart a mission from a brief — template (missionSlug) or ad-hoc (team); plans + runs to completion or first approval gate
mission_list_runsList mission runs (optionally by status)
mission_get_runOne run with its plan (task graph), team, artifacts, status
mission_approveApprove a paused run (awaiting_review) and continue
mission_cancelCancel a run
mission_promoteDraft a reusable Workflow from a completed run

Example flows

Edit the Sales Assistant's system prompt

> Use workspace_get to show me agent sales-assistant, then update it to add a rule
> about always including pricing in discovery summaries. Commit it.

Claude Code will call workspace_get with {kind: 'agent', slug: 'sales-assistant'}, edit the systemPrompt field, then call workspace_write_agent — one commit, one apply, done.

Test a new skill on real data

> Create a skill called objection_summary that reads a discovery transcript
> and lists the top 3 objections the prospect raised. Then run it against
> the last discovery call in my objects.

Claude Code will:

  1. workspace_write_skill with the new manifest + prompt
  2. objects_list with type_slug: 'discovery_call' to find the latest
  3. runtime_run_skill with the transcript as input
  4. Show you the output and the run_id so you can approve/reject

Review pending drafts

> Show me pending skill runs, then approve the email drafts that look good.

runtime_list_runs → read → runtime_approve_draft per selection.

Auto-apply semantics

Every write goes through this flow atomically:

  1. Validate the manifest against the Zod schema. Failure aborts with a structured error — nothing touches disk.
  2. Write YAML + markdown to workspace/<org>/<kind>/<slug>/…
  3. Commit to git (if autoCommit enabled and there are changes). Commit message: context: <default summary or override>.
  4. Apply to the DB (if autoApply enabled). Writes a workspace_version row with the new git SHA, diff counts, and applied_by: 'mcp'.
  5. Return {written, commit, apply} in one response — the MCP caller sees the new SHA and diff counts without an extra round-trip.

Turn off either leg with autoCommit: false / autoApply: false per call, or globally with env vars. If git commit fails (hooks, no repo, etc.), the apply is skipped and the caller sees an error — we don't want files on disk that don't match the DB.

Audit trail

Every skill_run stamps workspace_sha with the SHA active at execution time. Every workspace_version records the SHA, per-resource counts, errors, and who applied. To answer "why did the Sales Assistant write that email on April 14?":

SELECT sr.output, sr.workspace_sha, sr.created_at, cv.applied_by
FROM skill_run sr
JOIN workspace_version cv ON cv.sha = sr.workspace_sha AND cv.org_id = sr.org_id
WHERE sr.id = <run_id>;

-- then: git show <sha> on the workspace repo for the exact prompts

Limitations (today)

  • stdio + HTTP transports. Both ship: stdio (single-tenant, local IDE) and Streamable HTTP (multi-tenant, Bearer-scoped — see above), with CLI token issuance (npm run tokens:issue, v2.16). The remaining piece is the OAuth authorization-code flow for clients that prefer interactive sign-in over a pre-issued token (claude.ai, Cursor Cloud); Bearer tokens work today.
  • No workflow resource yet. Workflows (triggers + steps + actions) come in Phase 3 with the conversational bootstrap.
  • Search is first-party — pgvector + Postgres FTS via RetrievalService.search. No third-party retrieval engine.

Extending

Since v2.16 the fastest path is the domain tool registry: add a tool in src/services/agents/tools/ and register it in registry.ts — every provider gets it (in-process harness, BYOA endpoint, catalog, and the MCP bridge), with your source/grant gating enforced everywhere at once.

For MCP-only tools (workspace authoring, runtime admin), add a module in src/interfaces/mcp/tools/<group>.ts:

export function myTool(config: McpConfig): ToolModule {
  return {
    name: 'my_tool',
    title: 'Short title',
    description: 'What it does, shown to the LLM',
    inputSchema: { arg: z.string() },
    handler: async (input) => { /* ... */ },
  };
}

Register it in src/interfaces/mcp/server.ts. Types flow end-to-end.