MCP
MCP (Model Context Protocol) is how AI agents operate your data.
MCP (Model Context Protocol) is how AI agents operate your data. The engine exposes a streamable
HTTP endpoint at /mcp (JSON-RPC 2.0): Claude, Cursor, or any MCP host connects once, then
discovers your objects as tools.
Two things make that safe:
tools/listreturns a per-identity, permission-filtered tool surface.tools/callre-enforces RBAC against the proxied user at call time.
That double layer is the point. Every attempt is also rate-limited, alertable, and audited.
MCP is an adapter — shared by every project type and enabled by default (adapters.mcp
undeclared = on).
Enable / configure
// weavekit.config.ts
export default {
// ...
adapters: {
rest: { prefix: '/api' },
mcp: {
// endpoint: '/mcp/agent', // optional: mount on a different path (default /mcp)
identities: {
alice: { id: 'u-alice', roles: ['sales'] },
emma: { id: 'u-emma', roles: ['finance'], teamId: 't1' },
},
guardrails: {
rateLimit: { windowMs: 60_000, max: 100 }, // optional; defaults shown
alerts: { channel: 'console' }, // console | webhook | slack
},
},
},
};endpoint— path to mount on (default/mcp). Change it if/mcpcollides with another route in your deployment; client URLs must match.identities— the static on-behalf-of directory (ref → RbacSubject). A session's proxied user must resolve here; a missing or unknown ref is rejected at session establishment.guardrails.alerts— anyinfrastructure/alertsfactory config (webhook/slack channels reuseurl/webhookUrl, and so on).enabled: falseturns the endpoint off entirely.
How an agent connects
POST /mcpwithAuthorization: Bearer <apiKey>andX-Weavekit-On-Behalf-Of: <ref>, carrying theinitializerequest. The engine creates a session bound to the resolved identity and returns anMcp-Session-Idheader for reuse.tools/listreturns the tool surface compiled for that identity. RBAC decides which objects and operations appear.tools/callruns through the RBAC-decorated data-access layer against the session identity. A call-levelonBehalfOfargument is an optional temporary override.
Auth failures return 401 before the transport is entered. A missing or unknown on-behalf-of ref fails the session with a clear error.
Tool surface
For every object with read permission, the engine compiles:
| Tool | Purpose | Key args |
|---|---|---|
search_<object> | list (row scope applied) | filter, sort, limit (≤1000), offset, fields |
get_<object> | fetch one by primary key | the object's primary-key field name (e.g. doc_no) |
create_<object> | create (writable fields only) | data |
update_<object> | update (RBAC update whitelist only) | primary-key field, changes |
delete_<object> | delete (row scope applied) | the object's primary-key field name |
The primary-key argument is named after the schema's primary: true field — never a hardcoded id.
So get_repair_order takes { doc_no } when the object's primary field is doc_no.
Always present:
| Tool | Purpose |
|---|---|
list_objects | objects the identity can read ([{ name, label }]) |
describe_object | schema + the identity's effective permissions |
Tool shaping follows RBAC exactly: an object with no listed role yields zero tools; update: []
yields no update_; fields.exclude fields are stripped from parameter schemas and from results.
Argument schemas are plain JSON Schema (field types map 1:1 to
string/number/integer/boolean/object/enum, relations to their target primary-key type).
Guardrails
- Rate limiting — per-agent-key sliding window (default 100/60s). Over-limit calls return an
isErrorresult and fire awarnalert. - Alerts — injected
AlertSink(default console; webhook/slack via config). - Audit — every tool attempt writes
mcp.tool.<name>to the unifiedweavekit_audittable (action prefixACTION_PREFIXES.MCP_TOOL), including RBAC denials and failures.actorIdis the agent key;metacarries{ onBehalfOf, subjectId, roles, agentLabel, tool }. Audit is best-effort — a failing sink never blocks the tool call.
Wiring
createEngine wires the endpoint for you:
const engine = await createEngine({
databaseUrl: process.env.DATABASE_URL,
schemaDir: '.',
auth: { source: { 'sk-agent': { id: 'agent-1', roles: ['agent'] } } },
adapters: { mcp: { identities: { alice: { id: 'u-alice', roles: ['sales'] } } } },
});
await engine.app.listen({ port: 3000 });
// MCP endpoint: http://localhost:3000/mcpYou inject the sinks at assembly: audit (a buffered subsystem sink, or a no-op when audit is
disabled), alerts (createAlerts), and identity (mcp.identities — a static directory or your own
IdentityResolver). The MCP adapter itself depends only on core contracts; adapters never import
subsystems or infrastructure.
For a full end-to-end walkthrough — a customer with an existing CRM (customers / orders tables)
bringing agents in through the static identity directory — see the
customer integration practice.
The engine is a bridge to your database, not a DDL runner. createEngine loads the schema and
never touches your tables. Build or alter tables explicitly with weave migrate, and see
How migration handles existing tables for what it
does and doesn't change. (Greenfield and business projects can set migrate.auto: true instead.)
Plugging in your own user store
Both auth.source and mcp.identities accept either a static map (the defaults above) or a
resolver function. That lets you drive authentication and on-behalf-of identity from your own
users, roles, and teams instead of hardcoded config.
auth.source—Record<string, RbacSubject> | AuthResolver, whereAuthResolver = (header) => subject | null | Promise<...>. The resolver receives the fullAuthorizationheader (including theBearerprefix) and may verify a JWT or look up the user asynchronously. Returnnullfor unauthenticated (401).mcp.identities—Record<string, RbacSubject> | IdentityResolver, whereIdentityResolver = (ref) => subject | null | Promise<...>. The resolver may query your user table and return the subject with its roles and team. Returnnullfor an unknown ref (the session is rejected with 400).
// weavekit.config.ts — auth + identities driven by the customer's user table
export default {
schemaDir: '.',
auth: {
source: async (header) => {
const token = header?.replace(/^Bearer\s+/i, ''); // verify your JWT here
return token === undefined ? null : { id: `agent-${token}`, roles: ['agent'] };
},
},
adapters: {
mcp: {
identities: async (ref) => { // query the customer's user table
const row = await pool.query(
`SELECT id, role, team_id FROM crm_users WHERE id = $1`, [ref]);
if (row.rows.length === 0) return null;
const { id, role, team_id } = row.rows[0];
return { id, roles: [role], ...(team_id ? { teamId: team_id } : {}) };
},
},
},
} satisfies EngineConfig;A resolver wins over the static map when both are provided for the same field. Either way, the tool surface is compiled per identity and RBAC is re-enforced at call time — the double layer never changes.
For the full runnable case (a customer crm_users table, JWT auth source, and MCP end to end), see
the user-table identity practice.
Architecture (src/adapters/mcp)
| File | Responsibility |
|---|---|
generate.ts | RBAC → tool surface + JSON Schema compilation |
tools.ts | tool execution via data-access + audit + error mapping |
introspection.ts | list_objects / describe_object |
guardrails.ts | sliding-window rate limit + alert/audit injection |
session.ts | session model + in-memory store (TTL expiry) |
http.ts | fastify /mcp routes; per-session SDK transport + server wiring |
index.ts | registerMcp assembly (EngineMcpConfig) |
The SDK transport is stateful per session: each session owns one StreamableHTTPServerTransport and
one SDK Server (the SDK server may connect to only one transport). tools/list and tools/call are
served via setRequestHandler, so the surface stays dynamic per identity.
Next
- Practices & operations — integration and deployment walkthroughs
- Existing CRM → MCP end to end
- Plug in your own user store via resolvers
- Docker deployment engine + PostgreSQL
- Reverse proxy + TLS public agents over HTTPS
- MCP host setup Claude Desktop / Cursor / gateway
- Connect an agent local first-run (
weave dev+weave mcp:config)
- Custom tools & guardrails — custom tools + guardrail policies + audit replay
- Audit — the
mcp.tool.*event trail - RBAC — what shapes the tool surface