Skip to content

Commit a21dcd6

Browse files
SeaL773claude
authored andcommitted
fix: rename blocked tool names instead of blanket mcp_ prefixing
Anthropic's OAuth billing validation rejects certain tool names (todowrite, background_output, background_cancel) when multiple tools are present, returning spurious 400 "out of extra usage" errors. Replace the blanket mcp_ prefix strategy with a targeted rename map for known blocked names. Also add explicit baseURL to ensure correct API endpoint construction. Fixes griffinmartin#190 Related: griffinmartin#189 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c0d8db5 commit a21dcd6

2 files changed

Lines changed: 35 additions & 16 deletions

File tree

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ const plugin: Plugin = async () => {
249249

250250
return {
251251
apiKey: "",
252+
baseURL: "https://api.anthropic.com/v1",
252253
async fetch(input: RequestInfo | URL, init?: RequestInit) {
253254
const latest = await getCachedCredentials()
254255
if (!latest) {

src/transforms.ts

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -206,30 +206,44 @@ export function transformBody(
206206
}
207207
}
208208

209+
// Anthropic's OAuth billing validation rejects certain tool names when
210+
// multiple tools are present. Skip blanket mcp_ prefixing and only
211+
// rename the specific blocked names to safe alternatives.
212+
const BLOCKED_TOOL_NAMES: Record<string, string> = {
213+
"todowrite": "TodoWrite",
214+
"background_output": "backgroundOutput",
215+
"background_cancel": "backgroundCancel",
216+
}
209217
if (Array.isArray(parsed.tools)) {
210-
parsed.tools = parsed.tools.map((tool) => ({
211-
...tool,
212-
name: tool.name ? `${TOOL_PREFIX}${tool.name}` : tool.name,
213-
}))
218+
parsed.tools = parsed.tools.map((tool) => {
219+
if (typeof tool.name === "string" && BLOCKED_TOOL_NAMES[tool.name]) {
220+
return { ...tool, name: BLOCKED_TOOL_NAMES[tool.name] }
221+
}
222+
return tool
223+
})
214224
}
215225

216226
if (Array.isArray(parsed.messages)) {
217227
parsed.messages = parsed.messages.map((message) => {
218-
if (!Array.isArray(message.content)) {
219-
return message
220-
}
221-
228+
if (!Array.isArray(message.content)) return message
229+
const hasBlocked = message.content.some(
230+
(block) =>
231+
block.type === "tool_use" &&
232+
typeof block.name === "string" &&
233+
BLOCKED_TOOL_NAMES[block.name],
234+
)
235+
if (!hasBlocked) return message
222236
return {
223237
...message,
224238
content: message.content.map((block) => {
225-
if (block.type !== "tool_use" || typeof block.name !== "string") {
226-
return block
227-
}
228-
229-
return {
230-
...block,
231-
name: `${TOOL_PREFIX}${block.name}`,
239+
if (
240+
block.type === "tool_use" &&
241+
typeof block.name === "string" &&
242+
BLOCKED_TOOL_NAMES[block.name]
243+
) {
244+
return { ...block, name: BLOCKED_TOOL_NAMES[block.name] }
232245
}
246+
return block
233247
}),
234248
}
235249
})
@@ -246,7 +260,11 @@ export function transformBody(
246260
}
247261

248262
export function stripToolPrefix(text: string): string {
249-
return text.replace(/"name"\s*:\s*"mcp_([^"]+)"/g, '"name": "$1"')
263+
return text
264+
.replace(/"name"\s*:\s*"mcp_([^"]+)"/g, '"name": "$1"')
265+
.replace(/"name"\s*:\s*"TodoWrite"/g, '"name": "todowrite"')
266+
.replace(/"name"\s*:\s*"backgroundOutput"/g, '"name": "background_output"')
267+
.replace(/"name"\s*:\s*"backgroundCancel"/g, '"name": "background_cancel"')
250268
}
251269

252270
export function transformResponseStream(response: Response): Response {

0 commit comments

Comments
 (0)