The MCP bus is plumbing when it's right, and a world-readable HTTP listener when it's wrong. SMCP 3.1.0 is where we stopped pretending it was anything other than infrastructure.
Today we merged dev β master and tagged v3.1.0 on sanctumos/smcp. This is an omnibus production release β every change below is something you only ship when you've quit pretending the tool layer is a demo.
If you run SMCP anywhere an agent touches real infrastructure, read the security section first, then what changed for operators and plugin authors.
Why this release exists
SMCP sits between Letta agents (and other MCP clients) and everything else β Tasks, Gmail, GitHub, provisioning APIs, and our own product backends. When the layer underneath is sloppy, the failure modes start subtle: wrong tools in context, malformed argv, silent plugin skips, an HTTP listener that should never have been world-readable. 3.1.0 closes that gap.
What's actually new on the release branch:
- Session attach governor with config-driven profiles β product-specific tool allowlists live in JSON files, not hardcoded in core.
- Versioned
--describecontract validated at discovery β bad metadata fails loudly with field paths, not mystery empty catalogs. - Structured tool errors β clients can tell timeout from plugin_error from invalid_tool_name without parsing prose.
- Installable packaging β
pip install .exposes thesmcpandsmcp-stdioconsole entrypoints; import collisions are fixed. - CI + 90% coverage gate on Python 3.10β3.13 β 354 tests, ~99% line coverage on the release branch.
ServerContextβ governor state and plugin registries are per-context, not process-global singletons.
Deep-dive on the governor philosophy: SMCP Tool Governance: Attach, Detach, and Help as a Protocol.
The critical security patch (HTTP/SSE transport)
Operators: this section is mandatory if you expose SMCP over the network.
The HTTP authentication hardening we carried through the dev line (issue #39, first documented in 3.0.3) lands as a critical patch here:
- Shared-secret auth via
MCP_API_KEY/MCP_API_KEYSβ clients sendAuthorization: BearerorX-API-Key:. --allow-externalfails closed β binding beyond localhost refuses to start without a configured key, unless you explicitly setMCP_AUTH_DISABLED=1(logged as unsafe).- Loopback clients skip auth by default. Use
--require-authwhen you want keys even on127.0.0.1. - Auth runs as raw ASGI middleware so SSE streaming is never buffered by a broken auth wrapper.
- STDIO transport is unchanged β no network surface, no keys required.
If that reads as paranoid, it should. The operative posture on a tool layer is paranoid.
We're not publishing incident-specific details in this post; the fix is in the release. The operator action before you bind externally is: upgrade, generate a long random key, set MCP_API_KEY, restart.
Quick reference:
export MCP_API_KEY="$(openssl rand -hex 32)"
smcp --allow-external --port 8000
export MCP_API_KEY="$(openssl rand -hex 32)" smcp --allow-external --port 8000
Full tables: SMCP deployment guide and API reference.
Governor profiles: tools the agent should see, not everything you could expose
The catalog plane (what a plugin can do) and the attach plane (what this session may call) are different problems. 3.1.0 makes the attach plane data-driven:
- Profiles load from
SMCP_PROFILESβ a JSON file or a directory of*.jsonconfigs. - Built-ins:
full(the whole catalog) and optionaladminwhenSMCP_ADMIN_PREFIXis set. - A stale profile name falls back to
fullinstead of bricking the server. - Example config ships at
docs/examples/governor-profiles.json.
The sanctum__tools namespace (attach, detach, list-attached, help, profile management) is how an agent or harness curates what exposure looks like without rewriting plugins. The shape: the agent reasons about a smaller tools/list, pays fewer tokens, and you don't have to choose between "give the agent everything" and "give the agent nothing."
Plugin authors: --describe contract v1
If you're writing a plugin against SMCP 3.1.0, implement --describe returning JSON validated against docs/plugin-contract/v1.json. Invalid describe payloads are skipped at discovery with a named validation error β no silent half-registration, no mystery empty catalog.
The bundled demo_math and demo_text plugins show the contract in practice. The retired botfather / devops samples are gone from core; real integrations live in standalone plugin repos under plugins/.
Two changes worth flagging on the input side: structured array and object arguments now round-trip as clean JSON on argv (issue #56) β fixing the class of "the model sent valid JSON but the plugin got Python repr" failures that have wrecked production plugins.
Operator checklist
- Pin
v3.1.0(or mergemaster) on your deployment branch. pip install ., or use the newsmcp/smcp-stdioconsole scripts.- If the server is reachable off-box:
MCP_API_KEY+ verify--allow-externalwithout a key exits with code 2. - Point
SMCP_PROFILESat your product allowlists (chatter vs admin vs whatever else you run). - Set
MCP_PLUGIN_TIMEOUTif you run long subprocess tools. - Read the updated SanctumOS SMCP module doc.
Six things. The first one is the only one you'll regret skipping.
What's next
God-module decomposition (#43) stays deferred β the instance refactor (#46) bought us isolation without the file split we'd been avoiding. Plugin ecosystem work continues in satellite repos; core stays product-agnostic.
If something breaks in production, file at GitHub issues on sanctumos/smcp. We read those before we read the changelog.