Skip to content

feat: add MCP tools for ChatGPT #19102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 4, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: search and fetch mcp tools
  • Loading branch information
hugodutka committed Aug 4, 2025
commit a61194dfa938ceb3411d5418152273e45c4fd04d
6 changes: 5 additions & 1 deletion coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -996,8 +996,12 @@ func New(options *Options) *API {
r.Use(
httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentOAuth2, codersdk.ExperimentMCPServerHTTP),
)

// MCP HTTP transport endpoint with mandatory authentication
r.Mount("/http", api.mcpHTTPHandler())
r.Mount("/http", api.standardMCPHTTPHandler())
// ChatGPT gets a dedicated endpoint with a limited set of tools.
// See the docstring of the chatgptMCPHTTPHandler for more details.
r.Mount("/chatgpt", api.chatgptMCPHTTPHandler())
})
})

Expand Down
12 changes: 3 additions & 9 deletions coderd/mcp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.streamableServer.ServeHTTP(w, r)
}

// RegisterTools registers all available MCP tools with the server
func (s *Server) RegisterTools(client *codersdk.Client) error {
// RegisterTools registers MCP tools with the server
func (s *Server) RegisterTools(client *codersdk.Client, tools []toolsdk.GenericTool) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we leave the function as it was and add a new function called RegisterChatGPTTools instead?

This would benefit us by allowing us to add exceptions for ChatGPT more easily on the server structure, and keeping the existing MCP endpoint and function calls as they are/will be.

(Also, we can then write a unit test and assert that a ChatGPT MCP server only has those two tools that we expect)

if client == nil {
return xerrors.New("client cannot be nil: MCP HTTP server requires authenticated client")
}
Expand All @@ -79,13 +79,7 @@ func (s *Server) RegisterTools(client *codersdk.Client) error {
return xerrors.Errorf("failed to initialize tool dependencies: %w", err)
}

// Register all available tools, but exclude tools that require dependencies not available in the
// remote MCP context
for _, tool := range toolsdk.All {
if tool.Name == toolsdk.ToolNameReportTask {
continue
}

for _, tool := range tools {
s.mcpServer.AddTools(mcpFromSDK(tool, toolDeps))
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions coderd/mcp/mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ func TestMCPHTTP_ToolRegistration(t *testing.T) {
require.NoError(t, err)

// Test registering tools with nil client should return error
err = server.RegisterTools(nil)
err = server.RegisterTools(nil, toolsdk.All)
require.Error(t, err)
require.Contains(t, err.Error(), "client cannot be nil", "Should reject nil client with appropriate error message")

// Test registering tools with valid client should succeed
client := &codersdk.Client{}
err = server.RegisterTools(client)
err = server.RegisterTools(client, toolsdk.All)
require.NoError(t, err)

// Verify that all expected tools are available in the toolsdk
Expand Down
39 changes: 37 additions & 2 deletions coderd/mcp_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/mcp"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/toolsdk"
)

// mcpHTTPHandler creates the MCP HTTP transport handler
func (api *API) mcpHTTPHandler() http.Handler {
func (api *API) mcpHTTPHandler(tools []toolsdk.GenericTool) http.Handler {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could simplify this a bit by making it a boolean called isChatGPT bool, then calling the appropriate Register function (either .RegisterTools() or .RegisterChatGPTTools()).

This also makes the handler less complex.

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Create MCP server instance for each request
mcpServer, err := mcp.NewServer(api.Logger.Named("mcp"))
Expand All @@ -29,11 +30,45 @@ func (api *API) mcpHTTPHandler() http.Handler {
authenticatedClient.SetSessionToken(httpmw.APITokenFromRequest(r))

// Register tools with authenticated client
if err := mcpServer.RegisterTools(authenticatedClient); err != nil {
if err := mcpServer.RegisterTools(authenticatedClient, tools); err != nil {
api.Logger.Warn(r.Context(), "failed to register MCP tools", slog.Error(err))
}

// Handle the MCP request
mcpServer.ServeHTTP(w, r)
})
}

// standardMCPHTTPHandler sets up the MCP HTTP transport handler for the standard tools.
// Standard tools are all tools except for the report task, ChatGPT search, and ChatGPT fetch tools.
func (api *API) standardMCPHTTPHandler() http.Handler {
mcpTools := []toolsdk.GenericTool{}
// Register all available tools, but exclude:
// - ReportTask - which requires dependencies not available in the remote MCP context
// - ChatGPT search and fetch tools, which are redundant with the standard tools.
for _, tool := range toolsdk.All {
if tool.Name == toolsdk.ToolNameReportTask ||
tool.Name == toolsdk.ToolNameChatGPTSearch || tool.Name == toolsdk.ToolNameChatGPTFetch {
continue
}
mcpTools = append(mcpTools, tool)
}
return api.mcpHTTPHandler(mcpTools)
}

// chatgptMCPHTTPHandler sets up the MCP HTTP transport handler for the ChatGPT tools.
// ChatGPT tools are the search and fetch tools as defined in https://platform.openai.com/docs/mcp.
// We do not expose any extra ones because ChatGPT has an undocumented "Safety Scan" feature.
// In my experiments, if I included extra tools in the MCP server, ChatGPT would refuse
// to add Coder as a connector.
func (api *API) chatgptMCPHTTPHandler() http.Handler {
mcpTools := []toolsdk.GenericTool{}
// Register only the ChatGPT search and fetch tools.
for _, tool := range toolsdk.All {
if !(tool.Name == toolsdk.ToolNameChatGPTSearch || tool.Name == toolsdk.ToolNameChatGPTFetch) {
continue
}
mcpTools = append(mcpTools, tool)
}
return api.mcpHTTPHandler(mcpTools)
}
Loading