Skip to content

MCP server face

The Model Context Protocol (MCP) is the emerging standard that lets an agent host — Claude Desktop, Claude Code, an editor assistant — discover and call external tools. The synapse mcp command exposes the hub through it, so any MCP-compatible agent coordinates through Synapse with no Synapse-specific code: it adds one server entry and gains tools to claim work, send messages, hand off and declare tasks, and resources that read the shared board, state, and manifest as live context.

How it fits

The default transport is stdio. The optional authenticated Streamable HTTP profile uses a private TLS listener, provisioned subject-to-seat mappings and project-filtered actions. HTTP clients require their own acceptance evidence; stdio host compatibility does not imply cloud or browser support.

synapse mcp runs an MCP server over stdio that is itself a client of the hub — it opens one SynapseAgent connection and re-exposes the coordination verbs as MCP tools and resources. The hub itself never learns about MCP: the face is a separate adapter process, not a hub change, so the hub stays exactly as local-first and dependency-light as before.

This makes MCP an interoperability edge, not a replacement layer. MCP-compatible hosts such as Claude Code, Claude Desktop, Cursor, or other editor assistants still own their prompt/runtime/editor behavior; SYNAPSE only supplies shared coordination state through MCP tools and resources.

The MCP SDK is an optional extra — the core install keeps its single websockets dependency:

pip install 'synapse-channel[mcp]'

Running synapse mcp without the extra prints a one-line install hint and exits non-zero, so nothing fails silently.

For a bounded local walkthrough that places the MCP adapter beside the CLI and A2A surfaces, see the integration demo matrix.

Connect your agent

Install the adapter once. It needs no shell hook:

python -m pip install 'synapse-channel[mcp]'

The adapter registers on the hub under --name. An explicit name always wins. Without one, an agreeing SYN_PROJECT/SYN_IDENTITY pair supplies the exact identity; otherwise the git project becomes <project>/mcp. The command prints that resolution to stderr, leaving stdout exclusively for MCP frames. Give every concurrent host an explicit distinct name such as my-repo/codex or my-repo/claude.

Claude Code

For a separately versioned plugin that combines this MCP face with the claim-aware edit hook, see Claude Code plugin. The plugin installer preserves unrelated Claude settings and supports an owner-only --token-file path; synapse mcp --token-file PATH reads that token at process startup without putting its value in argv. --token and --token-file are mutually exclusive.

The shortest local-scope registration derives <git-project>/mcp:

claude mcp add synapse -- synapse mcp

For a multi-seat project, pin the host identity:

claude mcp add --scope local --transport stdio synapse \
  -- synapse mcp --name my-repo/claude
claude mcp get synapse

Use --scope project only when the team intends to commit a shared .mcp.json; Claude Code asks each user to approve a project-scoped server.

Codex CLI

codex mcp add synapse -- synapse mcp --name my-repo/codex
codex mcp list

Codex stores the stdio server in its MCP configuration. Use --env SYN_PROJECT=my-repo --env SYN_IDENTITY=my-repo/codex before the -- separator if the server also needs those environment values. The manual local stdio path was exercised with Codex CLI 0.156.0 on Linux. For a token-gated hub, add --token-file /path/to/owner-only-token to the synapse mcp command; Synapse reads that file before starting the stdio server. Use one active bridge process per identity.

The same local stdio server exposes the human app task queue with redacted task replies. Its private prompt and returned content remain in the owner-local queue.

OpenCode

Install a local stdio MCP entry together with the native fail-closed mutation plugin:

synapse adapters opencode install \
  --scope project \
  --project . \
  --identity my-repo/opencode

synapse adapters opencode status --scope project --project .

The adapter owns only the marked mcp.synapse object and marked plugin file, preserves unrelated strict-JSON configuration, refuses unowned collisions, and can uninstall its own assets without deleting user settings. A remote Synapse hub is still reached by this local stdio MCP process; pass --uri wss://… and an owner-only --token-file path rather than embedding a raw secret. See the OpenCode bridge for project/global paths, the participant and API connectors, remote attach behavior, native hook limits, and ACP IDE setup.

