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 doctorshows what the host is set up to start. Themcp-host-sourcescheck lists every server whose name containssynapsein 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'ssynapse mcp: a different command, a differentsynapseexecutable, a missing command, a modified plugin, or a file it cannot read. Themcp-claim-guardcheck 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:
- At session/turn start, call
synapse_status, thensynapse_inboxuntilhas_moreis false. - 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.
- Release
synapse-channel==0.99.24to 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
- Download the audited official Linux publisher release and verify it before
execution. The digest below is the upstream
v1.7.9release 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
- After explicit owner approval, dispatch the repository's
mcp-registryworkflow with the immutable release tag. It verifies the tag, PyPI boundary, publisher checksum, andserver.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.
- 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.