Skip to content

Commit dbac33e

Browse files
committed
feat: search and fetch mcp tools
1 parent 219d1b4 commit dbac33e

File tree

6 files changed

+391
-14
lines changed

6 files changed

+391
-14
lines changed

coderd/coderd.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,8 +996,12 @@ func New(options *Options) *API {
996996
r.Use(
997997
httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentOAuth2, codersdk.ExperimentMCPServerHTTP),
998998
)
999+
9991000
// MCP HTTP transport endpoint with mandatory authentication
1000-
r.Mount("/http", api.mcpHTTPHandler())
1001+
r.Mount("/http", api.standardMCPHTTPHandler())
1002+
// ChatGPT gets a dedicated endpoint with a limited set of tools.
1003+
// See the docstring of the chatgptMCPHTTPHandler for more details.
1004+
r.Mount("/chatgpt", api.chatgptMCPHTTPHandler())
10011005
})
10021006
})
10031007

coderd/mcp/mcp.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
6767
s.streamableServer.ServeHTTP(w, r)
6868
}
6969

70-
// RegisterTools registers all available MCP tools with the server
71-
func (s *Server) RegisterTools(client *codersdk.Client) error {
70+
// RegisterTools registers MCP tools with the server
71+
func (s *Server) RegisterTools(client *codersdk.Client, tools []toolsdk.GenericTool) error {
7272
if client == nil {
7373
return xerrors.New("client cannot be nil: MCP HTTP server requires authenticated client")
7474
}
@@ -79,13 +79,7 @@ func (s *Server) RegisterTools(client *codersdk.Client) error {
7979
return xerrors.Errorf("failed to initialize tool dependencies: %w", err)
8080
}
8181

82-
// Register all available tools, but exclude tools that require dependencies not available in the
83-
// remote MCP context
84-
for _, tool := range toolsdk.All {
85-
if tool.Name == toolsdk.ToolNameReportTask {
86-
continue
87-
}
88-
82+
for _, tool := range tools {
8983
s.mcpServer.AddTools(mcpFromSDK(tool, toolDeps))
9084
}
9185
return nil

coderd/mcp/mcp_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,13 @@ func TestMCPHTTP_ToolRegistration(t *testing.T) {
110110
require.NoError(t, err)
111111

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

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

122122
// Verify that all expected tools are available in the toolsdk

coderd/mcp_http.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99
"github.com/coder/coder/v2/coderd/httpmw"
1010
"github.com/coder/coder/v2/coderd/mcp"
1111
"github.com/coder/coder/v2/codersdk"
12+
"github.com/coder/coder/v2/codersdk/toolsdk"
1213
)
1314

1415
// mcpHTTPHandler creates the MCP HTTP transport handler
15-
func (api *API) mcpHTTPHandler() http.Handler {
16+
func (api *API) mcpHTTPHandler(tools []toolsdk.GenericTool) http.Handler {
1617
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1718
// Create MCP server instance for each request
1819
mcpServer, err := mcp.NewServer(api.Logger.Named("mcp"))
@@ -29,11 +30,45 @@ func (api *API) mcpHTTPHandler() http.Handler {
2930
authenticatedClient.SetSessionToken(httpmw.APITokenFromRequest(r))
3031

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

3637
// Handle the MCP request
3738
mcpServer.ServeHTTP(w, r)
3839
})
3940
}
41+
42+
// standardMCPHTTPHandler sets up the MCP HTTP transport handler for the standard tools.
43+
// Standard tools are all tools except for the report task, ChatGPT search, and ChatGPT fetch tools.
44+
func (api *API) standardMCPHTTPHandler() http.Handler {
45+
mcpTools := []toolsdk.GenericTool{}
46+
// Register all available tools, but exclude:
47+
// - ReportTask - which requires dependencies not available in the remote MCP context
48+
// - ChatGPT search and fetch tools, which are redundant with the standard tools.
49+
for _, tool := range toolsdk.All {
50+
if tool.Name == toolsdk.ToolNameReportTask ||
51+
tool.Name == toolsdk.ToolNameChatGPTSearch || tool.Name == toolsdk.ToolNameChatGPTFetch {
52+
continue
53+
}
54+
mcpTools = append(mcpTools, tool)
55+
}
56+
return api.mcpHTTPHandler(mcpTools)
57+
}
58+
59+
// chatgptMCPHTTPHandler sets up the MCP HTTP transport handler for the ChatGPT tools.
60+
// ChatGPT tools are the search and fetch tools as defined in https://platform.openai.com/docs/mcp.
61+
// We do not expose any extra ones because ChatGPT has an undocumented "Safety Scan" feature.
62+
// In my experiments, if I included extra tools in the MCP server, ChatGPT would refuse
63+
// to add Coder as a connector.
64+
func (api *API) chatgptMCPHTTPHandler() http.Handler {
65+
mcpTools := []toolsdk.GenericTool{}
66+
// Register only the ChatGPT search and fetch tools.
67+
for _, tool := range toolsdk.All {
68+
if !(tool.Name == toolsdk.ToolNameChatGPTSearch || tool.Name == toolsdk.ToolNameChatGPTFetch) {
69+
continue
70+
}
71+
mcpTools = append(mcpTools, tool)
72+
}
73+
return api.mcpHTTPHandler(mcpTools)
74+
}

0 commit comments

Comments
 (0)