Cursor

Cursor reads project servers from .cursor/mcp.json and global servers from ~/.cursor/mcp.json. Copy the object from examples/mcp/.mcp.json, replace both YOUR_PROJECT/YOUR_CLIENT placeholders, and save the same JSON body at the Cursor path. Cursor lists the resulting tools under Available Tools.

Claude Desktop and generic stdio hosts

Merge the same mcpServers.synapse object into the host's MCP configuration. The transport-independent launch contract is:

{
  "command": "synapse",
  "args": ["mcp", "--name", "my-repo/desktop"]
}

The checked-in examples/mcp/.mcp.json template contains no secret. Add "--token-file", "/owner-only/path/to/token" to args, or provide SYNAPSE_TOKEN through the host's private environment, when the hub requires authentication. Never commit a raw token.

Which server the host starts

An MCP client trusts whatever process its configuration names. A server that reports itself as synapse and offers the same tools can answer claim granted while the hub holds no claim; nothing in the MCP handshake tells the two apart. Two defences follow from that:

  • The claim guard enforces, the MCP reply does not. The Claude Code and Codex claim guards (claim guard hooks) ask the hub itself before an edit, so a false grant is refused at edit time. Install the guard wherever the Synapse MCP server runs.
  • synapse doctor shows what the host is set up to start. The mcp-host-sources check lists every server whose name contains synapse in Claude Code's user and local scope (~/.claude.json, or $CLAUDE_CONFIG_DIR/.claude.json), the project's .mcp.json, the Synapse Claude plugin, and the Codex profile ($CODEX_HOME/config.toml). It warns about any that does not launch this installation's synapse mcp: a different command, a different synapse executable, a missing command, a modified plugin, or a file it cannot read. The mcp-claim-guard check warns when a host has such a server but no claim guard configured.

Both checks read configuration only. They do not prove what the host loaded, and servers named without synapse, a replaced synapse executable, and hosts other than Claude Code and Codex are outside them. Remote endpoints are listed but not opened; their TLS certificate and bearer grant authenticate them.

Tools

Each tool maps to one coordination verb and returns a short text result. Action tools wait for the hub's grant or denial; query tools return JSON.

Tool Effect
synapse_claim(task_id, paths?, task_only?) Take a work lease. Inside Git, an explicit file scope carries the same canonical worktree/path identity as synapse_git_claim, and a claim without paths covers the MCP process's whole worktree (the reply names it). Outside Git a claim with paths keeps the legacy shared namespace, and a claim without paths is refused. task_only=true locks the task id alone with no file scope, like synapse lock; it contends only with the same task and cannot be combined with paths.
synapse_git_claim(task_id, paths?, base?, auto_release_on?, whole_worktree?) Resolve the MCP process's real Git worktree, branch, Git-index spelling, filesystem aliases, case policy, and existing object identities, then take a mutation-compatible canonical claim. Bounded paths are mandatory unless whole_worktree=true is explicit.
synapse_release(task_id, evidence?, changed_files?, confidence?) Release a lease you hold and validate the hub-attested receipt; supplied evidence is persisted as an assessment note.
synapse_send(target, message) Send a chat to an agent, a group glob, or all.
synapse_inbox(limit?) Consume up to 1–100 messages for this identity as JSON, from the selected local feed or connected hub journal.
synapse_handoff(task_id, to_agent) Hand a held task to another online agent.
synapse_task_declare(task_id, title, depends_on?) Declare or refine a task on the plan.
synapse_task_update(task_id, status?, suggested_owner?) Update a plan task.
synapse_board() Return the shared task/progress board as JSON.
synapse_status() Return live roster, waiter, work, resource, and mailbox-pending counts as JSON.
synapse_state() Return the live claims and checkpoints as JSON.
synapse_manifest() Return the capability manifest of advertised agents as JSON.
synapse_directory() Return the discovery-only capability directory as JSON.
synapse_route_task(task_id, limit?, include_zero?, event_store?) Return advisory route recommendations for a board task as JSON.
synapse_resource_bids(task_id, resource_kind?, limit?, include_zero?) Return advisory resource bids for a board task as JSON.
synapse_memory_recall(event_store, query, limit?, since_seq?) Return deterministic local memory recall hits as JSON.
synapse_entitlements() Return a redacted local account-ledger overview with counts only; no labels, pool ids, balances or spend authority.
synapse_app_task_offer(bundle) Offer a human app task against a current private allowance window; return redacted state.
synapse_app_task_status(task_id) Read redacted state for one app task.
synapse_app_task_attach(task_id, result) Bind an untrusted result envelope to a task offered by this MCP identity without echoing content.

