Skip to content

Connect an agent to your app

Every Ramose deployment exposes one MCP endpoint at POST /mcp. It lists exactly three tools—describe, query, and mutate—regardless of how many entities or operations your application contains.

This guide connects to the task app from Build your first Ramose web app.

Web app query · mutate · offline
One deployed model Schema · policy · operations authorization is applied before either caller sees data
Agent describe · query · mutate
The application and MCP endpoint are two doors into the same model, not two backends that must be kept aligned.

In your MCP client, add a Streamable HTTP server with the starter’s URL:

https://your-ramose-origin.example/mcp

Ramose publishes OAuth protected-resource metadata, so clients that support remote MCP authentication can discover the authorization server and start sign-in. The resulting bearer token represents one principal and one authorized root; the client never chooses an internal database or catalog.

For a client that accepts static configuration, the shape is typically:

{
"name": "tasks",
"transport": "streamable-http",
"url": "https://your-ramose-origin.example/mcp",
"headers": {
"Authorization": "Bearer ${RAMOSE_ACCESS_TOKEN}"
}
}

Use a short-lived token from the same identity provider as the web app. Client-specific field names differ; the URL and bearer-token contract do not.

After initialization, tools/list returns only:

describe Browse or search the visible application model
query Execute one bounded QueryDocument against the authorized database
mutate Invoke one exact authorized operation with a durable receipt

The agent begins with describe with describe:

{
"browse": { "kinds": ["entity", "operation"] },
"page": { "limit": 20 }
}

The result contains concise capability cards and an opaque catalogToken. For the starter, the visible cards include the task entity and its createTask and setDone operations. Full input schemas are returned only when the client drills into an exact reference.

The agent can now call query with the exact entity reference returned by describe:

{
"ifCatalog": "cat_opaque",
"query": {
"version": 1,
"from": { "kind": "entity", "name": "task" },
"where": {
"op": "eq",
"path": ["done"],
"value": false
},
"select": {
"id": { "path": ["id"] },
"title": { "path": ["title"] },
"createdAt": { "path": ["createdAt"] }
},
"order": [{ "path": ["createdAt"], "direction": "desc" }],
"page": { "first": 20 }
}
}

The plain-data query is QueryDocumentV1, the same representation emitted by the TypeScript builder. Field paths are arrays; every collection is bounded; continuation cursors are opaque.

Suppose the query returned task task_01H…. The agent marks it done by invoking the exact operation reference from discovery:

{
"ifCatalog": "cat_opaque",
"operation": {
"kind": "operation",
"owner": { "kind": "entity", "name": "task" },
"name": "setDone"
},
"target": { "kind": "entity", "entity": "task", "id": "task_01H…" },
"input": { "done": true },
"invocationId": "0195f4ee-6a6a-7c63-a0bf-4f5d7f8a9d20"
}

mutate does not expose raw writes. Ramose re-resolves the operation, target visibility, input schema, and grant at execution time. An identical retry with the same invocationId returns the original result and receipt. Reusing that id for different input returns invocation_conflict.

The React task list updates from its normal synchronization stream. There is no agent-only callback, webhook, or second permission model. The MCP call and the UI mutation both pass through Task.setDone and the same policy.

ErrorNext action
catalog_changedcall describe again and rebuild the exact request from the new catalog
query_budget_exceedednarrow the projection, filter earlier, lower recursion, or request a smaller page
inaccessiblestop or ask the user for access; missing and unauthorized are intentionally indistinguishable
invocation_conflictgenerate a new invocation id only for a genuinely new intent