diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f7fe156e7..cca5513a831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Docs: https://docs.openclaw.ai - Models/OpenRouter: hide missing-auth direct provider rows in `/model status` when they are only duplicated by a nested OpenRouter model id such as `openrouter/google/...`, while preserving explicitly configured direct providers. Fixes #62317. - Models: preserve an explicitly selected provider/model such as `opencode-go/deepseek-v4-pro` when another provider owns the same bare model alias. Fixes #79325. - Models/config: explain missing `models.providers..models[]` registration when a model exists only in `agents.defaults.models`, instead of returning a bare unknown-model error. Fixes #80089. +- MCP/tools: prefix bundle MCP server/tool fragments that would start with digits, keeping generated tool names valid for Moonshot/Kimi and other strict providers. Fixes #79179. - Kimi Code: use Kimi's stable `kimi-for-coding` API model id in bundled catalog, onboarding, and docs while normalizing legacy `kimi-code` and `k2p5` refs. Fixes #79965. - Volcengine/Kimi: strip provider-unsupported tool schema length and item constraint keywords for direct and coding-plan models so hosted Kimi runs do not reject message tools with `minLength`. Fixes #38817. - DeepSeek: backfill V4 `reasoning_content` replay fields for unowned OpenAI-compatible proxy providers, preventing follow-up request failures outside the bundled DeepSeek and OpenRouter routes. Fixes #79608. diff --git a/docs/plugins/bundles.md b/docs/plugins/bundles.md index ab6d376d1d5..62688f96d56 100644 --- a/docs/plugins/bundles.md +++ b/docs/plugins/bundles.md @@ -165,6 +165,8 @@ OpenClaw registers bundle MCP tools with provider-safe names in the form `memory_search` tool registers as `vigil-harbor__memory_search`. - characters outside `A-Za-z0-9_-` are replaced with `-` +- fragments that would start with a non-letter get a letter prefix, so numeric + server keys such as `12306` become provider-safe tool prefixes - server prefixes are capped at 30 characters - full tool names are capped at 64 characters - empty server names fall back to `mcp` diff --git a/src/agents/pi-bundle-mcp-names.test.ts b/src/agents/pi-bundle-mcp-names.test.ts index 1eb60393619..0675c8ec2e5 100644 --- a/src/agents/pi-bundle-mcp-names.test.ts +++ b/src/agents/pi-bundle-mcp-names.test.ts @@ -14,6 +14,20 @@ describe("pi bundle MCP names", () => { expect(sanitizeServerName("vigil:harbor", usedNames)).toBe("vigil-harbor-2"); }); + it("keeps server and tool fragments provider-safe when they start with digits", () => { + const usedNames = new Set(); + const serverName = sanitizeServerName("12306", usedNames); + + expect(serverName).toBe("mcp-12306"); + expect( + buildSafeToolName({ + serverName, + toolName: "2024-query", + reservedNames: new Set(), + }), + ).toBe(`mcp-12306${TOOL_NAME_SEPARATOR}tool-2024-query`); + }); + it("builds provider-safe tool names and avoids collisions", () => { const reservedNames = normalizeReservedToolNames(["memory__status"]); diff --git a/src/agents/pi-bundle-mcp-names.ts b/src/agents/pi-bundle-mcp-names.ts index 7345ee86a72..c938429d4c4 100644 --- a/src/agents/pi-bundle-mcp-names.ts +++ b/src/agents/pi-bundle-mcp-names.ts @@ -11,10 +11,11 @@ const TOOL_NAME_MAX_TOTAL = 64; function sanitizeToolFragment(raw: string, fallback: string, maxChars?: number): string { const cleaned = raw.trim().replace(TOOL_NAME_SAFE_RE, "-"); const normalized = cleaned || fallback; + const providerSafe = /^[A-Za-z]/.test(normalized) ? normalized : `${fallback}-${normalized}`; if (!maxChars) { - return normalized; + return providerSafe; } - return normalized.length > maxChars ? normalized.slice(0, maxChars) : normalized; + return providerSafe.length > maxChars ? providerSafe.slice(0, maxChars) : providerSafe; } export function sanitizeServerName(raw: string, usedNames: Set): string {