mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: expose MCP tool annotations for tool grouping (#23195)
## Summary - add shared MCP annotation metadata to toolsdk tools - emit MCP tool annotations from both coderd and CLI MCP servers - cover annotation serialization in toolsdk, coderd MCP e2e, and CLI MCP tests ## Why - Coder already exposed MCP tools, but it did not populate MCP tool annotation hints (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). - Hosts such as Claude Desktop use those hints to classify and group tools, so without them Coder tools can get lumped together. - This change adds a shared annotation source in `toolsdk` and has both MCP servers emit those hints through `mcp.Tool.Annotations`, avoiding drift between local and remote MCP implementations. ## Testing - Tested locally on Cladue Desktop and the tools are categorized correctly. <table> <tr> <td> Before <td> After <tr> <td> <img width="613" height="183" alt="image" src="https://github.com/user-attachments/assets/29d2e3fb-53bc-4ea7-bdb3-f10df4ef996b" /> <td> <img width="600" height="457" alt="image" src="https://github.com/user-attachments/assets/cc384036-c9a7-4db9-9400-43ad51920ff5" /> </table> Note: Done using Coder Agents, reviewed and tested by human locally
This commit is contained in:
@@ -136,6 +136,12 @@ func mcpFromSDK(sdkTool toolsdk.GenericTool, tb toolsdk.Deps) server.ServerTool
|
||||
Properties: sdkTool.Schema.Properties,
|
||||
Required: sdkTool.Schema.Required,
|
||||
},
|
||||
Annotations: mcp.ToolAnnotation{
|
||||
ReadOnlyHint: mcp.ToBoolPtr(sdkTool.MCPAnnotations.ReadOnlyHint),
|
||||
DestructiveHint: mcp.ToBoolPtr(sdkTool.MCPAnnotations.DestructiveHint),
|
||||
IdempotentHint: mcp.ToBoolPtr(sdkTool.MCPAnnotations.IdempotentHint),
|
||||
OpenWorldHint: mcp.ToBoolPtr(sdkTool.MCPAnnotations.OpenWorldHint),
|
||||
},
|
||||
},
|
||||
Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
@@ -91,21 +91,41 @@ func TestMCPHTTP_E2E_ClientIntegration(t *testing.T) {
|
||||
|
||||
// Verify we have some expected Coder tools
|
||||
var foundTools []string
|
||||
for _, tool := range tools.Tools {
|
||||
var userTool *mcp.Tool
|
||||
var writeFileTool *mcp.Tool
|
||||
for i := range tools.Tools {
|
||||
tool := tools.Tools[i]
|
||||
foundTools = append(foundTools, tool.Name)
|
||||
switch tool.Name {
|
||||
case toolsdk.ToolNameGetAuthenticatedUser:
|
||||
userTool = &tools.Tools[i]
|
||||
case toolsdk.ToolNameWorkspaceWriteFile:
|
||||
writeFileTool = &tools.Tools[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Check for some basic tools that should be available
|
||||
assert.Contains(t, foundTools, toolsdk.ToolNameGetAuthenticatedUser, "Should have authenticated user tool")
|
||||
require.NotNil(t, userTool)
|
||||
require.NotNil(t, writeFileTool)
|
||||
require.NotNil(t, userTool.Annotations.ReadOnlyHint)
|
||||
require.NotNil(t, userTool.Annotations.DestructiveHint)
|
||||
require.NotNil(t, userTool.Annotations.IdempotentHint)
|
||||
require.NotNil(t, userTool.Annotations.OpenWorldHint)
|
||||
assert.True(t, *userTool.Annotations.ReadOnlyHint)
|
||||
assert.False(t, *userTool.Annotations.DestructiveHint)
|
||||
assert.True(t, *userTool.Annotations.IdempotentHint)
|
||||
assert.False(t, *userTool.Annotations.OpenWorldHint)
|
||||
require.NotNil(t, writeFileTool.Annotations.ReadOnlyHint)
|
||||
require.NotNil(t, writeFileTool.Annotations.DestructiveHint)
|
||||
require.NotNil(t, writeFileTool.Annotations.IdempotentHint)
|
||||
require.NotNil(t, writeFileTool.Annotations.OpenWorldHint)
|
||||
assert.False(t, *writeFileTool.Annotations.ReadOnlyHint)
|
||||
assert.True(t, *writeFileTool.Annotations.DestructiveHint)
|
||||
assert.False(t, *writeFileTool.Annotations.IdempotentHint)
|
||||
assert.False(t, *writeFileTool.Annotations.OpenWorldHint)
|
||||
|
||||
// Find and execute the authenticated user tool
|
||||
var userTool *mcp.Tool
|
||||
for _, tool := range tools.Tools {
|
||||
if tool.Name == toolsdk.ToolNameGetAuthenticatedUser {
|
||||
userTool = &tool
|
||||
break
|
||||
}
|
||||
}
|
||||
// Execute the authenticated user tool.
|
||||
require.NotNil(t, userTool, "Expected to find "+toolsdk.ToolNameGetAuthenticatedUser+" tool")
|
||||
|
||||
// Execute the tool
|
||||
|
||||
Reference in New Issue
Block a user