Skip to content

Commit 4deaf0f

Browse files
authored
Merge pull request #3288 from mcowger/mcowger/nativeToolDefinitions
Native JSON MCP Tool Calling
2 parents 5f92e6b + 2925963 commit 4deaf0f

File tree

16 files changed

+689
-105
lines changed

16 files changed

+689
-105
lines changed

.changeset/green-melons-retire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": minor
3+
---
4+
5+
Add Native MCP Support for JSON Tool Calling

apps/kilocode-docs/docs/features/experimental/native-function-calling.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,16 @@ Because of these risks and considerations, this capability is experiment, and of
4141

4242
To enable and use native function calling, consider and perform the following:
4343

44-
1. Ensure you are using a provider that has been enabled in Kilo Code for this experiment. As of Oct 16, 2025, they include:
44+
1. Ensure you are using a provider that has been enabled in Kilo Code for this experiment. As of Oct 21, 2025, they include:
4545

4646
- OpenRouter
4747
- Kilo Code
4848
- LM Studio
4949
- OpenAI Compatible
50+
- Z.ai
51+
- Synthetic
52+
- X.ai
53+
- Chutes
5054

5155
By default, native function calling is _disabled_ for most models. Should you wish to try it, open the Advanced settings for a given provider profile that is included in the testing group.
5256

@@ -55,11 +59,13 @@ Change the Tool Calling Style to `JSON`, and save the profile.
5559
## Caveats
5660

5761
This feature is currently experimental and mostly intended for users interested in contributing to its development.
58-
It is so far only supported when using OpenRouter or Kilo Code providers. There are possible issues including, but not limited to:
5962

60-
- Missing tools
63+
There are possible issues including, but not limited to:
64+
65+
- ~~Missing tools~~: As of Oct 21, all tools are supported
6166
- Tools calls not updating the UI until they are complete
62-
- MCP servers not working
67+
- ~~MCP servers not working~~: As of Oct 21, MCPs are supported
6368
- Errors specific to certain inference providers
69+
- Not all inference providers use servers that are fully compatible with the OpenAI specification. As a result, behavior will vary, even with the same model across providers.
6470

6571
While nearly any provider can be configured via the OpenAI Compatible profile, testers should be aware that this is enabled purely for ease of testing and should be prepared to experience unexpected responses from providers that are not prepared to handle native function calls.

src/core/assistant-message/AssistantMessageParser.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type ToolName, toolNames } from "@roo-code/types"
22
import { TextContent, ToolUse, ToolParamName, toolParamNames } from "../../shared/tools"
33
import { AssistantMessageContent } from "./parseAssistantMessage"
4-
import { NativeToolCall, parseDoubleEncodedParams } from "./kilocode/native-tool-call"
4+
import { extractMcpToolInfo, NativeToolCall, parseDoubleEncodedParams } from "./kilocode/native-tool-call"
55
import Anthropic from "@anthropic-ai/sdk" // kilocode_change
66

