Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


syn mcp starts a Model Context Protocol server over stdio. Any MCP client can plug in. We document Claude Code below; the wiring is the same for any client that supports stdio MCP servers.

What gets exposed

Twenty-five tools. Twelve read-only on the current workspace (incl. query, the read-only aggregation tool, SYN-183, and workspace_health, SYN-310); four read-only across opt-in workspaces (SYN-78, SYN-92 read slice); one maintenance tool (reindex, SYN-138); one audit tool (replay_proposal, client-driven: it returns replay material the agent re-runs; no server-side sampling, SYN-219); one integrity tool (verify, SYN-263: proves the memory was not silently corrupted); six write proposals routed through a human confirmation queue. Write tools never touch the workspace directly: the AI proposes; you sanction (or, under the safe auto-apply policy, low-risk reversible writes apply and are stamped auto-accepted; SYN-218).

The read contract (SYN-177/178/201/203). Every read tool returns the uniform { "result": <payload>, "freshness"? } envelope as MCP structuredContent (read that field directly: no prose parsing), plus a compact-JSON text mirror for clients without structured support. Each read tool advertises an outputSchema on tools/list, and search_knowledge hits + get_entity attach a resource_link to synapse://entity/{type}/{id} so a result is openable with resources/ read: no second get_entity round-trip.

Read-time hints (SYN-308). The high-value reads (search_knowledge, search_everything, workspace_overview, list_by_type, recent_changes, blame) attach a deterministic _meta sidecar, a sibling of structuredContent, so it never touches the outputSchema contract. It carries workspace (which brain answered), a precision breakdown of the SYN-304 relevance bands ({strong, good, partial}) and result_count for searches, and a next_step, the cheapest sensible follow-up (triage-then-get_entity, paginate with cursor, call workspace_health, propose an update on a low-trust blame). The agent reads _meta to judge whether results are on-topic before spending tokens, and errors carry a matching data.remediation (SYN-309, below).

Workspace-local

ToolPurposeInputs
search_knowledgeSearch the whole workspace. Hybrid (FTS + vector + graph, RRF) by default; degrades to plain FTS5 when the semantic models aren’t downloaded yet (or on a minimal core build).query, optional type/tag/owner/limit/mode/as_of
get_entityReturns the frontmatter + body of one entity as {"markdown": "<full text>"}.type, id
get_entitiesBatch read (SYN-181): many entities in one call, each optionally projected to chosen fields (fields: ["title","status","body"]) instead of full markdown. A missing/mistyped item yields a per-item error. Capped at 100.items: [{type, id, fields?}]
list_by_typeLists entities of one type with optional status filter. Bounded + paginated (SYN-176): a full page means more remain.type, optional status, limit (default 200), cursor (offset), as_of (valid-time date, SYN-223)
project_graphInbound + outbound link neighbours of an entity.type, id, optional rel
workspace_overviewOne-call digest (SYN-179): per-type counts, total, and the most-recently-updated entities: orient without one list_by_type per type.optional recent (default 10, max 50)
workspace_healthAgent-facing health (SYN-310): recent activity + hygiene (pending reviews, orphans, dangling, untagged) + a deterministic next_steps list, each with the exact syn command to hand the human. MCP counterpart of syn digest.optional days (default 7)
recent_changes“What changed since I last looked” since an RFC-3339 instant. Returns decisions (audit log at/after since, newest first: SYN-180) and deltas (per touched entity, the field-level before/after with AI authorship: {slug, type, author, accepted_at, changes:[{field, kind, before, after}]}: SYN-240, git-derived against the commit that predates since). CLI counterpart: syn changes (SYN-256).since, optional limit (caps decisions; default 100, max 500)
blameAI provenance lineage of an entity, who/what touched it, when, on whose behalf, with the deterministic trust score (SYN-240/256). MCP counterpart of syn blame.slug
diffPer-field before/after of an entity over git since a ref (default HEAD); provenance keys excluded (SYN-240/256). MCP counterpart of syn diff. null when not a git repo.slug, optional since
queryRead-only aggregation (SYN-183, ADR-023): count entities by a whitelisted dimension, optionally filtered: compute totals out of context without paging entities. Takes no SQL; reads only entities/tags. Returns {group_by, buckets:[{value,count}], total}.group_by (type|status|owner|priority|due|tag), optional filter (type/status/owner/priority/tag), limit (1–1000)
verifyProve the AI memory was not silently corrupted (SYN-263 / ADR-035): runs the integrity invariants (reindex fixpoint, audit payloads, history chain + checkpoint, supersession close) and returns {ok, report}, a signed synapse.verify-report/1. ok=false ⇒ an invariant was violated. deep adds INV-4 (governed-journal replay integrity, SYN-266 / ADR-036) + INV-13 (deterministic logical-contradiction sweep: overlap/cycle/inverted interval, guards the un-gated direct-edit path, SYN-316); rebuild lists every diverging fact on an INV-1 mismatch (SYN-269). Offline; tamper-evident, not tamper-proof. MCP counterpart of syn verify.optional deep, rebuild (bool)

