The shape of it
connect_mcp(name, command, args, capability?) spawns an MCP server as a
subprocess over stdio, discovers its tools once at connection time, and
returns an McpServer value (.name, .tools, .close()). Each tool in
.tools reports as "Tool" from type_of() and can be handed to
ask_with_tools exactly like a Sense
tool — that’s the only way to invoke one; there’s no direct call syntax.
Requires the optional mcp package (pip install sense-lang[mcp],
imported lazily — importing sense_lang itself never requires it).
When to use this
Reach forconnect_mcp when the capability you want to expose to a model
already exists as an MCP server —
someone else’s filesystem tool, database tool, or API wrapper — rather
than reimplementing it as a Sense tool. Use a plain tool instead when
the logic is simple enough to write directly in Sense, or when you need
Sense-level type checking on the parameters.
An MCP tool has no Sense body — invoking it is a real RPC call
This is the one place in Sense’s interop story that needed genuinely new runtime infrastructure rather than composition of what already existed: an MCP tool’s implementation lives in another process, so calling one means a JSON-RPC request over the officialmcp SDK, not running a
ToolDecl’s body.
1
No direct call syntax
An
McpTool never gets a Sense identifier the way tool search(...)
does. MCP tools exist for a model to discover and choose from via
ask_with_tools (or a skill bundling them),
not for a human to call by name in source.2
One capability covers the whole connection
Not per tool — splitting that finely would mean guessing at an
external server’s tool semantics from its name/description. Two
servers needing different capabilities means two
connect_mcp(...)
calls. Tool names are namespaced server.tool so two servers (or a
server and a bare Sense tool) can’t collide on a generic name.3
close() is explicit
Unlike agent threads or SQLite connections elsewhere in Sense (no
explicit cleanup needed, reclaimed at process exit), an MCP connection
spawns a real OS subprocess — a more visible failure mode left
uncleaned than an idle thread, so it gets its own cleanup call.
tool call uses — same shape (capability check, audit entry,
denial fed back to the model rather than raised).
Why a background thread, not a callback
One detail worth knowing if you’re debugging a connection issue: the connection’s entire lifetime — connect, every tool call, and shutdown — runs as a single long-lived task on that background thread’s event loop, rather than one task per operation.mcp’s own context managers use
anyio cancel scopes internally, which require entering and exiting in
the same asyncio task; splitting connect and shutdown into separate tasks
raises a cancel-scope error from anyio itself.
What this doesn’t do (yet)
HTTP/SSE transport
HTTP/SSE transport
connect_mcp is stdio-only.Resources and prompts
Resources and prompts
Only MCP tool discovery/invocation is wired up — not MCP resources or
prompt templates.
Reconnection
Reconnection
A dead connection surfaces a clear error on the next call rather than
auto-reconnecting. One background thread per connection, not pooled.
Continue
Tool-Calling
The mechanism an MCP server’s tools are exposed through.
Skills
Bundling MCP tools alongside Sense tools under one description.
Installation
The
[mcp] optional extra connect_mcp needs.
