feat: Expose the assistant tool set over an MCP server#186
Conversation
FlatRun can now serve its assistant's full tool set to external MCP clients, so an IDE, desktop app, or another agent can drive the server through the same capabilities the built-in assistant has. The same tools back both front doors. Every MCP call is authenticated and runs as the calling actor, gated by the same per-tool permission and protected-mode checks the assistant enforces, so a caller can do exactly what their access allows and no more. The endpoint is off unless enabled in config.
Code Review SummaryThis PR implements an MCP (Model Context Protocol) server, allowing external agents to access FlatRun's assistant tools with full authentication and authorization parity. It is a well-integrated feature that leverages existing logic for tool registration and permission gating. 🚀 Key Improvements
💡 Minor Suggestions
|
Enabling or disabling the MCP server now takes effect immediately when the setting is saved, without restarting the agent, so it can be flipped from the settings UI. While disabled, the endpoint refuses calls rather than being absent.
| // toolGinContext builds the minimal gin.Context the tool dispatch expects: a | ||
| // request carrying ctx (for per-tool timeouts) and the actor for permission | ||
| // checks. A nil actor means auth is disabled, as elsewhere in the API. | ||
| func toolGinContext(ctx context.Context, actor *auth.ActorContext) *gin.Context { |
There was a problem hiding this comment.
The manual construction of gin.Context here leaves the Writer field as nil. While current tool handlers might only read from the context, any future logic or middleware (such as a permission check that calls c.AbortWithStatusJSON) will trigger a nil pointer dereference panic when attempting to write to the response. Using gin.CreateTestContext with an httptest.NewRecorder is a safer approach for internal dispatching. Note: This requires importing net/http/httptest.
| func toolGinContext(ctx context.Context, actor *auth.ActorContext) *gin.Context { | |
| func toolGinContext(ctx context.Context, actor *auth.ActorContext) *gin.Context { | |
| req, _ := http.NewRequestWithContext(ctx, http.MethodPost, "/", nil) | |
| w := httptest.NewRecorder() | |
| gc, _ := gin.CreateTestContext(w) | |
| gc.Request = req | |
| if actor != nil { | |
| gc.Set(contextkeys.Actor, actor) | |
| } | |
| return gc | |
| } |
Closes #184.
FlatRun's assistant and an external MCP client are now two front doors onto one
tool set: whatever the assistant can do, an MCP client can do too, subject to the
same authorization.
Every MCP call runs as the calling actor and passes the same per-tool permission
and protected-mode checks the assistant enforces, so access is bounded by the
caller's own permissions. The transport is stateless, so authorization is
re-evaluated on each request rather than fixed when a session opens. The endpoint
is off unless enabled in config.
The MCP protocol layer is the whilesmartgo/mcp bridge over the official Go SDK;
FlatRun supplies only the tool registry and its auth.