Skip to main content
MCP servers are in beta.
defineMcpServer from bijection/mcp declares one MCP server: its tools, the instructions that come with them and how its sessions start. mcpModule serves every server you declare, mcpTables adds the tables sessions need, and mountMcp mounts the routes.

Declaring a server

A session holds a role of your access model on the one object it acts for, and is granted nothing. Mark that role sessions: true, give the service that starts sessions a role carrying the power to issue it (customer:issue.supportAgent), and declare the service:
bijection/access.ts
A service is granted only roles of its ceiling. see covers a customer’s identifier alone, so the service can tell that a customer exists and reads none of its fields. The session’s role has no read.risk, so the fields behind it never reach the agent. maxDuration is the longest a session can last. A session holds its role only while its service holds the matching issue permission on that customer or above it. Revoke the service’s role, or let it expire, and every session it started loses its access at the same moment. A role marked sessions cannot carry grant.* or issue.* permissions. The operation resource is how your access model decides who may use an operation, for your staff and for sessions alike. A session holds operation:invoker on each operation its tools request and operation:reader on each it may only follow, until it ends. The service holds operation:issuer on those operations, which lets it issue those roles to its sessions. A server with no operation tools needs none of the three. Then declare the server and export the module:
bijection/mcp.ts
Keep the default export in bijection/mcp.ts. To export it from another file, pass that file’s path as module to mcpModule. Add the tables to your schema, and mount the routes:
bijection/schema.ts
bijection/http.ts
Each server is served at /mcp/<name> on your deployment’s .bijection.run host, with its OAuth protected-resource metadata beside it. Deployment checks the tables, their indexes, the public tool definitions, operation authorization and the mounted routes before it publishes your program, and an incomplete installation names what is missing. Adding a server changes nobody else’s permissions. Your access model keeps deciding who may use each operation; a session is one more principal holding roles, for exactly what its tools do: an operation tool reads and invokes its operation, a status tool only reads its requests, and no session previews.

Instructions

instructions tell the agent how to use the server’s tools together: what to read first, which tool answers what, what never to promise. Each tool’s description says what that tool does; the instructions say how the tools fit together for this server’s work. Every client receives the instructions when it connects (initialize and server/discover), before it calls any tool. An agent your deployment runs reads them before its own instructions. They are at most 8 KiB of text, and the same for every session of the server.

Tools

Each tool takes the definition’s module:export path, the definition itself, whose validators become the tool’s schema, and the description the model reads. A tool must be a public query or operation with explicit validators: publishing it adds a description, never a way in.
  • tool.query publishes a public query with a returns validator. It runs as the session, so your access rules decide which rows it reads.
  • tool.operation publishes a business operation defined with defineOperation. A call is a request of that operation through its ordinary invocation path.
  • tool.status publishes the safe status of accepted requests of an operation: its review state and each external call’s delivery and publication state, never its business result.
You can’t publish a mutation or an action as a tool. To let an agent change data, define an operation: operations give each write the stable identity, deduplication and recovery an agent retrying over the network needs. An operation tool answers with its request’s receipt:
Acceptance is a local receipt. If your operation makes external calls, an accepted result does not mean an external system has applied the change. Publish a status tool so the agent can check.

Binding a tool to the session’s object

A tool’s function takes the object it is about as an ordinary argument, so your app and your staff call it too. For a session, bind names that argument: the model never sees it and cannot supply it, and the session fills it in. An operation that declares the object as its target needs no bind: its tool binds the target on its own.
bijection/mcp.ts
bijection/desk.ts
The model is offered order_details with order_number alone. A bound argument is a required top-level ID or string. A tool with no arguments can read the session instead, with mcpSession(ctx) from bijection/mcp, which returns the session’s server, the object it acts for and its channel, or null for every other caller.

Writing a tool’s function

  • Select only what the role may read. Reading a whole document asks for every field. A tool that reads a field the session’s role lacks fails as a whole request, not as a tool error.
  • Keep an operation’s object type to what the session reads. An operation reads its target through the view or table it is declared on before it runs. Declare a view that selects only fields the session’s role may read; a view over the whole table is refused for a role that lacks one field.
  • Write under what you read. A function may write into the table it read, the tables below it in your access model, and grants. An operation that reads an order and writes a refund works when a refund belongs to its order; it is refused when both only belong to the customer.
  • Answer refusals as data. Return { outcome: "refused", reason } from an operation for the model to read. An error thrown after the function read protected data reaches the caller as a fixed refusal with no reason.

Names and values

Tool names are the keys of the tools object: a letter followed by letters, digits or underscores, up to 64 characters. A description is at most 4,096 characters, and a server publishes between 1 and 32 tools. Every tool’s arguments must be an object validator. Tool inputs and outputs use JSON, with these encodings for values JSON can’t represent exactly: Unknown fields and non-finite numbers are rejected. Publishing fails when a tool uses v.any(), or a union whose variants look the same on the wire, such as v.union(v.string(), v.int64()). Use an object union with a required v.literal discriminator instead.

When you deploy

A session records each tool’s contract when it starts: its name, description, schemas and binding. When you deploy while a conversation is running, the session keeps every tool whose contract you left unchanged, at the same URL. A tool whose contract changed answers tool_changed for that session, and a tool you add reaches new sessions only. The session’s host still reads back the requests it made through a changed operation tool. Changing the instructions changes no tool.

Tenants and staff directories

Sessions work with any access model, including one whose staff are a workspace directory and one that restricts objects to a tenant. A session principal is never a person of the directory: it holds only the roles its service may issue. In a workspace directory a service also acts only while a member sponsors it. When the object a session acts for is restricted to a tenant, the session passes that tenant exactly as its service does, so the service’s role must carry the tenant’s pass. A session that also reads the tenant itself, such as a store’s opening hours, names a role for that in ancestorRoles. It holds that role on the tenant the customer belongs to, while its service may issue it there.
bijection/mcp.ts
bijection/access.ts
The service’s role is granted per tenant, so its sessions hold something only in the tenants a person authorized it for; elsewhere they hold nothing and the host library refuses to start them. A service is one principal: every one of its credentials acts with all its grants, so tenants whose hosts must not reach each other need a service each. The role that issues a session’s ancestor roles is granted with the one that issues its role, on the same tenant.

Limits

  • Up to 16 servers in a module, 32 tools and four ancestorRoles in a server.
  • Instructions are at most 8 KiB, a description at most 1,024 characters.
  • A session is never given a marking’s pass: an object that carries a marking refuses the session unless one of its roles confers that pass.
  • In an app with a staff directory, a session cannot read people, so a tool cannot return a staff member’s name.
See Limits for request and result bounds and every error code.