Accept, start, decline, cancel, verify and usage correction are owner-local synapse app-task CLI operations. The MCP process cannot mark a task verified.

When the hub does not answer within the request window the tool returns a clear "no response from the hub" line rather than hanging.

synapse_entitlements reads the owner-local account and quota ledger independently of the hub. An MCP client identity does not prove account-owner authority, so the tool always withholds private details. If the private store is unavailable or corrupt, it returns a generic unavailable state without disclosing its path or contents.

By default, synapse_inbox reads the adapter host's local durable relay file (default $SYN_HOME/feed.ndjson) through an owner-only per-identity cursor. It consumes only complete lines, pages without skipping a remaining tail, and reports available: false when the adapter cannot see that local file. For a custom local layout pass synapse mcp --inbox-feed PATH --inbox-cursor PATH. A remote MCP process does not pretend that a remote hub's file is locally available.

Inbox read failures return cannot read local relay feed; a missing file keeps the authored local relay feed is missing refusal. A failed cursor write returns cannot persist MCP inbox cursor; messages may repeat, with available: false, the original cursor and the collected messages. Repair the local storage and retry; those messages may repeat until their cursor is durably saved. The source field remains the configured feed path, an explicit local provenance field. Error text never adds system exception details or temporary cursor paths.

Unexpected store-read failures in synapse_memory_recall and the optional observed-evidence path of synapse_route_task return, respectively, cannot read memory recall event store and cannot read observed capability event store. The original authored missing event store: PATH refusal remains: MemoryRecallInputError and ObservedCapabilityInputError identify that deliberate input refusal, while the latter remains a ValueError for existing Python callers. Other storage, key-file and database exceptions stay in server logs, not tool results. The session remains usable and the tool can be retried after storage recovery. The MCP SDK's documented argument-validation responses remain in use for malformed tool arguments. HTTP grants still determine which tools are callable; these response changes grant no additional access to local files.

There is deliberately no MCP synapse_lock(command) tool. synapse lock owns a local child process; exposing that wrapper would turn an MCP call into arbitrary shell execution. Through MCP, use synapse_git_claim(task_id, paths) before a mutation guarded by the OpenCode/Codex/Kimi/Gemini hooks. The plain synapse_claim tool remains for coordination leases. A path-scoped call now attaches the resolved worktree and canonical path identity when the MCP process runs inside Git, so it cannot bypass an overlapping synapse_git_claim; it still carries no branch or auto-release policy and therefore does not replace synapse_git_claim for guarded mutation workflows. After verification, call synapse_release with bounded evidence and changed-file names. The host remains responsible for commands and file I/O; the MCP face never exposes arbitrary shell execution.

Wake and inbox pattern

Tool discovery is automatic after the host registers this server. Wake delivery is separate: this adapter exposes tools and resources, but it does not implement the vendor claude/channel extension, inject prompts into Codex or Cursor, or start a provider turn. An idle client therefore does not react merely because a message reached the hub.

Use the same honest loop in every host:

  1. At session/turn start, call synapse_status, then synapse_inbox until has_more is false.
  2. Keep a permanent receiver active for prompt delivery:
synapse arm install --identity my-repo/codex --start

