Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit79cd80e

Browse files
authored
feat: add MCP tools for ChatGPT (#19102)
Addressescoder/internal#772.Adds the toolset query parameter to the `/api/experimental/mcp/http` endpoint, which, when set to "chatgpt", exposes new `fetch` and `search` tools compatible with ChatGPT, as described in the[ChatGPT docs](https://platform.openai.com/docs/mcp). These tools areexposed in isolation because in my usage I found that ChatGPT refuses toconnect to Coder if it sees additional MCP tools.<img width="1248" height="908" alt="Screenshot 2025-07-30 at 16 36 56"src="https://github.com/user-attachments/assets/ca31e57b-d18b-4998-9554-7a96a141527a"/>
1 parentd4b4418 commit79cd80e

File tree

6 files changed

+1223
-8
lines changed

6 files changed

+1223
-8
lines changed

‎coderd/mcp/mcp.go‎

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ 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
70+
// Register all available MCP tools with the server excluding:
71+
// - ReportTask - which requires dependencies not available in the remote MCP context
72+
// - ChatGPT search and fetch tools, which are redundant with the standard tools.
7173
func (s*Server)RegisterTools(client*codersdk.Client)error {
7274
ifclient==nil {
7375
returnxerrors.New("client cannot be nil: MCP HTTP server requires authenticated client")
@@ -79,10 +81,36 @@ func (s *Server) RegisterTools(client *codersdk.Client) error {
7981
returnxerrors.Errorf("failed to initialize tool dependencies: %w",err)
8082
}
8183

82-
// Register all available tools, but exclude tools that require dependencies not available in the
83-
// remote MCP context
8484
for_,tool:=rangetoolsdk.All {
85-
iftool.Name==toolsdk.ToolNameReportTask {
85+
// the ReportTask tool requires dependencies not available in the remote MCP context
86+
// the ChatGPT search and fetch tools are redundant with the standard tools.
87+
iftool.Name==toolsdk.ToolNameReportTask||
88+
tool.Name==toolsdk.ToolNameChatGPTSearch||tool.Name==toolsdk.ToolNameChatGPTFetch {
89+
continue
90+
}
91+
92+
s.mcpServer.AddTools(mcpFromSDK(tool,toolDeps))
93+
}
94+
returnnil
95+
}
96+
97+
// ChatGPT tools are the search and fetch tools as defined in https://platform.openai.com/docs/mcp.
98+
// We do not expose any extra ones because ChatGPT has an undocumented "Safety Scan" feature.
99+
// In my experiments, if I included extra tools in the MCP server, ChatGPT would often - but not always -
100+
// refuse to add Coder as a connector.
101+
func (s*Server)RegisterChatGPTTools(client*codersdk.Client)error {
102+
ifclient==nil {
103+
returnxerrors.New("client cannot be nil: MCP HTTP server requires authenticated client")
104+
}
105+
106+
// Create tool dependencies
107+
toolDeps,err:=toolsdk.NewDeps(client)
108+
iferr!=nil {
109+
returnxerrors.Errorf("failed to initialize tool dependencies: %w",err)
110+
}
111+
112+
for_,tool:=rangetoolsdk.All {
113+
iftool.Name!=toolsdk.ToolNameChatGPTSearch&&tool.Name!=toolsdk.ToolNameChatGPTFetch {
86114
continue
87115
}
88116

‎coderd/mcp/mcp_e2e_test.go‎

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,155 @@ func TestMCPHTTP_E2E_OAuth2_EndToEnd(t *testing.T) {
12151215
})
12161216
}
12171217

1218+
funcTestMCPHTTP_E2E_ChatGPTEndpoint(t*testing.T) {
1219+
t.Parallel()
1220+
1221+
// Setup Coder server with authentication
1222+
coderClient,closer,api:=coderdtest.NewWithAPI(t,&coderdtest.Options{
1223+
IncludeProvisionerDaemon:true,
1224+
})
1225+
defercloser.Close()
1226+
1227+
user:=coderdtest.CreateFirstUser(t,coderClient)
1228+
1229+
// Create template and workspace for testing search functionality
1230+
version:=coderdtest.CreateTemplateVersion(t,coderClient,user.OrganizationID,nil)
1231+
coderdtest.AwaitTemplateVersionJobCompleted(t,coderClient,version.ID)
1232+
template:=coderdtest.CreateTemplate(t,coderClient,user.OrganizationID,version.ID)
1233+
1234+
// Create MCP client pointing to the ChatGPT endpoint
1235+
mcpURL:=api.AccessURL.String()+"/api/experimental/mcp/http?toolset=chatgpt"
1236+
1237+
// Configure client with authentication headers using RFC 6750 Bearer token
1238+
mcpClient,err:=mcpclient.NewStreamableHttpClient(mcpURL,
1239+
transport.WithHTTPHeaders(map[string]string{
1240+
"Authorization":"Bearer "+coderClient.SessionToken(),
1241+
}))
1242+
require.NoError(t,err)
1243+
t.Cleanup(func() {
1244+
ifcloseErr:=mcpClient.Close();closeErr!=nil {
1245+
t.Logf("Failed to close MCP client: %v",closeErr)
1246+
}
1247+
})
1248+
1249+
ctx,cancel:=context.WithTimeout(t.Context(),testutil.WaitLong)
1250+
defercancel()
1251+
1252+
// Start client
1253+
err=mcpClient.Start(ctx)
1254+
require.NoError(t,err)
1255+
1256+
// Initialize connection
1257+
initReq:= mcp.InitializeRequest{
1258+
Params: mcp.InitializeParams{
1259+
ProtocolVersion:mcp.LATEST_PROTOCOL_VERSION,
1260+
ClientInfo: mcp.Implementation{
1261+
Name:"test-chatgpt-client",
1262+
Version:"1.0.0",
1263+
},
1264+
},
1265+
}
1266+
1267+
result,err:=mcpClient.Initialize(ctx,initReq)
1268+
require.NoError(t,err)
1269+
require.Equal(t,mcpserver.MCPServerName,result.ServerInfo.Name)
1270+
require.Equal(t,mcp.LATEST_PROTOCOL_VERSION,result.ProtocolVersion)
1271+
require.NotNil(t,result.Capabilities)
1272+
1273+
// Test tool listing - should only have search and fetch tools for ChatGPT
1274+
tools,err:=mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
1275+
require.NoError(t,err)
1276+
require.NotEmpty(t,tools.Tools)
1277+
1278+
// Verify we have exactly the ChatGPT tools and no others
1279+
varfoundTools []string
1280+
for_,tool:=rangetools.Tools {
1281+
foundTools=append(foundTools,tool.Name)
1282+
}
1283+
1284+
// ChatGPT endpoint should only expose search and fetch tools
1285+
assert.Contains(t,foundTools,toolsdk.ToolNameChatGPTSearch,"Should have ChatGPT search tool")
1286+
assert.Contains(t,foundTools,toolsdk.ToolNameChatGPTFetch,"Should have ChatGPT fetch tool")
1287+
assert.Len(t,foundTools,2,"ChatGPT endpoint should only expose search and fetch tools")
1288+
1289+
// Should NOT have other tools that are available in the standard endpoint
1290+
assert.NotContains(t,foundTools,toolsdk.ToolNameGetAuthenticatedUser,"Should not have authenticated user tool")
1291+
assert.NotContains(t,foundTools,toolsdk.ToolNameListWorkspaces,"Should not have list workspaces tool")
1292+
1293+
t.Logf("ChatGPT endpoint tools: %v",foundTools)
1294+
1295+
// Test search tool - search for templates
1296+
varsearchTool*mcp.Tool
1297+
for_,tool:=rangetools.Tools {
1298+
iftool.Name==toolsdk.ToolNameChatGPTSearch {
1299+
searchTool=&tool
1300+
break
1301+
}
1302+
}
1303+
require.NotNil(t,searchTool,"Expected to find search tool")
1304+
1305+
// Execute search for templates
1306+
searchReq:= mcp.CallToolRequest{
1307+
Params: mcp.CallToolParams{
1308+
Name:searchTool.Name,
1309+
Arguments:map[string]any{
1310+
"query":"templates",
1311+
},
1312+
},
1313+
}
1314+
1315+
searchResult,err:=mcpClient.CallTool(ctx,searchReq)
1316+
require.NoError(t,err)
1317+
require.NotEmpty(t,searchResult.Content)
1318+
1319+
// Verify the search result contains our template
1320+
assert.Len(t,searchResult.Content,1)
1321+
iftextContent,ok:=searchResult.Content[0].(mcp.TextContent);ok {
1322+
assert.Equal(t,"text",textContent.Type)
1323+
assert.Contains(t,textContent.Text,template.ID.String(),"Search result should contain our test template")
1324+
t.Logf("Search result: %s",textContent.Text)
1325+
}else {
1326+
t.Errorf("Expected TextContent type, got %T",searchResult.Content[0])
1327+
}
1328+
1329+
// Test fetch tool
1330+
varfetchTool*mcp.Tool
1331+
for_,tool:=rangetools.Tools {
1332+
iftool.Name==toolsdk.ToolNameChatGPTFetch {
1333+
fetchTool=&tool
1334+
break
1335+
}
1336+
}
1337+
require.NotNil(t,fetchTool,"Expected to find fetch tool")
1338+
1339+
// Execute fetch for the template
1340+
fetchReq:= mcp.CallToolRequest{
1341+
Params: mcp.CallToolParams{
1342+
Name:fetchTool.Name,
1343+
Arguments:map[string]any{
1344+
"id":fmt.Sprintf("template:%s",template.ID.String()),
1345+
},
1346+
},
1347+
}
1348+
1349+
fetchResult,err:=mcpClient.CallTool(ctx,fetchReq)
1350+
require.NoError(t,err)
1351+
require.NotEmpty(t,fetchResult.Content)
1352+
1353+
// Verify the fetch result contains template details
1354+
assert.Len(t,fetchResult.Content,1)
1355+
iftextContent,ok:=fetchResult.Content[0].(mcp.TextContent);ok {
1356+
assert.Equal(t,"text",textContent.Type)
1357+
assert.Contains(t,textContent.Text,template.Name,"Fetch result should contain template name")
1358+
assert.Contains(t,textContent.Text,template.ID.String(),"Fetch result should contain template ID")
1359+
t.Logf("Fetch result contains template data")
1360+
}else {
1361+
t.Errorf("Expected TextContent type, got %T",fetchResult.Content[0])
1362+
}
1363+
1364+
t.Logf("ChatGPT endpoint E2E test successful: search and fetch tools working correctly")
1365+
}
1366+
12181367
// Helper function to parse URL safely in tests
12191368
funcmustParseURL(t*testing.T,rawURLstring)*url.URL {
12201369
u,err:=url.Parse(rawURL)

‎coderd/mcp_http.go‎

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package coderd
22

33
import (
4+
"fmt"
45
"net/http"
56

67
"cdr.dev/slog"
@@ -11,7 +12,15 @@ import (
1112
"github.com/coder/coder/v2/codersdk"
1213
)
1314

15+
typeMCPToolsetstring
16+
17+
const (
18+
MCPToolsetStandardMCPToolset="standard"
19+
MCPToolsetChatGPTMCPToolset="chatgpt"
20+
)
21+
1422
// mcpHTTPHandler creates the MCP HTTP transport handler
23+
// It supports a "toolset" query parameter to select the set of tools to register.
1524
func (api*API)mcpHTTPHandler() http.Handler {
1625
returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request) {
1726
// Create MCP server instance for each request
@@ -23,14 +32,30 @@ func (api *API) mcpHTTPHandler() http.Handler {
2332
})
2433
return
2534
}
26-
2735
authenticatedClient:=codersdk.New(api.AccessURL)
2836
// Extract the original session token from the request
2937
authenticatedClient.SetSessionToken(httpmw.APITokenFromRequest(r))
3038

31-
// Register tools with authenticated client
32-
iferr:=mcpServer.RegisterTools(authenticatedClient);err!=nil {
33-
api.Logger.Warn(r.Context(),"failed to register MCP tools",slog.Error(err))
39+
toolset:=MCPToolset(r.URL.Query().Get("toolset"))
40+
// Default to standard toolset if no toolset is specified.
41+
iftoolset=="" {
42+
toolset=MCPToolsetStandard
43+
}
44+
45+
switchtoolset {
46+
caseMCPToolsetStandard:
47+
iferr:=mcpServer.RegisterTools(authenticatedClient);err!=nil {
48+
api.Logger.Warn(r.Context(),"failed to register MCP tools",slog.Error(err))
49+
}
50+
caseMCPToolsetChatGPT:
51+
iferr:=mcpServer.RegisterChatGPTTools(authenticatedClient);err!=nil {
52+
api.Logger.Warn(r.Context(),"failed to register MCP tools",slog.Error(err))
53+
}
54+
default:
55+
httpapi.Write(r.Context(),w,http.StatusBadRequest, codersdk.Response{
56+
Message:fmt.Sprintf("Invalid toolset: %s",toolset),
57+
})
58+
return
3459
}
3560

3661
// Handle the MCP request

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp