@@ -4,9 +4,17 @@ import type { Anthropic } from "@anthropic-ai/sdk"
44import { describe , it , expect , beforeEach , vi } from "vitest"
55import { presentAssistantMessage } from "../presentAssistantMessage"
66import { validateToolUse } from "../../tools/validateToolUse"
7+ import { getModeBySlug } from "../../../shared/modes"
78import type { Task } from "../../task/Task"
89
910vi . mock ( "../../task/Task" )
11+ vi . mock ( "../../../shared/modes" , async ( importOriginal ) => {
12+ const actual = await importOriginal < typeof import ( "../../../shared/modes" ) > ( )
13+ return {
14+ ...actual ,
15+ getModeBySlug : vi . fn ( actual . getModeBySlug ) ,
16+ }
17+ } )
1018vi . mock ( "../../tools/validateToolUse" , ( ) => ( {
1119 validateToolUse : vi . fn ( ) ,
1220 isValidToolName : vi . fn ( ( toolName : string ) =>
@@ -53,7 +61,12 @@ interface MockTask {
5361 recordToolUsage : ReturnType < typeof vi . fn >
5462 recordToolError : ReturnType < typeof vi . fn >
5563 toolRepetitionDetector : { check : ReturnType < typeof vi . fn > }
56- providerRef : { deref : ( ) => { getState : ReturnType < typeof vi . fn > } }
64+ providerRef : {
65+ deref : ( ) => {
66+ getState : ReturnType < typeof vi . fn >
67+ getMcpHub ?: ( ) => { findServerNameBySanitizedName : ( name : string ) => string | undefined }
68+ }
69+ }
5770 say : ReturnType < typeof vi . fn >
5871 ask : ReturnType < typeof vi . fn >
5972 pushToolResultToUserContent : ReturnType < typeof vi . fn >
@@ -218,4 +231,80 @@ describe("presentAssistantMessage - tool usage attribution", () => {
218231 expect ( mockTask . recordToolError ) . not . toHaveBeenCalledWith ( "totally_made_up_tool" , expect . anything ( ) )
219232 expect ( mockTask . recordToolUsage ) . not . toHaveBeenCalled ( )
220233 } )
234+
235+ describe ( "native mcp_tool_use block" , ( ) => {
236+ it ( "records exactly one attempt once the MCP tool's own validation passes" , async ( ) => {
237+ mockTask . providerRef = {
238+ deref : ( ) => ( {
239+ getState : vi . fn ( ) . mockResolvedValue ( {
240+ mode : "code" ,
241+ customModes : [ ] ,
242+ } ) ,
243+ getMcpHub : ( ) => ( {
244+ findServerNameBySanitizedName : ( ) => "my_server" ,
245+ } ) ,
246+ } ) ,
247+ }
248+
249+ mockTask . assistantMessageContent = [
250+ {
251+ type : "mcp_tool_use" ,
252+ id : "call_native_mcp" ,
253+ name : "mcp_my_server_do_thing" ,
254+ serverName : "my_server" ,
255+ toolName : "do_thing" ,
256+ arguments : { } ,
257+ partial : false ,
258+ } ,
259+ ]
260+
261+ await presentAssistantMessage ( mockTask as unknown as Task )
262+
263+ expect ( mockTask . recordToolUsage ) . toHaveBeenCalledTimes ( 1 )
264+ expect ( mockTask . recordToolUsage ) . toHaveBeenCalledWith ( "use_mcp_tool" )
265+ expect ( TelemetryService . instance . captureToolUsage ) . toHaveBeenCalledTimes ( 1 )
266+ expect ( TelemetryService . instance . captureToolUsage ) . toHaveBeenCalledWith ( mockTask . taskId , "use_mcp_tool" )
267+ } )
268+
269+ it ( "records no attempt when the MCP server is not on the mode's allow-list" , async ( ) => {
270+ vi . mocked ( getModeBySlug ) . mockReturnValueOnce ( {
271+ slug : "code" ,
272+ name : "Code" ,
273+ roleDefinition : "" ,
274+ groups : [ ] ,
275+ allowedMcpServers : [ "some-other-server" ] ,
276+ } )
277+
278+ mockTask . providerRef = {
279+ deref : ( ) => ( {
280+ getState : vi . fn ( ) . mockResolvedValue ( {
281+ mode : "code" ,
282+ customModes : [ ] ,
283+ } ) ,
284+ getMcpHub : ( ) => ( {
285+ findServerNameBySanitizedName : ( ) => "my_server" ,
286+ } ) ,
287+ } ) ,
288+ }
289+
290+ mockTask . assistantMessageContent = [
291+ {
292+ type : "mcp_tool_use" ,
293+ id : "call_native_mcp_disallowed" ,
294+ name : "mcp_my_server_do_thing" ,
295+ serverName : "my_server" ,
296+ toolName : "do_thing" ,
297+ arguments : { } ,
298+ partial : false ,
299+ } ,
300+ ]
301+
302+ await presentAssistantMessage ( mockTask as unknown as Task )
303+
304+ // The server is disallowed, so the call never reaches onValidated:
305+ // no success attempt is recorded for a call that was never permitted to execute.
306+ expect ( mockTask . recordToolUsage ) . not . toHaveBeenCalled ( )
307+ expect ( TelemetryService . instance . captureToolUsage ) . not . toHaveBeenCalled ( )
308+ } )
309+ } )
221310} )
0 commit comments