Use the exact identity for that provider seat. On systems without Linux systemd, use the documented WSL or terminal wake bridge instead. 3. Treat the MCP server connection as tool availability, not as a waiter. synapse_status reports whether <identity>-rx is online and whether the durable hub has pending mailbox messages.

This split prevents a tool process from silently acknowledging provider work it never surfaced.

Resources

Four read-only resources let an agent pull live coordination context without issuing a tool call:

Resource Content
synapse://board The shared task/progress blackboard.
synapse://state Active claims and their resume checkpoints.
synapse://manifest The capability cards of advertised agents.
synapse://directory Discovery-only directory joining capability cards and resource offers.

Three read-only resource templates expose narrow dynamic views without adding new tools:

Resource template Content
synapse://task/{task_id} A single board task by id.
synapse://agent/{agent} One agent's capability card plus resource offers.
synapse://resource-kind/{kind} Resource offers matching one resource kind.

The directory is a marketplace-shaped discovery surface, not an executable marketplace. Its entries can help an agent host choose a likely worker or tool, but they do not reserve capacity, authorize execution, or certify trust. synapse_route_task uses the directory plus the shared board to rank likely agents with deterministic local signals. It returns the reasons behind each candidate. When event_store points at a local hub database, the tool also adds positive release-receipt evidence with source task ids and event sequences. The boundary stays the same: no claim, assignment, capacity reservation, permission grant, agent grade, or trust certification happens through the route.

synapse_resource_bids uses the same directory and board snapshots to rank live resource offers for a task. The JSON report includes resource id, provider, kind, name, capacity, score, and reason codes. It is a marketplace-style directory hint only: it does not reserve capacity, authorize execution, mutate tasks, or certify provider trust.

synapse_memory_recall is a local event-store reader. It projects findings, checkpoints, and handoffs into deterministic token matches and returns matched hits with source sequence, event kind, source field, task id, actor, evidence reference, and matched tokens. It does not create external embeddings, call an outside service, certify truth, or mutate hub state.

The dynamic resource templates are MCP v2-style read-only views over the same hub snapshots used by synapse_board, synapse_manifest, and synapse_state. They provide narrower context retrieval for hosts that support resource templates. They do not stream updates, chain tools, reserve resources, assign work, or change the hub protocol.

Official registry metadata

The repository ships server.json for io.github.anulum/synapse-channel. It follows the official 2025-12-11 schema, points at the PyPI package and stdio transport, and supplies a uvx --with mcp==1.30.0 runtime hint. The exact pin matches the package extra and prevents an unreviewed MCP-major upgrade. The synapse-channel console entry starts this MCP face directly for package launchers; humans can keep using synapse mcp.

The official MCP Registry is still a preview and its published versions are immutable. SYNAPSE CHANNEL is active there: version 0.99.23 was the latest public record when last re-verified; 0.99.24 is the in-tree preparation tip until the attested tag and registry query both return that exact version. Registry metadata does not automatically follow PyPI or this repository, so every release remains incomplete until the registry query returns the cut version. Check live publication rather than inferring it from local metadata. The official package rules require the PyPI description to carry the exact mcp-name marker, and the versioning rules make a published version immutable.

Release operators use the following fail-closed order. Publishing is an owner action: preparation and validation do not authorise it.

  1. Release synapse-channel==0.99.24 to PyPI through the normal attested tag workflow. Wait until both the wheel and source archive are publicly visible, then verify the package and ownership marker:
PYTHONPATH=. .venv/bin/python tools/verify_mcp_registry_release.py \
  --phase package --expect-version 0.99.24 --json
  1. Download the audited official Linux publisher release and verify it before execution. The digest below is the upstream v1.7.9 release digest:
curl -fL -o mcp-publisher_linux_amd64.tar.gz \
  https://github.com/modelcontextprotocol/registry/releases/download/v1.7.9/mcp-publisher_linux_amd64.tar.gz
printf '%s  %s\n' \
  ab128162b0616090b47cf245afe0a23f3ef08936fdce19074f5ba0a4469281ac \
  mcp-publisher_linux_amd64.tar.gz | sha256sum --check -