search_knowledge mode (v0.5, SYN-88). Mirrors the CLI search modes: "hybrid" (default, FTS + vector + graph fused with RRF), "semantic" (vector similarity only), or "no-vector" (FTS + graph, skips the embedding step). The semantic layer ships in the default server (SYN-168); the vector legs go quiet: every search falls back to plain FTS5: when the models haven’t been downloaded yet or on a minimal core build (--no-default-features --features mcp). The hits keep the {type, id, title, snippet} shape (plus matched_by/relevance/compatibility when fused: SYN-304/306), wrapped in the {result, freshness?} envelope described above; a client that does not set mode keeps the default hybrid ranking. The independent legs (FTS + vector) run in parallel on separate connections (SYN-306 / ADR-044); the fused order is a pure function of the per-leg orders, so it stays deterministic. There is no separate semantic_search tool, the mode parameter delivers it without duplicating the search path.

Enrichment on accept (v0.5, BR-13). With the semantic models present, accepting a write proposal (or any other workspace write) runs the insertion-time enrichment pipeline inside the transaction: the new entity is embedded into entities_vec and its mentions are linked, so it is immediately findable by the next search_knowledge call. NER mentions that don’t resolve queue their own create_entity_from_ner proposals for review. Before the models are downloaded, the write still succeeds, enrichment is skipped gracefully and never triggers a download (run syn embed --all to backfill once the models are in place).

Federation (v0.2, SYN-78 + SYN-92 read slice)

ToolPurposeInputs
list_workspacesEvery workspace the user opted in for cross-workspace queries. Identity fields (description, topics, tags, language) come from each workspace’s SYNAPSE.md.none
federated_searchKeyword search (FTS + graph, no per-workspace vector leg) fanned out across opt-in workspaces. Each workspace’s hits carry the same search_knowledge hit shape. Returns [{workspace, id, hits}].query, optional type/tag/owner/limit/workspaces
get_entity_fromRead one entity from a specific opt-in workspace.workspace, type, id
get_workspace_manifestRead the SYNAPSE.md identity card of an opt-in workspace.workspace

Write proposals (v0.2, SYN-60/61/62/92)

Every write tool below enqueues a proposal; it does not touch the workspace. The response carries the pending_id plus accept_command / reject_command strings the LLM can surface verbatim. The user reviews with syn pending show <id> and applies with syn pending accept <id>. See BR-12.3: MCP não escreve sem confirmação humana.

ToolPurposeInputs
capture_factPropose creating a new entity (note, decision, person, project, …).type, title, optional body/tags/fields/reason
link_entityPropose linking two entities with a labelled relation.from_type, from_id, to_type, to_id, rel, optional reason
update_statusPropose updating an entity’s status field (task done, project paused).type, id, status, optional reason
add_workspace_topicPropose appending a topic to an opt-in workspace’s SYNAPSE.md.workspace, topic, optional reason
add_workspace_tagPropose appending a tag to an opt-in workspace’s SYNAPSE.md.workspace, tag, optional reason
propose_batchBundle several proposals into ONE pending row the human accepts with a single syn pending accept (SYN-182). Each item is {kind, payload} where kind ∈ capture_fact/link_entity/update_status/topic_add/tag_add (note: topic_add/tag_add, the leaf-kind names) and payload is that tool’s usual fields, validated up front. Applied in order on accept (best-effort sequential, not one atomic transaction: atomicity tracked as SYN-202).proposals: [{kind, payload}], optional reason/context

