1package provider
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "net/http/httptest"
9 "testing"
10)
11
12func TestAnthropicMCPResourceToolNames(t *testing.T) {
13 for _, helper := range []struct{ local, wire string }{
14 {"mcp_list_resources", "ListMcpResourcesTool"},
15 {"mcp_read_resource", "ReadMcpResourceTool"},
16 } {
17 for _, oauth := range []bool{false, true} {
18 t.Run(fmt.Sprintf("%s/oauth=%t", helper.local, oauth), func(t *testing.T) {
19 wireName := helper.local
20 if oauth {
21 wireName = helper.wire
22 }
23 tools := []Tool{
24 {Name: helper.local, Schema: json.RawMessage(`{"type":"object","properties":{}}`)},
25 {Name: "mcp__example__lookup", Schema: json.RawMessage(`{"type":"object","properties":{}}`)},
26 }
27 req := Request{
28 Model: "claude-sonnet-5-5",
29 Tools: tools,
30 Messages: []Message{
31 {Role: RoleUser, Content: []Content{TextBlock{Text: "List resources."}}},
32 {Role: RoleAssistant, Content: []Content{ToolCallBlock{ID: "prior-call", Name: helper.local, Arguments: json.RawMessage(`{}`)}}},
33 {Role: RoleTool, Content: []Content{ToolResultBlock{CallID: "prior-call", Content: []Content{TextBlock{Text: "No resources."}}}}},
34 },
35 }
36 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37 var body struct {
38 Tools []anthTool `json:"tools"`
39 Messages []struct {
40 Content []struct {
41 Type string `json:"type"`
42 Name string `json:"name"`
43 } `json:"content"`
44 } `json:"messages"`
45 }
46 if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
47 t.Error(err)
48 w.WriteHeader(http.StatusBadRequest)
49 return
50 }
51 if len(body.Tools) != 2 || body.Tools[0].Name != wireName || body.Tools[1].Name != "mcp__example__lookup" {
52 t.Errorf("wire tools = %+v, want %q and unchanged server tool", body.Tools, wireName)
53 }
54 foundCall := false
55 for _, message := range body.Messages {
56 for _, block := range message.Content {
57 if block.Type == "tool_use" {
58 foundCall = true
59 if block.Name != wireName {
60 t.Errorf("replayed tool name = %q, want %q", block.Name, wireName)
61 }
62 }
63 }
64 }
65 if !foundCall {
66 t.Error("request lost replayed tool call")
67 }
68 w.Header().Set("Content-Type", "text/event-stream")
69 fmt.Fprintf(w, "event: content_block_start\ndata: {\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"next-call\",\"name\":%q,\"input\":{}}}\n\n", wireName)
70 fmt.Fprint(w, "event: content_block_delta\ndata: {\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{}\"}}\n\n")
71 fmt.Fprint(w, "event: content_block_stop\ndata: {\"index\":0}\n\nevent: message_delta\ndata: {\"delta\":{\"stop_reason\":\"tool_use\"}}\n\nevent: message_stop\ndata: {}\n\n")
72 }))
73 defer srv.Close()
74 client := NewAnthropic("synthetic-key", srv.URL)
75 if oauth {
76 client = NewAnthropicOAuth("synthetic-token", srv.URL)
77 }
78 events, err := client.Stream(context.Background(), req)
79 if err != nil {
80 t.Fatal(err)
81 }
82 started, completed := false, false
83 for event := range events {
84 switch e := event.(type) {
85 case EventToolStart:
86 started = true
87 if e.Name != helper.local {
88 t.Errorf("streamed tool name = %q, want %q", e.Name, helper.local)
89 }
90 case EventDone:
91 completed = true
92 if e.Err != nil || e.Stop != StopToolUse || len(e.Message.Content) != 1 {
93 t.Errorf("unexpected completion: %+v", e)
94 continue
95 }
96 call, ok := e.Message.Content[0].(ToolCallBlock)
97 if !ok || call.Name != helper.local || call.ID != "next-call" || string(call.Arguments) != "{}" {
98 t.Errorf("completed content = %+v, want local helper call", e.Message.Content)
99 }
100 }
101 }
102 if !started || !completed {
103 t.Fatalf("missing events: started=%t completed=%t", started, completed)
104 }
105 if tools[0].Name != helper.local {
106 t.Error("request mutated caller's tool definition")
107 }
108 })
109 }
110 }
111}