77
/**
@@ -119,8 +119,11 @@ export class AssistantMessageParser {
119119
if (toolCall.function?.name) {
120120
const toolName = toolCall.function.name
121121

122-
// Validate that this is a recognized tool name
123-
if (!toolNames.includes(toolName as ToolName)) {
122+
// Check if it's a dynamic MCP tool or a recognized static tool name
123+
const mcpToolInfo = extractMcpToolInfo(toolName)
124+
const isValidTool = mcpToolInfo !== null || toolNames.includes(toolName as ToolName)
125+
126+
if (!isValidTool) {
124127
console.warn("[AssistantMessageParser] Unknown tool name in native call:", toolName)
125128
continue
126129
}
@@ -176,17 +179,46 @@ export class AssistantMessageParser {
176179
// Tool call is complete - convert it to ToolUse format
177180
if (isComplete) {
178181
const toolName = accumulatedCall.function!.name
182+
179183
// Finalize any current text content before adding tool use
180184
if (this.currentTextContent) {
181185
this.currentTextContent.partial = false
182186
this.currentTextContent = undefined
183187
}
184188

189+
// Normalize dynamic MCP tool names to "use_mcp_tool"
190+
// Dynamic tools have format: use_mcp_tool_{serverName}_{toolName}
191+
const mcpToolInfo = extractMcpToolInfo(toolName)
192+
let normalizedToolName: ToolName
193+
let normalizedParams = parsedArgs
194+
195+
if (mcpToolInfo) {
196+
// Dynamic MCP tool - normalize to "use_mcp_tool"
197+
// Tool name format: use_mcp_tool___{serverName}___{toolName}
198+
normalizedToolName = "use_mcp_tool"
199+
200+
// Extract toolInputProps and convert to JSON string for the arguments parameter
201+
// The model provides: { server_name, tool_name, toolInputProps: {...actual args...} }
202+
// We need: { server_name, tool_name, arguments: "{...actual args as JSON string...}" }
203+
const toolInputProps = (parsedArgs as any).toolInputProps || {}
204+
const argumentsJson = JSON.stringify(toolInputProps)
205+
206+
// Add server_name, tool_name, and arguments to params
207+
normalizedParams = {
208+
server_name: parsedArgs.server_name || mcpToolInfo.serverName,
209+
tool_name: parsedArgs.tool_name || mcpToolInfo.toolName,
210+
arguments: argumentsJson,
211+
}
212+
} else {
213+
// Standard tool
214+
normalizedToolName = toolName as ToolName
215+
}
216+
185217
// Create a ToolUse block from the native tool call
186218
const toolUse: ToolUse = {
187219
type: "tool_use",
188-
name: toolName as ToolName,
189-
params: parsedArgs,
220+
name: normalizedToolName,
221+
params: normalizedParams,
190222
partial: false, // Now complete after accumulation
191223
toolUseId: accumulatedCall.id,
192224
}
@@ -207,6 +239,7 @@ export class AssistantMessageParser {
207239
}
208240
}
209241
}
242+
210243
// kilocode_change end
211244

212245
/**

src/core/assistant-message/kilocode/native-tool-call.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { ToolName } from "@roo-code/types"
2+
import { ToolUse } from "../../../shared/tools"
3+
14
/**
25
* Represents a native tool call from OpenAI-compatible APIs
36
*/
@@ -57,3 +60,45 @@ export function parseDoubleEncodedParams(obj: any): any {
5760
// Primitive types (number, boolean, etc.) return as-is
5861
return obj
5962
}
63+
64+
const NATIVE_MCP_TOOL_PREFIX = "use_mcp_tool___"
65+
const NATIVE_MCP_TOOL_SEPARATOR = "___"
66+
67+
/**
68+
* Check if a tool name is a dynamic MCP tool (starts with "use_mcp_tool_")
69+
*/
70+
function isDynamicMcpTool(toolName: string): boolean {
71+
return toolName.startsWith(NATIVE_MCP_TOOL_PREFIX)
72+
}
73+
74+
/**
75+
* Extract server name and tool name from dynamic MCP tool names.
76+
* Format: use_mcp_tool___{serverName}___{toolName}
77+
* Uses triple underscores as separator to allow underscores in tool names.
78+
* Returns null if the format is invalid.
79+
*/
80+
export function extractMcpToolInfo(toolName: string): { serverName: string; toolName: string } | null {
81+
if (!isDynamicMcpTool(toolName)) {
82+
return null
83+
}
84+
85+
// Remove the prefix
86+
const remainder = toolName.slice(NATIVE_MCP_TOOL_PREFIX.length)
87+
88+
// Find first triple underscore to split server name and tool name
89+
90+
const firstSeparatorIndex = remainder.indexOf(NATIVE_MCP_TOOL_SEPARATOR)
91+
92+
if (firstSeparatorIndex === -1) {
93+
return null // Invalid format
94+
}
95+
96+
const serverName = remainder.slice(0, firstSeparatorIndex)
97+
const extractedToolName = remainder.slice(firstSeparatorIndex + NATIVE_MCP_TOOL_SEPARATOR.length)
98+
99+
if (!serverName || !extractedToolName) {
100+
return null // Invalid format
101+
}
102+
103+
return { serverName, toolName: extractedToolName }
104+
}

src/core/prompts/sections/mcp-servers.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export async function getMcpServersSection(
1111
if (!mcpHub) {
1212
return ""
1313
}
14+
// kilocode_change start
15+
if (toolUseStyle === "json") {
16+
return ""
17+
}
18+
// kilocode_change end
1419

1520
const connectedServers =
1621
mcpHub.getServers().length > 0
@@ -68,19 +73,14 @@ ${connectedServers}`
6873
return baseSection
6974
}
7075

71-
let descSection =
76+
return (
7277
baseSection +
7378
`
7479
## Creating an MCP Server
7580
76-
The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. If they do, you should obtain detailed instructions on this topic using the fetch_instructions tool, `
77-
// kilocode_change: toolUseStyle
78-
if (toolUseStyle !== "json") {
79-
descSection += `like this:
81+
The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. If they do, you should obtain detailed instructions on this topic using the fetch_instructions tool, like this:
8082
<fetch_instructions>
8183
<task>create_mcp_server</task>
8284
</fetch_instructions>`
83-
}
84-
85-
return descSection
85+
)
8686
}

src/core/prompts/system.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@ async function generatePrompt(
9191
const [modesSection, mcpServersSection] = await Promise.all([
9292
getModesSection(context, toolUseStyle /*kilocode_change*/),
9393
shouldIncludeMcp
94-
? getMcpServersSection(mcpHub, effectiveDiffStrategy, enableMcpServerCreation)
94+
? getMcpServersSection(
95+
mcpHub,
96+
effectiveDiffStrategy,
97+
enableMcpServerCreation,
98+
toolUseStyle, // kilocode_change
99+
)
95100
: Promise.resolve(""),
96101
])
97102

0 commit comments

Comments
 (0)