MCP Both Directions: Your Platform as Client and Server
Matrix sits on both sides of the Model Context Protocol — it exposes its own MCP server and its agents consume external ones, under one JWT auth model.
Most platforms pick a side of the Model Context Protocol. They either expose an MCP server so other tools can reach their data, or they consume MCP servers so their agents gain new capabilities. Matrix does both — and, more importantly, it does both under one authentication model. The token that authorizes a REST call is the same token that authorizes an MCP call. There is no second auth surface to provision, rotate, or get wrong.
This post walks both directions: Matrix as an MCP server that other clients drive, and Matrix as an MCP client that drives external servers on behalf of an agent. Then it tells you, candidly, where the client transport story is finished and where it isn't.
Why both directions matter
The Model Context Protocol is a wire standard for connecting LLM applications to tools and data. The interesting design choice isn't "do we support MCP" — almost everyone does now — it's which role you play.
- Server role: you publish a stable, typed surface (
list_agents,query_entities, …) that any MCP-speaking client — an IDE assistant, a developer's own agent, another platform — can call to read and act on your data. - Client role: your agents reach out to the open ecosystem. An MCP server someone else runs becomes a tool your agent can call mid-turn, with no bespoke integration code.
Picking only one role leaves half the value on the table. A platform that's only a server is a closed garden you can read from. A platform that's only a client can borrow capabilities but can't be composed into anyone else's workflow. Matrix is on both sides, which means your Matrix workspace is simultaneously a thing other agents can use and a thing that can use other agents' tools.
Matrix as an MCP server: /mcp/**
Layer 6 of the architecture is the MCP server. It lives at /mcp/** and speaks Streamable HTTP — the current MCP transport. It exposes five tools:
| Tool | What it does |
|---|---|
list_agents | Enumerate the agents in the caller's org |
query_entities | Read from the generic entity store, tenant-filtered |
read_memory | Pull a contact's recalled memory |
write_memory | Persist a memory back into the graph |
dispatch_task | Kick off an async task on the platform's task bus |
That's not an arbitrary grab-bag — it's a deliberate slice of the platform: discover what agents exist, read and write the entity graph, read and write memory, and trigger work. An external client wired to this server can drive Matrix the way one of its own dashboards does.
One auth model, not two
Here is the part that matters most. The /mcp/** path is gated by the same JWT used for REST. From docs/ARCHITECTURE.md:
MCP server —
/mcp/**(Streamable HTTP) exposeslist_agents,query_entities,read_memory,write_memory,dispatch_task. Same JWT used for REST gates this path — no second auth model.
Concretely: you log in once and use the bearer token everywhere.
# Get a token the normal way
POST /api/auth/login
{ "email": "...", "password": "..." }
→ { "accessToken": "<jwt>" }
# Use it against the REST surface…
GET /api/orgs/{slug}/agents
Authorization: Bearer <jwt>
# …and the exact same token against the MCP server
POST /mcp/...
Authorization: Bearer <jwt>
This is more than a convenience. A JWT in Matrix carries the request-scoped TenantContext — the org, the user, the roles — that every read and write filters by. Because MCP rides the same token, an MCP client is automatically scoped to exactly the tenant and permissions its caller has. There's no separate API-key namespace that could drift out of sync with your real access boundaries, and no second place to leak credentials from. Multi-tenancy and access control don't get re-implemented for the MCP surface; they're inherited from the auth layer wholesale.
Matrix as an MCP client: agents that consume external servers
Flip the arrow. An agent in Matrix can call out to MCP servers that someone else runs.
You don't write integration code to make that happen. External servers are modeled as first-class McpServer entities — the same generic EntityType / EntityNode model everything else uses. An agent references them in one of two ways:
- Directly, via the agent's
mcpServersfield, or - Bundled into a Skill — a Skill carries
mcpServers(a multi-ref) right alongside itstools, prompt block, and required contact fields. Attach the skill, and its MCP servers come with it.
The composition path
The single place this all comes together is AgentToolSurface.composeForCaller — the canonical composition path described in the architecture doc. For each turn it unions everything the agent can do into one tool surface:
agent.tools— direct HTTP / display tool refsagent.mcpServers— external MCP serversskill.toolsfrom every attached SkillBuiltinToolRegistrylookups for the INTERNAL-transport built-in toolbox (web_search,bash, …)- the auto-attached
search_knowledgetool when the agent has Knowledge - the memory built-ins and the ambient
get_current_time
The payoff: MCP tools are not special-cased to the model. From the agent's perspective, a method on an external MCP server is just another callable in the surface, sitting next to an HTTP tool, a sandboxed bash, and a knowledge search. That uniformity is the whole point of the four-primitives composition model — Tool, Skill, Knowledge, and the built-in toolbox all collapse into one prompt and one tool surface, with no per-feature wiring on the agent end. MCP servers compose in exactly the same way.
Built on Spring AI's MCP starters
Both directions are built on Spring AI 1.0's MCP support — specifically the MCP client starter and the MCP server-webmvc starter (both pulled in via the Spring AI BOM). The server-webmvc starter is what stands up the Streamable HTTP endpoint at /mcp/**; the client starter is what lets AgentToolSurface dial out to external servers.
Leaning on Spring AI here is a deliberate choice. The protocol evolves, and the SDK absorbs that churn so the platform code doesn't have to. It also means the same Spring Security filter chain that protects the rest of the app protects the MCP server — which is precisely why the "same JWT" guarantee falls out for free rather than being something we bolted on.
The honest part: client transport status
Now the candid bit, because accuracy beats marketing.
The MCP server is Streamable HTTP, full stop. The MCP client, today, speaks SSE transport. That's an important asymmetry to state plainly: when a Matrix agent consumes an external MCP server, it currently connects over Server-Sent Events.
The practical consequence: a number of GitHub-hosted MCP servers have moved to Streamable HTTP as their transport, and reaching those from the Matrix client needs an SDK bump. So:
- Done: Matrix as a Streamable HTTP MCP server, JWT-gated, with the five tools above.
- Done: Matrix as an MCP client over SSE — agents can consume SSE-transport MCP servers right now, composed in through
AgentToolSurface. - In progress: Streamable HTTP client transport, to reach the GitHub-hosted servers that have moved off SSE. This is an SDK-version uplift, not a redesign — the
McpServerentity model and the composition path don't change.
If you're wiring up an external MCP server today, an SSE-transport server is the supported path. Streamable HTTP on the client side is coming; describe it to yourself as scheduled work, not a shipped feature.
Takeaway
Being on both sides of the Model Context Protocol isn't symmetry for its own sake. It means your workspace is composable in two directions at once: other agents can drive Matrix through /mcp/**, and your agents can pull in external MCP tools through the same surface that already carries their HTTP tools, skills, knowledge, and built-ins. And because the MCP server reuses the exact JWT that gates REST, you get multi-tenancy and access scoping on the MCP surface for free — there's no second auth model to maintain or mis-configure. The one caveat worth holding in your head: the client transport is SSE today, with Streamable-HTTP client support in progress.
Ready to plug in? Spin up a workspace, mint a token with POST /api/auth/login, and point your MCP client at /mcp/** — or attach an McpServer to an agent and watch it show up in the tool surface on the next turn. The built-in toolbox post shows what else is already in that surface, and the four-primitives post shows how it all composes.
Build your first agent on Matrix
Spin up a workspace, wire up tools and knowledge, give your agent a voice, and talk to it in real time — no agent code required.