Files
computer-playbook/roles/web-app-baserow/files/mcp/tools.json
kevinveenbirkenbach 0b9fe7861a feat(baserow): front the native MCP endpoint with the adapter
The least-privilege half of the criterion could not hold as built.
provision_mcp.py creates the endpoint owner's workspace through
CoreHandler().create_workspace, which makes that account its ADMIN, and
2.3.3 knows only ADMIN and MEMBER - both write table data. There is no
read-only workspace role to demote to, so mutating_tools_enabled: false
bounded nothing while create_rows, update_rows and delete_rows sat in the
served allowlist.

The adapter's contract simply omits them: a client calling one is refused
with DENY_UNKNOWN_TOOL. Omission is the mechanism, since passthrough.py
refuses to load a contract that lists a mutating tool while mutations are
off. The other ten stay recorded under upstream_serves so a version bump
enabling one shows up as drift rather than as a silently wider surface.

The transport was what reverted this twice. Baserow speaks classic
HTTP+SSE, where a GET opens the response stream and the stream names a
second URL for requests, while the adapter only ever spoke Streamable
HTTP. adapter.upstream_transport selects sse.py and defaults to
streamable_http, so no existing provider changes.

The endpoint key stops being a client credential. Clients present
credentials.mcp_bearer to the sidecar; the key reaches the sidecar as
ADAPTER_UPSTREAM_PATH_KEY and is spliced in percent-encoded, so it never
enters the rendered contract and a key carrying a separator cannot open a
path of its own.

Deploy is green: the probe reached list_databases over SSE, the served
surface matched the contract, and the sidecar proved it reaches only its
provider.
2026-08-29 11:08:49 +02:00

71 lines
1.8 KiB
JSON

{
"get_table_schema": {
"description": "Get the field schema for one or more tables.",
"input_schema": {
"properties": {
"table_ids": {
"description": "List of table IDs to get the schema for.",
"items": {
"type": "integer"
},
"type": "array"
}
},
"required": [
"table_ids"
],
"type": "object"
},
"mutating": false
},
"list_databases": {
"description": "List all databases in the workspace. Use this to discover database IDs before listing tables.",
"input_schema": {
"properties": {},
"type": "object"
},
"mutating": false
},
"list_table_rows": {
"description": "List rows from a table with optional search and pagination. Returns rows with user-facing field names as keys.",
"input_schema": {
"properties": {
"page": {
"description": "Page number (1-based).",
"type": "integer"
},
"search": {
"description": "Optional search term to filter rows.",
"type": "string"
},
"size": {
"description": "Maximum number of rows to return.",
"type": "integer"
},
"table_id": {
"description": "The ID of the table to list rows from.",
"type": "integer"
}
},
"required": [
"table_id"
],
"type": "object"
},
"mutating": false
},
"list_tables": {
"description": "List all tables in the workspace, optionally filtered by database.",
"input_schema": {
"properties": {
"database_id": {
"description": "If provided, only return tables from this database.",
"type": "integer"
}
},
"type": "object"
},
"mutating": false
}
}