tar -xzf mcp-publisher_linux_amd64.tar.gz mcp-publisher
./mcp-publisher --version
./mcp-publisher validate server.json
  1. After explicit owner approval, dispatch the repository's mcp-registry workflow with the immutable release tag. It verifies the tag, PyPI boundary, publisher checksum, and server.json, then authenticates through GitHub Actions OIDC (id-token: write) without a repository secret:
gh workflow run mcp-registry.yml --ref main \
  --field release_tag=v0.99.24

Future successful release workflows dispatch this publication automatically. Interactive recovery remains available with ./mcp-publisher login github followed by ./mcp-publisher publish server.json.

  1. Require the public record itself to prove completion:
PYTHONPATH=. .venv/bin/python tools/verify_mcp_registry_release.py \
  --phase registry --expect-version 0.99.24 --json

The verifier exits 0 only when the requested boundary matches, 1 for public metadata drift, and 2 when local metadata or public evidence is unavailable. The underlying exact-name query remains useful for independent inspection:

curl --get --data-urlencode \
  'search=io.github.anulum/synapse-channel' \
  https://registry.modelcontextprotocol.io/v0.1/servers

Surface audit

The registered MCP tools and resources are checked against this guide by a source parser:

.venv/bin/python tools/audit_mcp_surface.py --check

The audit fails when src/synapse_channel/mcp/registration.py exposes a tool or resource that this page does not list, or when this page loses the adapter, authentication, or optional-dependency boundary text. It verifies documentation completeness for the local adapter surface; it does not certify external MCP client conformance.

What stays out of the hub

The adapter holds all MCP knowledge; the hub holds none. The MCP SDK is never a core dependency, the hub protocol is unchanged, and the bridge translates each MCP call into an ordinary hub message and correlates the reply. That keeps the single-transport-dependency guarantee — and the local-first model — intact.

Outbound: calling external MCP tools

The directions are independent. synapse mcp serves the hub to MCP clients; synapse mcp-tools and synapse mcp-call let a Synapse operator call tools on an external MCP server, with a deny-by-default trust boundary.

An outbound JSON config is executable policy, not ordinary project data: the server process starts before any MCP tool allowlist can protect you. Synapse therefore reads it through an owner-only, single-link descriptor, walking every path component with O_NOFOLLOW, and refuses a config inside the active Git repository by default. Store it under an operator-controlled config directory, for example ~/.config/synapse/mcp-allow.json, and run chmod 600 on it.

Each server needs a raw absolute path with no symlink component. Synapse copies the validated executable descriptor into a sealed Linux memfd and launches that exact immutable snapshot; a configured cwd is retained through its own descriptor. command_sha256 is optional but recommended and is checked against the bytes that actually execute. cwd is required, must be outside the active repository, and must not be group/world-writable. The repository-local escape hatch relaxes only repository locality; it never relaxes the mode check. Low-level library callers that construct a spec without cwd are descriptor-bound to /, never to the caller's current directory. Executable/hash proof covers the configured command, not files named in its arguments. A shebang script is therefore rejected as command: the kernel would open its interpreter separately, outside the sealed snapshot. Configure a native interpreter binary as command; until auxiliary-artifact pins exist, doctor conservatively warns about the script and every other command argument, including launcher flags such as -m. The child receives no parent environment values by default. Synapse explicitly blanks the MCP SDK's baseline POSIX names unless approved; literal env entries are passed exactly, while inherit_env approves individual parent variable names. An empty tool allowlist denies every tool; "*" explicitly opts the whole server in. A positive finite timeout_seconds greater than zero and no greater than 3600 (default 30) is the startup and discovery/invocation deadline. Larger or non-representable values fail schema parsing. Once cancellation begins, the pinned SDK applies its separate audited two-second graceful process-exit window before force termination:

