Add native authenticated MCP support to TrailBase - #262
Conversation
ignatz
left a comment
There was a problem hiding this comment.
Thanks jumping into the cold water - much appreciated 🙏
To reduce churn, maybe its best if first work out some of the highlevel questions.
| CLANG_PATH = { value = "./.dev-tools/libclang-18/usr/bin/clang-18", relative = true } | ||
| PKG_CONFIG_PATH = { value = "./.dev-tools/geos/usr/lib/x86_64-linux-gnu/pkgconfig", relative = true } | ||
| PKG_CONFIG_SYSROOT_DIR = { value = "./.dev-tools/geos", relative = true } | ||
| PROTOC = { value = "./.cargo/protoc-wrapper.sh", relative = true } |
There was a problem hiding this comment.
I'm going to assume that his is an artifact
| export LD_LIBRARY_PATH="${PROTOBUF_DIR}/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" | ||
| exec "${PROTOBUF_DIR}/usr/bin/protoc" \ | ||
| -I"${PROTOBUF_DIR}/usr/include" \ | ||
| "$@" |
| ```sh | ||
| scripts/bootstrap-local-dev-tools.sh | ||
| cargo check --workspace --all-targets | ||
| ``` |
There was a problem hiding this comment.
Ok, so the above files are not artifacts.
This is a bit surprising since, I would expect most devs to have access to their machines (seems like a reasonable requirement). Otherwise, this is also very deb centric. If you don't have full access, wouldn't one rather develop inside a container? Would love to hear more about the reasoning.
| "summary": "Exchange authorization code for auth tokens.", | ||
| "mcp_support": "call_trailbase_api_operation or trailbase_request", | ||
| "requires_write_permission": True, | ||
| }, |
There was a problem hiding this comment.
Just guessing, wouldn't the MCP also need a POST request definition?
| from typing import Any | ||
| from urllib.parse import quote | ||
|
|
||
| TRAILBASE_API_OPERATIONS: tuple[dict[str, Any], ...] = ( |
There was a problem hiding this comment.
I'm a bit worried that this will get out of sync. Naively, I would have expected the MCP implementation to be part of the main binary running in some dev mode.
| from urllib.parse import quote | ||
|
|
||
| TRAILBASE_API_OPERATIONS: tuple[dict[str, Any], ...] = ( | ||
| { |
There was a problem hiding this comment.
I'm also a bit surprised over the selection of methods. Naively, I would have expected only or mostly admin APIs to be used in dev mode basically as an alternative to the dashboard. Isn't exposing only the public APIs with access protection inherently limiting maybe even useless for dev tasks.
As an example, i would have expected this to be used to drive schema changes.
|
Thanks again for the earlier feedback @ignatz. I’ve substantially redesigned this PR around your suggestions. The MCP server is now part of the main TrailBase binary and runs in the same container and process as TrailBase. It is enabled explicitly with Authentication now uses a browser-based OAuth authorization-code flow with PKCE. MCP clients open TrailBase’s existing login UI, and only current administrators are authorized. MCP access tokens are scoped and audience-bound to the instance’s MCP operations are routed through TrailBase’s existing in-process admin handlers to reduce drift and provide the administrative functionality discussed in the review, including schema changes, tables, indexes, triggers, Record APIs, configuration, users, files, backups, jobs, OAuth providers, and WASM components. Focused tools are also included for SQL, schema discovery, and configuration. I tested the complete flow with a disposable local TrailBase instance and RustRover via The PR title and description have been updated to reflect the new implementation. I’d appreciate another review when you have time, particularly around whether the native integration and admin-tool approach now align with what you had in mind. |
🙏 gave it a quick skim and it looks much closer to what I had in mind. I still have a few questions, some of it may just be my ignorance showing.
That's cool and very interesting to me. Naively I was expecting folks would trust their agents and give them the credentials to do the authentication on their behalf but arguably this would be nicer to hand the agent tokens only. Will have to look a bit more into how it's wired up.
I'm a bit confused. Originally, i thought MCP endpoints would act as actuators (i.e. mediate the action) but then based on your original proposal I think I learned that MCP servers only share metadata, which is consumed and then used to talk to the actual endpoints (which in hindsight makes sense). Maybe you can do either, maybe you could shed some light on what the flow is, i.e. after calling the MCP and learning about the "list tables" tool, which endpoint does it call. At the end of the day, I'm wondering if OpenAPI data would be enough thus allowing us to get rid of any tool duplication?
I'll definitely will need to look into how to run and validate this myself in order to squelch my confusion and be able to maintain this. FTR: I hadn't heard back so I've started some work around making OpenAPI better integrated and more complete hoping that this would also trivialize an MCP integration. Independently, I was wondering about the desired execution model, which will depend on whether the MCP only serves metadata or actual data. If it's the former, it may make sense to colocate in the same binary but not necessarily in the same process. Specifically, should it be: or i.e. run a second process that serves the metadata. I don't have experience, would be interesting to hear what others do. Thanks so much for this work. |
|
Thanks for taking another look — these are good questions, and I think the main source of confusion is that MCP supports both descriptive and executable primitives. The admin request is dispatched through the in-process Axum router using the same AppState; it does not make a second HTTP request. execute_sql similarly dispatches to the existing POST /query admin handler. The generic call_admin_api tool accepts an HTTP method, admin-relative path, and body, then invokes the matching existing admin handler in-process.
I would be happy to align this PR with the OpenAPI work. The focused tools currently contain very little independent business logic: most are convenience wrappers around existing admin handlers. The old hand-maintained operation catalog has been removed.
because the MCP tools execute real administrative operations and can reuse the existing router, application state, authorization, configuration, and schema-refresh behavior directly. A separate:
process would either need to call the running TrailBase server over HTTP and manage separate administrative credentials, or open the same depot concurrently. The former starts to recreate the sidecar architecture, while the latter seems undesirable for database ownership and consistency. The focused tests can also be run with:
I’m very open to reducing or generating the tool surface once I understand the direction of the improved admin OpenAPI integration. In particular, I’d appreciate your preference between a generic OpenAPI-backed actuator and individually generated MCP tools. |
|
Thanks for taking the time to discuss this with me - very much appreciated 🙏 - and I'm sorry I'm not already more up to speed.
You're saying the agent calls the MCP and the MCP proxies the action. My confusion also stems from browsing the landscape and stumbling over the code examples on https://crates.io/crates/rmcp-openapi, which seemed rather declarative but looking closer it does exactly what you said: "rmcp-openapi acts as a proxy between MCP clients and OpenAPI services..." 🙏
With my newfound understanding that the MCP actuates the action, this makes sense. Let me give the code a more proper look 🙏 |
| ### Public HTTPS URL | ||
|
|
||
| Use this for Cloudflare Tunnel, a reverse proxy, or another deployed TrailBase | ||
| instance: |
There was a problem hiding this comment.
For my understanding, should an MCP ever be running in production? Naively, I would expected: no
| cargo test -p trailbase --lib mcp::tests | ||
| ``` | ||
|
|
||
| For an isolated manual test: |
There was a problem hiding this comment.
? - Isn't this just the dev setup, i.e. the way you'd normally run your mcp
|
I squashed and merged all the latest changes into a single commit: #276. I'll use this as a base for the review, there's just less auxiliary changes. I'm happy to discuss here or there. Tentatively here to preserve the discussion but feel free to comment on either. |
|
If we'd focus on dev-only use-cases (at least for now), and looking at mcp-remote:
makes me wonder if (I'm also just liberally collecting my thoughts here as I'm reading up on the individual aspects) |
Summary
Adds optional, native MCP support directly to the TrailBase binary and existing Docker container. MCP is served from
/mcpon the TrailBase admin server and is enabled explicitly with--mcp.This replaces the earlier FastMCP sidecar design. There is no second container, second port, copied bearer token, shared depot mount, or hand-maintained public API catalog.
Authentication and security
mcp-remotemcpand audience-bound to the instance/mcpURLTools
call_admin_api: dispatches to TrailBase existing in-process admin router to avoid API driftexecute_sql: runs SQL using TrailBase administrative query handlinglist_tables: reads current tables, views, indexes, and triggersget_config: returns redacted TrailBase configurationupdate_config: validates and updates configuration while preserving secretsThis gives trusted administrators the same administrative surface used by the dashboard, including schema, table, Record API, user, file, backup, job, OAuth-provider, and WASM-component operations.
Deployment
OAuth-capable clients connect to
https://trailbase.example.com/mcp. The documentation includes localhost,mcp-remote, Docker, Portainer, Cloudflare Tunnel, reverse-proxy, and direct bearer-token examples.Validation
mcp-remoteThis redesign addresses the review feedback by moving MCP into the main binary, routing operations through existing admin handlers, avoiding a separately maintained operation catalog, and supporting administrative schema changes.