Every write tool also accepts an optional context JSON snapshot (SYN-114) recorded with the proposal for syn audit replay.

Response shape for every write tool:

{
  "pending_id": 7,
  "accept_command": "syn pending accept 7",
  "reject_command": "syn pending reject 7",
  "kind": "capture_fact",
  "summary": "note \"PKCE OAuth decision\""
}

Write tools return this object as structuredContent plus a compact-JSON text mirror (the read envelope is for read tools only). The same use cases back the CLI; there is no “second truth”.

Freshness advisory (SYN-93, ADR-011)

Every tool response that touched a workspace can carry a freshness block when the workspace’s tracked origin branch differs from the local one (or the check has timed out: Phase 2). The shape is:

{
  "result": <original tool payload>,
  "freshness": {
    "workspace": "client-alfa",
    "status": "diverged",
    "behind_count": -1,
    "last_check": "2026-05-25T14:00:00Z",
    "advice": "git -C /Users/.../client-alfa pull"
  }
}

Rules:

  • result is always present (the uniform envelope, SYN-203); only the freshness block is conditional; it is attached when there is something to say, and omitted for up_to_date/unknown statuses (silence is the polite default).
  • behind_count = -1 in Phase 1 means “histories differ; the precise count lands with SYN-93b”.
  • advice is always a concrete git -C <root> pull command the LLM can surface verbatim.
  • The check itself is filesystem-only and rate-limited (default 30 min); the AI never pays a network round-trip for it.

Disable per-workspace with syn ws freshness off <name>. Disable globally with [freshness] enabled = false in any config layer. The MCP enrichment ignores silent_cli; that knob only governs the CLI stderr line.

Privacy gate (BR-12.4, SYN-79)

Federation defaults OFF. A workspace only appears in list_workspaces, contributes hits to federated_search, or is readable via get_entity_from / get_workspace_manifest after the user runs syn ws include <name> on it.

This is a structural invariant, not a check: every fan-out path in the codebase goes through crate::core::federation::fan_out{,_filtered}, which hard-codes Registry::list_federated() (the SQL filter is WHERE federated = 1). Non-opt-in ids passed to federated_search.workspaces are silently dropped: distinguishing “exists but not federated” from “does not exist” would leak the bit BR-12.4 is meant to keep private. The same applies to get_entity_from and get_workspace_manifest: both raise an indistinguishable entity '<name>' not found for “no such workspace” and “exists but private”.

Claude Code

The zero-friction path (SYN-288): let syn write the config for you:

syn mcp install                          # Claude Desktop (claude_desktop_config.json)
syn mcp install --client claude-code     # project-local .mcp.json in the current dir
syn mcp install --workspace my-brain     # pin a brain by name/path (SYN-300 / #287)
syn mcp install --print                  # print the JSON snippet, write nothing
syn mcp uninstall                        # remove just the synapse entry