{
  "version": 1,
  "servers": [
    {
      "name": "fs",
      "command": "/opt/synapse-mcp/bin/mcp-server-filesystem",
      "command_sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
      "args": ["--root", "/data"],
      "cwd": "/var/empty/synapse-mcp",
      "env": {"MCP_LOG_LEVEL": "warning"},
      "inherit_env": ["LANG"],
      "allowed_tools": ["read_file", "list_directory"]
    }
  ]
}
chmod 600 ~/.config/synapse/mcp-allow.json
synapse doctor --mcp-config ~/.config/synapse/mcp-allow.json
synapse mcp-tools fs --config ~/.config/synapse/mcp-allow.json
synapse mcp-call fs read_file --config ~/.config/synapse/mcp-allow.json \
  --arg path="/data/notes.txt"

Signed outbound manifests

For centrally managed or distributed policy, add a version-1 Ed25519 signature envelope and supply an owner-only trust bundle with --config-trust-bundle FILE. The signature covers the UTF-8 canonical JSON (sorted keys and compact separators), excluding only the signature value. The signed bytes therefore bind the policy plus envelope version, algorithm, and whitespace-free key_id, after the domain bytes SYNAPSE-CHANNEL:MCP-CONFIG:v1\0. A trust bundle cannot reuse the same public key under multiple IDs. The envelope is:

{
  "version": 1,
  "algorithm": "ed25519",
  "key_id": "operations-2026",
  "value": "BASE64_ED25519_SIGNATURE"
}

The separate trust bundle is also chmod 600, outside the repository, and has this shape:

{
  "version": 1,
  "keys": [
    {
      "key_id": "operations-2026",
      "public_key": "BASE64_RAW_32_BYTE_ED25519_PUBLIC_KEY",
      "revoked": false
    }
  ]
}

Passing a trust bundle makes the signature mandatory; a signed config without a trust bundle also fails closed. synapse doctor --mcp-config FILE --mcp-config-trust-bundle TRUST reports signature, hash-pin, repository, and environment posture before an operator attempts a call. The explicit --allow-repo-mcp-config compatibility escape hatch keeps the owner-only and executable checks but accepts a repository-local config or trust bundle and reports each accepted override as a warning. It never accepts group/world-writable config, trust, or cwd paths.

Subprocess startup and transport failures cross a stable operational-error boundary. The synthesized CLI error never reflects raw exception-group text. Configured server stderr remains attached to the operator's stderr and is not sanitized, so treat it as trusted server output.

The mcp extra installs the audited mcp==1.30.0 SDK and Ed25519 verification dependency. Runtime startup also verifies that SDK's inherited-environment list before spawning, so dependency drift fails closed rather than exposing a newly inherited name.

This descriptor-bound outbound launcher currently supports Linux with memfd_create and mounted procfs at /proc/self/fd. mcp-tools, mcp-call, and doctor --mcp-config fail closed on macOS, Windows, containers without procfs, or kernels without sealing support. Those platforms need a future equivalent native descriptor-execution backend; there is no pathname fallback. Per-agent ACLs over which identity may invoke outbound MCP remain a later tranche; the controls here bind the operator's process-launch policy before tool discovery.

A Git claim timeout is an unknown outcome (exit 3), not proof of denial. Use --confirm-only with the original identity and scope to verify an exact live lease without replaying a mutation. Claim recovery.

Reading the connected hub inbox

Set SYN_INBOX_SOURCE=hub in the MCP adapter's environment to make synapse_inbox read the central journal over its existing authenticated connection. This opens no additional socket. --uri and --token-file remain the server's connection options. The default feed source reads the adapter host's local relay file. Local --inbox-feed or --inbox-cursor overrides cannot be combined with the hub source.

Hub pages report available, hub_id, identity, cursor, messages, has_more and the endpoint in source. Repeat while has_more is true. The bridge has an independent endpoint and identity sequence cursor under $SYN_HOME/hub-inbox-cursor/. A refused, malformed, missing or unsupported response returns fixed unavailable JSON and retains the prior cursor; it never drains the local feed. Admitted roles participate in recipient matching. Channel-tagged chat is excluded. See remote inbox CLI for bounds, retention and replay. Reading does not prove model processing.