|
@@ -2,12 +2,12 @@ import {
|
|
|
sanitizeMcpName,
|
|
sanitizeMcpName,
|
|
|
buildMcpToolName,
|
|
buildMcpToolName,
|
|
|
parseMcpToolName,
|
|
parseMcpToolName,
|
|
|
- decodeMcpName,
|
|
|
|
|
normalizeMcpToolName,
|
|
normalizeMcpToolName,
|
|
|
|
|
+ normalizeForComparison,
|
|
|
|
|
+ toolNamesMatch,
|
|
|
isMcpTool,
|
|
isMcpTool,
|
|
|
MCP_TOOL_SEPARATOR,
|
|
MCP_TOOL_SEPARATOR,
|
|
|
MCP_TOOL_PREFIX,
|
|
MCP_TOOL_PREFIX,
|
|
|
- HYPHEN_ENCODING,
|
|
|
|
|
} from "../mcp-name"
|
|
} from "../mcp-name"
|
|
|
|
|
|
|
|
describe("mcp-name utilities", () => {
|
|
describe("mcp-name utilities", () => {
|
|
@@ -16,16 +16,58 @@ describe("mcp-name utilities", () => {
|
|
|
expect(MCP_TOOL_SEPARATOR).toBe("--")
|
|
expect(MCP_TOOL_SEPARATOR).toBe("--")
|
|
|
expect(MCP_TOOL_PREFIX).toBe("mcp")
|
|
expect(MCP_TOOL_PREFIX).toBe("mcp")
|
|
|
})
|
|
})
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe("normalizeForComparison", () => {
|
|
|
|
|
+ it("should convert hyphens to underscores", () => {
|
|
|
|
|
+ expect(normalizeForComparison("get-user-profile")).toBe("get_user_profile")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should not modify strings without hyphens", () => {
|
|
|
|
|
+ expect(normalizeForComparison("get_user_profile")).toBe("get_user_profile")
|
|
|
|
|
+ expect(normalizeForComparison("tool")).toBe("tool")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should handle mixed hyphens and underscores", () => {
|
|
|
|
|
+ expect(normalizeForComparison("get-user_profile")).toBe("get_user_profile")
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- it("should have correct hyphen encoding", () => {
|
|
|
|
|
- expect(HYPHEN_ENCODING).toBe("___")
|
|
|
|
|
|
|
+ it("should handle multiple hyphens", () => {
|
|
|
|
|
+ expect(normalizeForComparison("mcp--server--tool")).toBe("mcp__server__tool")
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe("toolNamesMatch", () => {
|
|
|
|
|
+ it("should match identical names", () => {
|
|
|
|
|
+ expect(toolNamesMatch("get_user", "get_user")).toBe(true)
|
|
|
|
|
+ expect(toolNamesMatch("get-user", "get-user")).toBe(true)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should match names with hyphens vs underscores", () => {
|
|
|
|
|
+ expect(toolNamesMatch("get-user", "get_user")).toBe(true)
|
|
|
|
|
+ expect(toolNamesMatch("get_user", "get-user")).toBe(true)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should match complex MCP tool names", () => {
|
|
|
|
|
+ expect(toolNamesMatch("mcp--server--get-user-profile", "mcp__server__get_user_profile")).toBe(true)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should not match different names", () => {
|
|
|
|
|
+ expect(toolNamesMatch("get_user", "get_profile")).toBe(false)
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe("isMcpTool", () => {
|
|
describe("isMcpTool", () => {
|
|
|
- it("should return true for valid MCP tool names", () => {
|
|
|
|
|
|
|
+ it("should return true for valid MCP tool names with hyphens", () => {
|
|
|
expect(isMcpTool("mcp--server--tool")).toBe(true)
|
|
expect(isMcpTool("mcp--server--tool")).toBe(true)
|
|
|
expect(isMcpTool("mcp--my_server--get_forecast")).toBe(true)
|
|
expect(isMcpTool("mcp--my_server--get_forecast")).toBe(true)
|
|
|
|
|
+ expect(isMcpTool("mcp--server--get-user-profile")).toBe(true)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should return true for MCP tool names with underscore separators", () => {
|
|
|
|
|
+ // Models may convert hyphens to underscores
|
|
|
|
|
+ expect(isMcpTool("mcp__server__tool")).toBe(true)
|
|
|
|
|
+ expect(isMcpTool("mcp__my_server__get_forecast")).toBe(true)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it("should return false for non-MCP tool names", () => {
|
|
it("should return false for non-MCP tool names", () => {
|
|
@@ -35,7 +77,7 @@ describe("mcp-name utilities", () => {
|
|
|
expect(isMcpTool("")).toBe(false)
|
|
expect(isMcpTool("")).toBe(false)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should return false for old underscore format", () => {
|
|
|
|
|
|
|
+ it("should return false for old single-underscore format", () => {
|
|
|
expect(isMcpTool("mcp_server_tool")).toBe(false)
|
|
expect(isMcpTool("mcp_server_tool")).toBe(false)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -60,10 +102,9 @@ describe("mcp-name utilities", () => {
|
|
|
expect(sanitizeMcpName("test#$%^&*()")).toBe("test")
|
|
expect(sanitizeMcpName("test#$%^&*()")).toBe("test")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should keep alphanumeric and underscores, but encode hyphens", () => {
|
|
|
|
|
|
|
+ it("should keep alphanumeric, underscores, and hyphens", () => {
|
|
|
expect(sanitizeMcpName("server_name")).toBe("server_name")
|
|
expect(sanitizeMcpName("server_name")).toBe("server_name")
|
|
|
- // Hyphens are now encoded as triple underscores
|
|
|
|
|
- expect(sanitizeMcpName("server-name")).toBe("server___name")
|
|
|
|
|
|
|
+ expect(sanitizeMcpName("server-name")).toBe("server-name")
|
|
|
expect(sanitizeMcpName("Server123")).toBe("Server123")
|
|
expect(sanitizeMcpName("Server123")).toBe("Server123")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -71,16 +112,14 @@ describe("mcp-name utilities", () => {
|
|
|
// Dots and colons are NOT allowed due to AWS Bedrock restrictions
|
|
// Dots and colons are NOT allowed due to AWS Bedrock restrictions
|
|
|
expect(sanitizeMcpName("server.name")).toBe("servername")
|
|
expect(sanitizeMcpName("server.name")).toBe("servername")
|
|
|
expect(sanitizeMcpName("server:name")).toBe("servername")
|
|
expect(sanitizeMcpName("server:name")).toBe("servername")
|
|
|
- // Hyphens are encoded as triple underscores
|
|
|
|
|
- expect(sanitizeMcpName("awslabs.aws-documentation-mcp-server")).toBe(
|
|
|
|
|
- "awslabsaws___documentation___mcp___server",
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ // Hyphens are preserved
|
|
|
|
|
+ expect(sanitizeMcpName("awslabs.aws-documentation-mcp-server")).toBe("awslabsaws-documentation-mcp-server")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it("should prepend underscore if name starts with non-letter/underscore", () => {
|
|
it("should prepend underscore if name starts with non-letter/underscore", () => {
|
|
|
expect(sanitizeMcpName("123server")).toBe("_123server")
|
|
expect(sanitizeMcpName("123server")).toBe("_123server")
|
|
|
- // Hyphen at start is encoded to ___, which starts with underscore (valid)
|
|
|
|
|
- expect(sanitizeMcpName("-server")).toBe("___server")
|
|
|
|
|
|
|
+ // Hyphen at start still needs underscore prefix (function names must start with letter/underscore)
|
|
|
|
|
+ expect(sanitizeMcpName("-server")).toBe("_-server")
|
|
|
// Dots are removed, so ".server" becomes "server" which starts with a letter
|
|
// Dots are removed, so ".server" becomes "server" which starts with a letter
|
|
|
expect(sanitizeMcpName(".server")).toBe("server")
|
|
expect(sanitizeMcpName(".server")).toBe("server")
|
|
|
})
|
|
})
|
|
@@ -91,17 +130,15 @@ describe("mcp-name utilities", () => {
|
|
|
expect(sanitizeMcpName("Server")).toBe("Server")
|
|
expect(sanitizeMcpName("Server")).toBe("Server")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should replace double-hyphen sequences with single hyphen then encode", () => {
|
|
|
|
|
- // Double hyphens become single hyphen, then encoded as ___
|
|
|
|
|
- expect(sanitizeMcpName("server--name")).toBe("server___name")
|
|
|
|
|
- expect(sanitizeMcpName("test---server")).toBe("test___server")
|
|
|
|
|
- expect(sanitizeMcpName("my----tool")).toBe("my___tool")
|
|
|
|
|
|
|
+ it("should replace double-hyphen sequences with single hyphen to avoid separator conflicts", () => {
|
|
|
|
|
+ expect(sanitizeMcpName("server--name")).toBe("server-name")
|
|
|
|
|
+ expect(sanitizeMcpName("test---server")).toBe("test-server")
|
|
|
|
|
+ expect(sanitizeMcpName("my----tool")).toBe("my-tool")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it("should handle complex names with multiple issues", () => {
|
|
it("should handle complex names with multiple issues", () => {
|
|
|
expect(sanitizeMcpName("My Server @ Home!")).toBe("My_Server__Home")
|
|
expect(sanitizeMcpName("My Server @ Home!")).toBe("My_Server__Home")
|
|
|
- // Hyphen is encoded as ___
|
|
|
|
|
- expect(sanitizeMcpName("123-test server")).toBe("_123___test_server")
|
|
|
|
|
|
|
+ expect(sanitizeMcpName("123-test server")).toBe("_123-test_server")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it("should return placeholder for names that become empty after sanitization", () => {
|
|
it("should return placeholder for names that become empty after sanitization", () => {
|
|
@@ -110,26 +147,10 @@ describe("mcp-name utilities", () => {
|
|
|
expect(sanitizeMcpName(" ")).toBe("_")
|
|
expect(sanitizeMcpName(" ")).toBe("_")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should encode hyphens as triple underscores for model compatibility", () => {
|
|
|
|
|
- // This is the key feature: hyphens are encoded so they survive model tool calling
|
|
|
|
|
- expect(sanitizeMcpName("atlassian-jira_search")).toBe("atlassian___jira_search")
|
|
|
|
|
- expect(sanitizeMcpName("atlassian-confluence_search")).toBe("atlassian___confluence_search")
|
|
|
|
|
- })
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- describe("decodeMcpName", () => {
|
|
|
|
|
- it("should decode triple underscores back to hyphens", () => {
|
|
|
|
|
- expect(decodeMcpName("server___name")).toBe("server-name")
|
|
|
|
|
- expect(decodeMcpName("atlassian___jira_search")).toBe("atlassian-jira_search")
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- it("should not modify names without triple underscores", () => {
|
|
|
|
|
- expect(decodeMcpName("server_name")).toBe("server_name")
|
|
|
|
|
- expect(decodeMcpName("tool")).toBe("tool")
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- it("should handle multiple encoded hyphens", () => {
|
|
|
|
|
- expect(decodeMcpName("a___b___c")).toBe("a-b-c")
|
|
|
|
|
|
|
+ it("should preserve hyphens in tool names", () => {
|
|
|
|
|
+ // Hyphens are preserved, not encoded
|
|
|
|
|
+ expect(sanitizeMcpName("atlassian-jira_search")).toBe("atlassian-jira_search")
|
|
|
|
|
+ expect(sanitizeMcpName("atlassian-confluence_search")).toBe("atlassian-confluence_search")
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -162,26 +183,38 @@ describe("mcp-name utilities", () => {
|
|
|
expect(buildMcpToolName("my_server", "my_tool")).toBe("mcp--my_server--my_tool")
|
|
expect(buildMcpToolName("my_server", "my_tool")).toBe("mcp--my_server--my_tool")
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should encode hyphens in tool names", () => {
|
|
|
|
|
- // Hyphens are encoded as triple underscores
|
|
|
|
|
- expect(buildMcpToolName("onellm", "atlassian-jira_search")).toBe("mcp--onellm--atlassian___jira_search")
|
|
|
|
|
|
|
+ it("should preserve hyphens in tool names", () => {
|
|
|
|
|
+ // Hyphens are preserved (not encoded)
|
|
|
|
|
+ expect(buildMcpToolName("onellm", "atlassian-jira_search")).toBe("mcp--onellm--atlassian-jira_search")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should handle tool names with multiple hyphens", () => {
|
|
|
|
|
+ expect(buildMcpToolName("server", "get-user-profile")).toBe("mcp--server--get-user-profile")
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe("parseMcpToolName", () => {
|
|
describe("parseMcpToolName", () => {
|
|
|
- it("should parse valid mcp tool names", () => {
|
|
|
|
|
|
|
+ it("should parse valid mcp tool names with hyphen separators", () => {
|
|
|
expect(parseMcpToolName("mcp--server--tool")).toEqual({
|
|
expect(parseMcpToolName("mcp--server--tool")).toEqual({
|
|
|
serverName: "server",
|
|
serverName: "server",
|
|
|
toolName: "tool",
|
|
toolName: "tool",
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ it("should parse MCP tool names with underscore separators (model output)", () => {
|
|
|
|
|
+ // Models may convert hyphens to underscores
|
|
|
|
|
+ expect(parseMcpToolName("mcp__server__tool")).toEqual({
|
|
|
|
|
+ serverName: "server",
|
|
|
|
|
+ toolName: "tool",
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
it("should return null for non-mcp tool names", () => {
|
|
it("should return null for non-mcp tool names", () => {
|
|
|
expect(parseMcpToolName("server--tool")).toBeNull()
|
|
expect(parseMcpToolName("server--tool")).toBeNull()
|
|
|
expect(parseMcpToolName("tool")).toBeNull()
|
|
expect(parseMcpToolName("tool")).toBeNull()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should return null for old underscore format", () => {
|
|
|
|
|
|
|
+ it("should return null for old single-underscore format", () => {
|
|
|
expect(parseMcpToolName("mcp_server_tool")).toBeNull()
|
|
expect(parseMcpToolName("mcp_server_tool")).toBeNull()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -206,9 +239,8 @@ describe("mcp-name utilities", () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should decode triple underscores back to hyphens", () => {
|
|
|
|
|
- // This is the key feature: encoded hyphens are decoded back
|
|
|
|
|
- expect(parseMcpToolName("mcp--onellm--atlassian___jira_search")).toEqual({
|
|
|
|
|
|
|
+ it("should handle tool names with hyphens", () => {
|
|
|
|
|
+ expect(parseMcpToolName("mcp--onellm--atlassian-jira_search")).toEqual({
|
|
|
serverName: "onellm",
|
|
serverName: "onellm",
|
|
|
toolName: "atlassian-jira_search",
|
|
toolName: "atlassian-jira_search",
|
|
|
})
|
|
})
|
|
@@ -220,6 +252,34 @@ describe("mcp-name utilities", () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ describe("normalizeMcpToolName", () => {
|
|
|
|
|
+ it("should convert underscore separators to hyphen separators", () => {
|
|
|
|
|
+ expect(normalizeMcpToolName("mcp__server__tool")).toBe("mcp--server--tool")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should not modify names that already have hyphen separators", () => {
|
|
|
|
|
+ expect(normalizeMcpToolName("mcp--server--tool")).toBe("mcp--server--tool")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should not modify non-MCP tool names", () => {
|
|
|
|
|
+ expect(normalizeMcpToolName("read_file")).toBe("read_file")
|
|
|
|
|
+ expect(normalizeMcpToolName("some__tool")).toBe("some__tool")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should preserve underscores within names while normalizing separators", () => {
|
|
|
|
|
+ // Model outputs: mcp__my_server__get_user_profile
|
|
|
|
|
+ // Should become: mcp--my_server--get_user_profile (preserving underscores in names)
|
|
|
|
|
+ expect(normalizeMcpToolName("mcp__my_server__get_user_profile")).toBe("mcp--my_server--get_user_profile")
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should handle tool names that originally had hyphens (converted by model)", () => {
|
|
|
|
|
+ // Original: mcp--server--get-user-profile
|
|
|
|
|
+ // Model outputs: mcp__server__get_user_profile (hyphens converted to underscores)
|
|
|
|
|
+ // Normalized: mcp--server--get_user_profile
|
|
|
|
|
+ expect(normalizeMcpToolName("mcp__server__get_user_profile")).toBe("mcp--server--get_user_profile")
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
describe("roundtrip behavior", () => {
|
|
describe("roundtrip behavior", () => {
|
|
|
it("should be able to parse names that were built", () => {
|
|
it("should be able to parse names that were built", () => {
|
|
|
const toolName = buildMcpToolName("server", "tool")
|
|
const toolName = buildMcpToolName("server", "tool")
|
|
@@ -230,7 +290,7 @@ describe("mcp-name utilities", () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should preserve sanitized names through roundtrip with underscores", () => {
|
|
|
|
|
|
|
+ it("should preserve names through roundtrip with underscores", () => {
|
|
|
const toolName = buildMcpToolName("my_server", "my_tool")
|
|
const toolName = buildMcpToolName("my_server", "my_tool")
|
|
|
const parsed = parseMcpToolName(toolName)
|
|
const parsed = parseMcpToolName(toolName)
|
|
|
expect(parsed).toEqual({
|
|
expect(parsed).toEqual({
|
|
@@ -257,15 +317,16 @@ describe("mcp-name utilities", () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should preserve hyphens through roundtrip via encoding/decoding", () => {
|
|
|
|
|
- // This is the key test: hyphens survive the roundtrip
|
|
|
|
|
|
|
+ it("should preserve hyphens through roundtrip", () => {
|
|
|
|
|
+ // Build with hyphens in tool name
|
|
|
const toolName = buildMcpToolName("onellm", "atlassian-jira_search")
|
|
const toolName = buildMcpToolName("onellm", "atlassian-jira_search")
|
|
|
- expect(toolName).toBe("mcp--onellm--atlassian___jira_search")
|
|
|
|
|
|
|
+ expect(toolName).toBe("mcp--onellm--atlassian-jira_search")
|
|
|
|
|
|
|
|
|
|
+ // Parse directly
|
|
|
const parsed = parseMcpToolName(toolName)
|
|
const parsed = parseMcpToolName(toolName)
|
|
|
expect(parsed).toEqual({
|
|
expect(parsed).toEqual({
|
|
|
serverName: "onellm",
|
|
serverName: "onellm",
|
|
|
- toolName: "atlassian-jira_search", // Hyphen is preserved!
|
|
|
|
|
|
|
+ toolName: "atlassian-jira_search",
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -279,72 +340,134 @@ describe("mcp-name utilities", () => {
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- describe("normalizeMcpToolName", () => {
|
|
|
|
|
- it("should convert underscore separators to hyphen separators", () => {
|
|
|
|
|
- expect(normalizeMcpToolName("mcp__server__tool")).toBe("mcp--server--tool")
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ describe("model compatibility - full flow", () => {
|
|
|
|
|
+ it("should handle the complete flow when model preserves hyphens", () => {
|
|
|
|
|
+ // Step 1: Build the tool name
|
|
|
|
|
+ const builtName = buildMcpToolName("onellm", "atlassian-jira_search")
|
|
|
|
|
+ expect(builtName).toBe("mcp--onellm--atlassian-jira_search")
|
|
|
|
|
|
|
|
- it("should not modify names that already have hyphen separators", () => {
|
|
|
|
|
- expect(normalizeMcpToolName("mcp--server--tool")).toBe("mcp--server--tool")
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ // Step 2: Model outputs as-is (no mangling)
|
|
|
|
|
+ const modelOutput = "mcp--onellm--atlassian-jira_search"
|
|
|
|
|
|
|
|
- it("should not modify non-MCP tool names", () => {
|
|
|
|
|
- expect(normalizeMcpToolName("read_file")).toBe("read_file")
|
|
|
|
|
- expect(normalizeMcpToolName("some__tool")).toBe("some__tool")
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ // Step 3: Normalize (no change needed)
|
|
|
|
|
+ const normalizedName = normalizeMcpToolName(modelOutput)
|
|
|
|
|
+ expect(normalizedName).toBe("mcp--onellm--atlassian-jira_search")
|
|
|
|
|
|
|
|
- it("should preserve triple underscores (encoded hyphens) while normalizing separators", () => {
|
|
|
|
|
- // Model outputs: mcp__onellm__atlassian___jira_search
|
|
|
|
|
- // Should become: mcp--onellm--atlassian___jira_search
|
|
|
|
|
- expect(normalizeMcpToolName("mcp__onellm__atlassian___jira_search")).toBe(
|
|
|
|
|
- "mcp--onellm--atlassian___jira_search",
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ // Step 4: Parse
|
|
|
|
|
+ const parsed = parseMcpToolName(normalizedName)
|
|
|
|
|
+ expect(parsed).toEqual({
|
|
|
|
|
+ serverName: "onellm",
|
|
|
|
|
+ toolName: "atlassian-jira_search",
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it("should handle multiple encoded hyphens", () => {
|
|
|
|
|
- expect(normalizeMcpToolName("mcp__server__get___user___profile")).toBe("mcp--server--get___user___profile")
|
|
|
|
|
|
|
+ it("should handle the complete flow when model converts separators only", () => {
|
|
|
|
|
+ // Step 1: Build the tool name
|
|
|
|
|
+ const builtName = buildMcpToolName("onellm", "atlassian-jira_search")
|
|
|
|
|
+ expect(builtName).toBe("mcp--onellm--atlassian-jira_search")
|
|
|
|
|
+
|
|
|
|
|
+ // Step 2: Model converts -- separators to __
|
|
|
|
|
+ const modelOutput = "mcp__onellm__atlassian-jira_search"
|
|
|
|
|
+
|
|
|
|
|
+ // Step 3: Normalize the separators back
|
|
|
|
|
+ const normalizedName = normalizeMcpToolName(modelOutput)
|
|
|
|
|
+ expect(normalizedName).toBe("mcp--onellm--atlassian-jira_search")
|
|
|
|
|
+
|
|
|
|
|
+ // Step 4: Parse
|
|
|
|
|
+ const parsed = parseMcpToolName(normalizedName)
|
|
|
|
|
+ expect(parsed).toEqual({
|
|
|
|
|
+ serverName: "onellm",
|
|
|
|
|
+ toolName: "atlassian-jira_search",
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
- })
|
|
|
|
|
|
|
|
|
|
- describe("model compatibility - full flow", () => {
|
|
|
|
|
- it("should handle the complete flow: build -> model mangles -> normalize -> parse", () => {
|
|
|
|
|
- // Step 1: Build the tool name (hyphens encoded as ___)
|
|
|
|
|
|
|
+ it("should handle the complete flow when model converts ALL hyphens to underscores", () => {
|
|
|
|
|
+ // Step 1: Build the tool name
|
|
|
const builtName = buildMcpToolName("onellm", "atlassian-jira_search")
|
|
const builtName = buildMcpToolName("onellm", "atlassian-jira_search")
|
|
|
- expect(builtName).toBe("mcp--onellm--atlassian___jira_search")
|
|
|
|
|
|
|
+ expect(builtName).toBe("mcp--onellm--atlassian-jira_search")
|
|
|
|
|
|
|
|
- // Step 2: Model mangles the separators (-- becomes __)
|
|
|
|
|
- const mangledName = "mcp__onellm__atlassian___jira_search"
|
|
|
|
|
|
|
+ // Step 2: Model converts ALL hyphens to underscores
|
|
|
|
|
+ const modelOutput = "mcp__onellm__atlassian_jira_search"
|
|
|
|
|
|
|
|
- // Step 3: Normalize the separators back (__ becomes --)
|
|
|
|
|
- const normalizedName = normalizeMcpToolName(mangledName)
|
|
|
|
|
- expect(normalizedName).toBe("mcp--onellm--atlassian___jira_search")
|
|
|
|
|
|
|
+ // Step 3: Normalize
|
|
|
|
|
+ const normalizedName = normalizeMcpToolName(modelOutput)
|
|
|
|
|
+ expect(normalizedName).toBe("mcp--onellm--atlassian_jira_search")
|
|
|
|
|
|
|
|
- // Step 4: Parse the normalized name (decodes ___ back to -)
|
|
|
|
|
|
|
+ // Step 4: Parse - the tool name now has underscore instead of hyphen
|
|
|
const parsed = parseMcpToolName(normalizedName)
|
|
const parsed = parseMcpToolName(normalizedName)
|
|
|
expect(parsed).toEqual({
|
|
expect(parsed).toEqual({
|
|
|
serverName: "onellm",
|
|
serverName: "onellm",
|
|
|
- toolName: "atlassian-jira_search", // Original hyphen is preserved!
|
|
|
|
|
|
|
+ toolName: "atlassian_jira_search", // Note: underscore, not hyphen
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+ // Step 5: Use fuzzy matching to find the original tool
|
|
|
|
|
+ expect(toolNamesMatch("atlassian-jira_search", parsed!.toolName)).toBe(true)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it("should handle tool names with multiple hyphens through the full flow", () => {
|
|
it("should handle tool names with multiple hyphens through the full flow", () => {
|
|
|
// Build
|
|
// Build
|
|
|
const builtName = buildMcpToolName("server", "get-user-profile")
|
|
const builtName = buildMcpToolName("server", "get-user-profile")
|
|
|
- expect(builtName).toBe("mcp--server--get___user___profile")
|
|
|
|
|
|
|
+ expect(builtName).toBe("mcp--server--get-user-profile")
|
|
|
|
|
|
|
|
- // Model mangles
|
|
|
|
|
- const mangledName = "mcp__server__get___user___profile"
|
|
|
|
|
|
|
+ // Model converts all hyphens to underscores
|
|
|
|
|
+ const modelOutput = "mcp__server__get_user_profile"
|
|
|
|
|
|
|
|
// Normalize
|
|
// Normalize
|
|
|
- const normalizedName = normalizeMcpToolName(mangledName)
|
|
|
|
|
- expect(normalizedName).toBe("mcp--server--get___user___profile")
|
|
|
|
|
|
|
+ const normalizedName = normalizeMcpToolName(modelOutput)
|
|
|
|
|
+ expect(normalizedName).toBe("mcp--server--get_user_profile")
|
|
|
|
|
|
|
|
// Parse
|
|
// Parse
|
|
|
const parsed = parseMcpToolName(normalizedName)
|
|
const parsed = parseMcpToolName(normalizedName)
|
|
|
expect(parsed).toEqual({
|
|
expect(parsed).toEqual({
|
|
|
serverName: "server",
|
|
serverName: "server",
|
|
|
- toolName: "get-user-profile",
|
|
|
|
|
|
|
+ toolName: "get_user_profile",
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Use fuzzy matching to find the original tool
|
|
|
|
|
+ expect(toolNamesMatch("get-user-profile", parsed!.toolName)).toBe(true)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe("edge cases", () => {
|
|
|
|
|
+ it("should handle very long tool names by truncating", () => {
|
|
|
|
|
+ const longServer = "very-long-server-name-that-exceeds"
|
|
|
|
|
+ const longTool = "very-long-tool-name-that-also-exceeds"
|
|
|
|
|
+ const result = buildMcpToolName(longServer, longTool)
|
|
|
|
|
+
|
|
|
|
|
+ expect(result.length).toBeLessThanOrEqual(64)
|
|
|
|
|
+ // Should still be parseable
|
|
|
|
|
+ const parsed = parseMcpToolName(result)
|
|
|
|
|
+ expect(parsed).not.toBeNull()
|
|
|
|
|
+ expect(parsed?.serverName).toBeDefined()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it("should handle server names with hyphens", () => {
|
|
|
|
|
+ const toolName = buildMcpToolName("my-server", "tool")
|
|
|
|
|
+ expect(toolName).toBe("mcp--my-server--tool")
|
|
|
|
|
+
|
|
|
|
|
+ const parsed = parseMcpToolName(toolName)
|
|
|
|
|
+ expect(parsed).toEqual({
|
|
|
|
|
+ serverName: "my-server",
|
|
|
|
|
+ toolName: "tool",
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+ it("should handle both server and tool names with hyphens", () => {
|
|
|
|
|
+ const toolName = buildMcpToolName("my-server", "get-user")
|
|
|
|
|
+ expect(toolName).toBe("mcp--my-server--get-user")
|
|
|
|
|
+
|
|
|
|
|
+ // When model converts all hyphens
|
|
|
|
|
+ const modelOutput = "mcp__my_server__get_user"
|
|
|
|
|
+ const parsed = parseMcpToolName(modelOutput)
|
|
|
|
|
+
|
|
|
|
|
+ expect(parsed).toEqual({
|
|
|
|
|
+ serverName: "my_server",
|
|
|
|
|
+ toolName: "get_user",
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Fuzzy match should work
|
|
|
|
|
+ expect(toolNamesMatch("my-server", parsed!.serverName)).toBe(true)
|
|
|
|
|
+ expect(toolNamesMatch("get-user", parsed!.toolName)).toBe(true)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|