TestMCPServerToolInvocation verifies that when a chat has mcp_server_ids set, the chat loop connects to those MCP servers, discovers their tools, and the LLM can invoke them. NOTE: This test uses a raw database.Store (no dbauthz wrapper). The chatd RBAC authorization of GetMCPServerConfigsByIDs (wh
(t *testing.T)
| 7661 | // requires ActionRead on ResourceDeploymentConfig) is covered by |
| 7662 | // the chatd role definition tests, not here. |
| 7663 | func TestMCPServerToolInvocation(t *testing.T) { |
| 7664 | t.Parallel() |
| 7665 | |
| 7666 | db, ps := dbtestutil.NewDB(t) |
| 7667 | ctx := testutil.Context(t, testutil.WaitLong) |
| 7668 | |
| 7669 | // Start a real MCP server that exposes an "echo" tool. |
| 7670 | mcpSrv := mcpserver.NewMCPServer("test-mcp", "1.0.0") |
| 7671 | mcpSrv.AddTools(mcpserver.ServerTool{ |
| 7672 | Tool: mcpgo.NewTool("echo", |
| 7673 | mcpgo.WithDescription("Echoes the input"), |
| 7674 | mcpgo.WithString("input", |
| 7675 | mcpgo.Description("The input string"), |
| 7676 | mcpgo.Required(), |
| 7677 | ), |
| 7678 | ), |
| 7679 | Handler: func(_ context.Context, req mcpgo.CallToolRequest) (*mcpgo.CallToolResult, error) { |
| 7680 | input, _ := req.GetArguments()["input"].(string) |
| 7681 | return mcpgo.NewToolResultText("echo: " + input), nil |
| 7682 | }, |
| 7683 | }) |
| 7684 | mcpHTTP := mcpserver.NewStreamableHTTPServer(mcpSrv) |
| 7685 | mcpTS := httptest.NewServer(mcpHTTP) |
| 7686 | t.Cleanup(mcpTS.Close) |
| 7687 | |
| 7688 | // Track which tool names are sent to the LLM and capture |
| 7689 | // whether the MCP tool result appears in the second call. |
| 7690 | var ( |
| 7691 | callCount atomic.Int32 |
| 7692 | llmToolNames []string |
| 7693 | llmToolsMu sync.Mutex |
| 7694 | foundMCPResult atomic.Bool |
| 7695 | ) |
| 7696 | |
| 7697 | openAIURL := chattest.NewOpenAI(t, func(req *chattest.OpenAIRequest) chattest.OpenAIResponse { |
| 7698 | if !req.Stream { |
| 7699 | return chattest.OpenAINonStreamingResponse("title") |
| 7700 | } |
| 7701 | |
| 7702 | // Record tool names from the first streamed call. |
| 7703 | if callCount.Add(1) == 1 { |
| 7704 | names := make([]string, 0, len(req.Tools)) |
| 7705 | for _, tool := range req.Tools { |
| 7706 | names = append(names, tool.Function.Name) |
| 7707 | } |
| 7708 | llmToolsMu.Lock() |
| 7709 | llmToolNames = names |
| 7710 | llmToolsMu.Unlock() |
| 7711 | |
| 7712 | // Ask the LLM to call the MCP echo tool. |
| 7713 | return chattest.OpenAIStreamingResponse( |
| 7714 | chattest.OpenAIToolCallChunk( |
| 7715 | "test-mcp__echo", |
| 7716 | `{"input":"hello from LLM"}`, |
| 7717 | ), |
| 7718 | ) |
| 7719 | } |
| 7720 |
nothing calls this directly
no test coverage detected