refactor(mcp): route buildRequestBodyFromRequest through hooks + full Anthropic format

Problem: callWithRequest/Full/Stream all called client.buildRequestBodyFromRequest
directly (not via hooks), so ClaudeClient could never override it. This meant
tool calling sent OpenAI format to Anthropic (wrong field names, wrong roles).

Changes:

mcp/interface.go
- Add buildRequestBodyFromRequest(*Request) map[string]any to clientHooks
- Improve comments: document what each hook group does and why

mcp/client.go
- All three paths (callWithRequest, callWithRequestFull, CallWithRequestStream)
  now call client.hooks.buildRequestBodyFromRequest — ClaudeClient picks up

mcp/claude_client.go
- Full rewrite with format comparison table in package doc
- buildRequestBodyFromRequest: produces correct Anthropic wire format
    * system prompt → top-level "system" field
    * tools: parameters → input_schema, no "type:function" wrapper
    * tool_choice "auto" → {"type":"auto"} object
    * assistant tool calls → content[{type:tool_use, id, name, input}]
    * role=tool results → role=user content[{type:tool_result,...}]
    * consecutive tool results merged into single user turn
- convertMessagesToAnthropic: handles all three message types
- parseMCPResponseFull: extracts text + tool_use blocks
- parseMCPResponse: delegates to parseMCPResponseFull

All mcp and agent tests pass.
This commit is contained in:
tinkle-community
2026-03-08 17:29:21 +08:00
parent 9fcf44af65
commit ea7b450a7e
3 changed files with 242 additions and 117 deletions

View File

@@ -477,7 +477,7 @@ func (client *Client) CallWithRequestFull(req *Request) (*LLMResponse, error) {
func (client *Client) callWithRequestFull(req *Request) (*LLMResponse, error) {
client.logger.Infof("📡 [%s] Request AI Server (full): BaseURL: %s", client.String(), client.BaseURL)
requestBody := client.buildRequestBodyFromRequest(req)
requestBody := client.hooks.buildRequestBodyFromRequest(req)
jsonData, err := client.hooks.marshalRequestBody(requestBody)
if err != nil {
return nil, err
@@ -512,44 +512,36 @@ func (client *Client) callWithRequest(req *Request) (string, error) {
client.logger.Infof("📡 [%s] Request AI Server with Builder: BaseURL: %s", client.String(), client.BaseURL)
client.logger.Debugf("[%s] Messages count: %d", client.String(), len(req.Messages))
// Build request body (from Request object)
requestBody := client.buildRequestBodyFromRequest(req)
requestBody := client.hooks.buildRequestBodyFromRequest(req)
// Serialize request body
jsonData, err := client.hooks.marshalRequestBody(requestBody)
if err != nil {
return "", err
}
// Build URL
url := client.hooks.buildUrl()
client.logger.Infof("📡 [MCP %s] Request URL: %s", client.String(), url)
// Create HTTP request
httpReq, err := client.hooks.buildRequest(url, jsonData)
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// Send HTTP request
resp, err := client.httpClient.Do(httpReq)
if err != nil {
return "", fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
// Read response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
// Check HTTP status code
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API returned error (status %d): %s", resp.StatusCode, string(body))
}
// Parse response
result, err := client.hooks.parseMCPResponse(body)
if err != nil {
return "", fmt.Errorf("fail to parse AI server response: %w", err)
@@ -651,7 +643,7 @@ func (client *Client) CallWithRequestStream(req *Request, onChunk func(string))
}
req.Stream = true
requestBody := client.buildRequestBodyFromRequest(req)
requestBody := client.hooks.buildRequestBodyFromRequest(req)
jsonData, err := client.hooks.marshalRequestBody(requestBody)
if err != nil {
return "", err