mcpHTTPHandler creates the MCP HTTP transport handler It supports a "toolset" query parameter to select the set of tools to register.
()
| 29 | // mcpHTTPHandler creates the MCP HTTP transport handler |
| 30 | // It supports a "toolset" query parameter to select the set of tools to register. |
| 31 | func (api *API) mcpHTTPHandler() http.Handler { |
| 32 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 33 | // Create MCP server instance for each request |
| 34 | mcpServer, err := mcp.NewServer(api.Logger.Named("mcp")) |
| 35 | if err != nil { |
| 36 | api.Logger.Error(r.Context(), "failed to create MCP server", slog.Error(err)) |
| 37 | httpapi.Write(r.Context(), w, http.StatusInternalServerError, codersdk.Response{ |
| 38 | Message: "MCP server initialization failed", |
| 39 | }) |
| 40 | return |
| 41 | } |
| 42 | // Extract the original session token from the request |
| 43 | authenticatedClient := codersdk.New(api.AccessURL, |
| 44 | codersdk.WithSessionToken(httpmw.APITokenFromRequest(r))) |
| 45 | |
| 46 | // Wrap the agent connection function to enforce ActionSSH |
| 47 | // on the workspace. Without this check, a user who can read |
| 48 | // a workspace but lacks SSH permission could still execute |
| 49 | // commands through MCP tools. |
| 50 | toolOpt := toolsdk.WithAgentConnFunc(func(ctx context.Context, agentID uuid.UUID) (workspacesdk.AgentConn, func(), error) { |
| 51 | if api.Entitlements.Enabled(codersdk.FeatureBrowserOnly) { |
| 52 | return nil, nil, xerrors.New("non-browser connections are disabled") |
| 53 | } |
| 54 | // Use system context for the lookup because the tool |
| 55 | // handler context does not carry a dbauthz actor. The |
| 56 | // real authorization happens in the Authorize call below. |
| 57 | //nolint:gocritic // The system query only fetches the workspace |
| 58 | // object so we can perform an ActionSSH check against it |
| 59 | // with the real user's roles via api.Authorize. |
| 60 | workspace, err := api.Database.GetWorkspaceByAgentID(dbauthz.AsSystemRestricted(ctx), agentID) |
| 61 | if err != nil { |
| 62 | return nil, nil, xerrors.Errorf("get workspace by agent ID: %w", err) |
| 63 | } |
| 64 | // Enforce the same ActionSSH check that the coordinate |
| 65 | // endpoint uses (workspaceagents.go:1317). |
| 66 | if !api.Authorize(r, policy.ActionSSH, workspace) { |
| 67 | return nil, nil, xerrors.New("unauthorized: you do not have SSH access to this workspace") |
| 68 | } |
| 69 | return api.agentProvider.AgentConn(ctx, agentID) |
| 70 | }) |
| 71 | |
| 72 | toolset := MCPToolset(r.URL.Query().Get("toolset")) |
| 73 | // Default to standard toolset if no toolset is specified. |
| 74 | if toolset == "" { |
| 75 | toolset = MCPToolsetStandard |
| 76 | } |
| 77 | |
| 78 | switch toolset { |
| 79 | case MCPToolsetStandard: |
| 80 | if err := mcpServer.RegisterTools(authenticatedClient, toolOpt); err != nil { |
| 81 | api.Logger.Warn(r.Context(), "failed to register MCP tools", slog.Error(err)) |
| 82 | } |
| 83 | case MCPToolsetChatGPT: |
| 84 | if err := mcpServer.RegisterChatGPTTools(authenticatedClient, toolOpt); err != nil { |
| 85 | api.Logger.Warn(r.Context(), "failed to register MCP tools", slog.Error(err)) |
| 86 | } |
| 87 | default: |
| 88 | httpapi.Write(r.Context(), w, http.StatusBadRequest, codersdk.Response{ |
no test coverage detected