feat: add MCP resource reading and fix HTTP transport hang

- Add ReadResource method to MCP Manager for reading server resources
- Add mcp_read_resource action to MCPBridgeTool so LLM can read MCP
  resources like data type catalogs
- Set DisableStandaloneSSE=true on StreamableClientTransport to prevent
  connection hang when server/proxy doesn't support standalone SSE GET

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
KoheiYamashita 2026-02-23 01:57:05 +09:00
parent 65fd24165a
commit 76f6ce5340
2 changed files with 66 additions and 4 deletions

View file

@ -156,6 +156,42 @@ func (m *Manager) CallTool(ctx context.Context, serverName, toolName string, arg
return text, nil return text, nil
} }
// ReadResource reads a resource from an MCP server by URI.
func (m *Manager) ReadResource(ctx context.Context, serverName, uri string) (string, error) {
inst, err := m.ensureRunning(ctx, serverName)
if err != nil {
return "", err
}
inst.mu.Lock()
defer inst.mu.Unlock()
inst.lastUsed = time.Now()
result, err := inst.session.ReadResource(ctx, &sdkmcp.ReadResourceParams{
URI: uri,
})
if err != nil {
m.handleSessionError(serverName, inst, err)
return "", fmt.Errorf("resources/read %s: %w", uri, err)
}
var parts []string
for _, content := range result.Contents {
if content.Text != "" {
parts = append(parts, content.Text)
} else if len(content.Blob) > 0 {
parts = append(parts, fmt.Sprintf("[blob: %s, %d bytes]", content.MIMEType, len(content.Blob)))
}
}
if len(parts) == 0 {
return "(no content)", nil
}
return strings.Join(parts, "\n"), nil
}
// BuildSummary generates XML for the system prompt using config only (no process start). // BuildSummary generates XML for the system prompt using config only (no process start).
func (m *Manager) BuildSummary() string { func (m *Manager) BuildSummary() string {
m.mu.RLock() m.mu.RLock()
@ -280,8 +316,9 @@ func (m *Manager) ensureRunning(ctx context.Context, serverName string) (*Server
} }
} }
transport = &sdkmcp.StreamableClientTransport{ transport = &sdkmcp.StreamableClientTransport{
Endpoint: cfg.URL, Endpoint: cfg.URL,
HTTPClient: httpClient, HTTPClient: httpClient,
DisableStandaloneSSE: true,
} }
inst.isHTTP = true inst.isHTTP = true

View file

@ -24,7 +24,7 @@ func (t *MCPBridgeTool) Name() string {
} }
func (t *MCPBridgeTool) Description() string { func (t *MCPBridgeTool) Description() string {
return "Interact with MCP (Model Context Protocol) servers. Actions: mcp_list (list available servers), mcp_tools (get server's tool list), mcp_call (call a server tool)" return "Interact with MCP (Model Context Protocol) servers. Actions: mcp_list (list available servers), mcp_tools (get server's tool list), mcp_call (call a server tool), mcp_read_resource (read a resource by URI)"
} }
func (t *MCPBridgeTool) Parameters() map[string]interface{} { func (t *MCPBridgeTool) Parameters() map[string]interface{} {
@ -34,7 +34,7 @@ func (t *MCPBridgeTool) Parameters() map[string]interface{} {
"action": map[string]interface{}{ "action": map[string]interface{}{
"type": "string", "type": "string",
"description": "The MCP action to perform", "description": "The MCP action to perform",
"enum": []string{"mcp_list", "mcp_tools", "mcp_call"}, "enum": []string{"mcp_list", "mcp_tools", "mcp_call", "mcp_read_resource"},
}, },
"server": map[string]interface{}{ "server": map[string]interface{}{
"type": "string", "type": "string",
@ -48,6 +48,10 @@ func (t *MCPBridgeTool) Parameters() map[string]interface{} {
"type": "object", "type": "object",
"description": "Arguments to pass to the tool (for mcp_call)", "description": "Arguments to pass to the tool (for mcp_call)",
}, },
"uri": map[string]interface{}{
"type": "string",
"description": "Resource URI to read (required for mcp_read_resource)",
},
}, },
"required": []string{"action"}, "required": []string{"action"},
} }
@ -66,6 +70,8 @@ func (t *MCPBridgeTool) Execute(ctx context.Context, args map[string]interface{}
return t.getTools(ctx, args) return t.getTools(ctx, args)
case "mcp_call": case "mcp_call":
return t.callTool(ctx, args) return t.callTool(ctx, args)
case "mcp_read_resource":
return t.readResource(ctx, args)
default: default:
return ErrorResult(fmt.Sprintf("unknown action: %s", action)) return ErrorResult(fmt.Sprintf("unknown action: %s", action))
} }
@ -142,3 +148,22 @@ func (t *MCPBridgeTool) callTool(ctx context.Context, args map[string]interface{
return SilentResult(result) return SilentResult(result)
} }
func (t *MCPBridgeTool) readResource(ctx context.Context, args map[string]interface{}) *ToolResult {
server, ok := args["server"].(string)
if !ok || server == "" {
return ErrorResult("server is required for mcp_read_resource")
}
uri, ok := args["uri"].(string)
if !ok || uri == "" {
return ErrorResult("uri is required for mcp_read_resource")
}
result, err := t.manager.ReadResource(ctx, server, uri)
if err != nil {
return ErrorResult(fmt.Sprintf("mcp_read_resource %s/%s failed: %v", server, uri, err))
}
return SilentResult(result)
}