--workspace <path|name> (SYN-300 / #287). By default the installed config runs syn mcp with no cwd, so it only finds a brain when the client launches inside the workspace. Pass --workspace and install bakes --workspace <ref> into the config args, so the server serves that brain from any directory, the cross-project “query my brain from another repo” flow, with no manual editing.

install merges a synapse entry into the existing config: other MCP servers and unrelated keys are left untouched, the prior file is backed up to *.json.bak, and re-running updates the entry in place (idempotent). It points command at the absolute path of the running syn binary.

Prefer to edit by hand? Add this to ~/.claude.json (or run claude mcp add synapse …):

{
  "mcpServers": {
    "synapse": {
      "command": "syn",
      "args": ["mcp"],
      "cwd": "/Users/you/brain"
    }
  }
}
  • command is whatever syn you installed (which syn to confirm).
  • cwd is the workspace you want the server to read from.
  • No environment variables needed for v0.1.

Serve a brain from another project (cross-directory)

syn mcp reads the workspace from the current directory by default, so a client launched in a different project (not itself a Synapse workspace) has nothing to serve. To point the server at your brain wherever you are, pass --workspace (SYN-300 P1): an absolute path, or a registered name/id from the global catalog (syn ws list; every syn init registers there):

# by path
claude mcp add synapse -- syn mcp --workspace /Users/you/brain
# or by registered name/id: works from any project directory
claude mcp add synapse -- syn mcp --workspace my-brain

Now Claude Code in any repo answers from that brain: you don’t have to cd into it. (This is cleaner than the cwd field above, which depends on the client honoring a working directory; --workspace resolves the brain itself.)

Restart Claude Code and ask, in any project:

“Who is the focal contact for projeto-alfa, and which APIs do they own?”

Claude calls project_graph(type=project, id=projeto-alfa) to find Maria, then get_entity(type=person, id=maria-silva) to read her file, then surfaces the answer with the right slugs.

Other MCP clients

If your client speaks MCP over stdio, the launch command is the same:

syn mcp

Synapse also advertises resources on resources/list: the workspace manifest, the server card, synapse://capabilities (SYN-311, the self-describing vocabulary: entity types + their fields, link relations, reserved x-* keys, write-proposal payload shapes, enrichment sources; read it before proposing to avoid an invalid kind/rel/key, the same dump the CLI syn context prints), every entity, and the recent audit trail.

Synapse advertises tool schemas (input + output JSON Schema) on tools/list, each with MCP tool annotations (readOnlyHint on every read, no destructiveHint on the additive write proposals, idempotentHint on reindex) so a client can auto-allow reads and gate writes without guessing. The 25 names are search_knowledge, search_everything (SYN-226, RRF over every kind), query (SYN-183, read-only aggregation), get_entity, get_entities, list_by_type, project_graph, workspace_overview, workspace_health (SYN-310), recent_changes, blame (SYN-256), diff (SYN-256), verify (SYN-263, proves non-corruption), list_workspaces, federated_search, get_entity_from, get_workspace_manifest, reindex (SYN-138, emits Tasks progress under --features mcp_tasks), replay_proposal (SYN-136/SYN-219, client-driven: it returns replay material the agent re-runs: no server-initiated sampling, which the MCP spec has since deprecated in SEP-2577), capture_fact, link_entity, update_status, add_workspace_topic, add_workspace_tag, propose_batch.

Actionable errors (SYN-309)

A recoverable condition (unknown id/type, a missing/invalid proposal field, no workspace, a model still downloading) comes back as a JSON-RPC invalid_params error whose data carries a stable code (the i18n key), a category (user/transient), recoverable/retryable flags, and a remediation string naming the exact next step (e.g. “unknown type: read the synapse://capabilities resource for the registered types”, or “no such entity: discover ids with search_knowledge”). The remediation is also appended to the human-readable message. Genuine faults (a corrupted audit trail, a migration mismatch) stay internal_error but are still annotated, so a client never has to parse prose to know whether the agent can retry. Internal paths and SQL are never leaked (ADR-023 posture).

Locking down what the AI sees

The server’s cwd workspace is always fully readable through the local read tools (search_knowledge, search_everything, get_entity, get_entities, list_by_type, project_graph, workspace_overview, workspace_health, recent_changes, plus the read-only query aggregation). Other workspaces are only reachable when the user has opted them in:

cd ~/work-brain && syn ws include work-brain    # join the federated pool
cd ~/client-acme-brain && syn ws include client-acme-brain
syn ws exclude work-brain                       # leave anytime

The federation tools (list_workspaces, federated_search, get_entity_from, get_workspace_manifest) only ever see workspaces marked federated = 1. Workspaces left at the default (private) never leak through the server, even if the LLM literally asks for them by name. See the Privacy gate section above.

The simplest segmentation is still one workspace per audience:

~/brain/                       ← personal: notes, decisions, drafts
~/work-brain/                  ← work: APIs, stakeholders, tasks
~/client-acme-brain/           ← per-client

Run one syn mcp per cwd (Claude Code spawns one process per mcpServers entry); opt only the workspaces you trust into the federated pool.

Inspect the server by hand

Useful when something is off. The protocol is JSON-RPC over stdio:

cd ~/brain
{
  printf '%s\n' \
    '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"manual","version":"0"}}}' \
    '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
    '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
    '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_knowledge","arguments":{"query":"pagamentos"}}}'
  sleep 1
} | syn mcp | jq .

You’ll see serverInfo + the full tools/list schema set (25 tools, each read carrying an outputSchema) + a search_knowledge result whose structuredContent is the {result, freshness?} envelope, with resource links on the hits. With the semantic models present the result is hybrid-ranked; add "mode":"semantic" to the arguments for vector-only.

Shipped in v0.5

CapabilityTicket
Hybrid ranking in search_knowledge + the mode parameter (FTS + vector + graph, RRF)SYN-86 / SYN-88
Insertion-time enrichment on accepted proposals (embed + NER + link)SYN-160 / SYN-161
Network ls-refs freshness (precise behind_count, unreachable fallback)SYN-93b

Prompts

Nine named prompts (the synapse: namespace), each a deterministic function of (name, args, workspace): no LLM round-trip to assemble:

  • synapse:capture-decision: guided capture_fact flow for a decision note.
  • synapse:daily-review: yesterday’s accepted proposals + open tasks.
  • synapse:weekly-brief: audience-tailored weekly briefing.
  • synapse:proposal-contract (SYN-252): the proposal-quality contract an agent should read before a capture session.
  • synapse:onboard-workspace (SYN-312): first-run orientation: read the capabilities + overview, confirm the self-identity, learn the propose→accept gate. Start a fresh session here.
  • synapse:orient (SYN-312): the cheapest-read decision table (which read tool for which question) as a fetchable prompt.
  • synapse:catch-me-up (SYN-312): resume with a delta via recent_changes since a given instant, not a full re-read.
  • synapse:resolve-contradiction (SYN-312): when a capture_fact returns a contradicts signal, walk the supersede-don’t-duplicate path (x-supersedes).
  • synapse:capture-and-verify (SYN-312): the full governed-write loop: propose → hand over the accept command → offer syn verify.

Contradiction detection (SYN-242)

At propose-time Synapse checks, deterministically and on structured fields, whether a write contradicts an accepted fact: same subject + predicate, different object: an update_status that flips a field to a different value, or a link_entity that re-points the same edge to a different relationship. A contradiction is never “low-risk reversible”, so it is forced to the human gate even under auto_apply = safe, and the write tool’s response carries a contradicts: {subject, predicate, existing, proposed} signal. Resolve by superseding the existing fact (x-supersedes, SYN-223): invalidation, not duplication, not by adding a near-duplicate. Free-text (NLI) contradiction and same-subject detection across separate captures (identity resolution) are deferred to v0.11 (SYN-245/254).

Proposal-quality contract (SYN-252)

The differentiator of governed memory is that a human accepts each fact, so the scarce resource is the reviewer’s attention. A good proposal respects:

  1. Atomicity: one subject–predicate–object per proposal; never bundle unrelated claims.
  2. Observation date: x-valid-from is when the fact became true in the world, not when you learned it (transaction-time comes from git; don’t duplicate it).
  3. No echo: search first; propose deltas, not duplicates of what’s already there.
  4. Supersede, don’t duplicate: a changed fact carries x-supersedes: <old-id> (the old interval is closed on accept: invalidation, not deletion, SYN-223).
  5. No meta-extraction: facts about the domain, not about the conversation.
  6. Attribution: cite the source; provenance (x-ai-*) records who/when automatically.
  7. Typed over prose: prefer a typed entity + indexed fields when the domain has structure (syn types).

Agents fetch the canonical copy via prompts/get synapse:proposal-contract; this section is the human-readable mirror.

What doesn’t exist yet (and when)

CapabilityTicketPhase
HTTP/SSE transport (today: stdio only)-backlog
Lil Syn dozing

When syn mcp hands back a (no hits) response, that’s Lil Syn dozing on an empty index. The fix is usually one of: a missing tag, a typo, or just not enough indexed yet. Run syn search <term> from your shell to see if you find it before blaming the AI.