@utdk/mcp 0.1.0-dev.646adf4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE +373 -0
- package/README.md +81 -0
- package/dist/__tests__/auth.test.js +63 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/loader.test.js +205 -0
- package/dist/__tests__/loader.test.js.map +1 -0
- package/dist/__tests__/search.test.js +204 -0
- package/dist/__tests__/search.test.js.map +1 -0
- package/dist/auth.js +82 -0
- package/dist/auth.js.map +1 -0
- package/dist/loader.js +211 -0
- package/dist/loader.js.map +1 -0
- package/dist/search.js +143 -0
- package/dist/search.js.map +1 -0
- package/dist/server.js +294 -0
- package/dist/server.js.map +1 -0
- package/package.json +60 -0
- package/src/__tests__/auth.test.ts +78 -0
- package/src/__tests__/loader.test.ts +264 -0
- package/src/__tests__/search.test.ts +243 -0
- package/src/auth.ts +111 -0
- package/src/loader.ts +309 -0
- package/src/search.ts +175 -0
- package/src/server.ts +362 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@utdk/mcp",
|
|
3
|
+
"version": "0.1.0-dev.646adf4",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Unified MCP server with dynamic tool loading from @utdk/* providers",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"utdk",
|
|
9
|
+
"modelcontextprotocol",
|
|
10
|
+
"tools"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"bin": {
|
|
14
|
+
"utdk-mcp": "./dist/server.js"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
18
|
+
"@utcp/http": "^1.1.1",
|
|
19
|
+
"@utcp/sdk": "^1.1.0",
|
|
20
|
+
"@utdk/common": "0.1.0-dev.646adf4"
|
|
21
|
+
},
|
|
22
|
+
"optionalDependencies": {
|
|
23
|
+
"@utdk/datadog": "1.0.0-20260407.1-dev.646adf4",
|
|
24
|
+
"@utdk/airtable": "0.1.0-20260530.2-dev.646adf4",
|
|
25
|
+
"@utdk/figma": "0.37.0-20260408.5-dev.646adf4",
|
|
26
|
+
"@utdk/github": "1.1.4-20260407.6-dev.646adf4",
|
|
27
|
+
"@utdk/google": "0.0.1-20260407.8-dev.646adf4",
|
|
28
|
+
"@utdk/hubspot": "3.0.0-20260530.2-dev.646adf4",
|
|
29
|
+
"@utdk/intercom": "2.15.0-20260530.1-dev.646adf4",
|
|
30
|
+
"@utdk/linear": "1.0.0-20260530.1-dev.646adf4",
|
|
31
|
+
"@utdk/jira": "1001.0.0-SNAPSHOT-500abd49de29b046db51cf0460caa503167075c0-20260530.2-dev.646adf4",
|
|
32
|
+
"@utdk/openai": "2.3.0-20260407.1-dev.646adf4",
|
|
33
|
+
"@utdk/notion": "1.0.0-20260530.1-dev.646adf4",
|
|
34
|
+
"@utdk/salesforce": "58.0.0-20260530.1-dev.646adf4",
|
|
35
|
+
"@utdk/slack": "1.7.0-20260530.1-dev.646adf4",
|
|
36
|
+
"@utdk/sendgrid": "1.0.0-20260530.1-dev.646adf4",
|
|
37
|
+
"@utdk/spotify": "1.0.0-20260407.6-dev.646adf4",
|
|
38
|
+
"@utdk/stripe": "0.0.1-20260530.2-dev.646adf4",
|
|
39
|
+
"@utdk/twilio": "1.0.0-20260530.2-dev.646adf4",
|
|
40
|
+
"@utdk/zendesk": "2.0.0-20260530.2-dev.646adf4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.5.0",
|
|
44
|
+
"tsx": "^4.19.2",
|
|
45
|
+
"typescript": "^5.7.3",
|
|
46
|
+
"vitest": "2.1.5"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20.0.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsc -p tsconfig.json",
|
|
53
|
+
"check-types": "tsc -p tsconfig.json --noEmit",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
55
|
+
"clean": "rm -rf dist",
|
|
56
|
+
"start": "node dist/server.js",
|
|
57
|
+
"dev": "tsx src/server.ts",
|
|
58
|
+
"test": "vitest run"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, it, afterEach, vi } from "vitest";
|
|
2
|
+
import { buildAuthProvider } from "../auth.js";
|
|
3
|
+
import type { UtdkAuthConfig } from "../auth.js";
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.unstubAllEnvs();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe("buildAuthProvider", () => {
|
|
10
|
+
it("returns a BearerToken when GITHUB_TOKEN is set", async () => {
|
|
11
|
+
vi.stubEnv("GITHUB_TOKEN", "ghp_test123");
|
|
12
|
+
|
|
13
|
+
const provider = buildAuthProvider("github", []);
|
|
14
|
+
expect(provider).not.toBeNull();
|
|
15
|
+
expect(provider).not.toBeUndefined();
|
|
16
|
+
|
|
17
|
+
const headers: Record<string, string> = {};
|
|
18
|
+
await provider!.authenticate(headers);
|
|
19
|
+
expect(headers["Authorization"]).toBe("Bearer ghp_test123");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("resolves api_key auth from environment variables", async () => {
|
|
23
|
+
vi.stubEnv("STRIPE_SECRET_KEY", "sk_test_abc");
|
|
24
|
+
|
|
25
|
+
const authConfigs: UtdkAuthConfig[] = [
|
|
26
|
+
{
|
|
27
|
+
auth_type: "api_key",
|
|
28
|
+
api_key: "Bearer ${STRIPE_SECRET_KEY}",
|
|
29
|
+
var_name: "Authorization",
|
|
30
|
+
location: "header",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const provider = buildAuthProvider("stripe", authConfigs);
|
|
35
|
+
expect(provider).not.toBeUndefined();
|
|
36
|
+
|
|
37
|
+
const headers: Record<string, string> = {};
|
|
38
|
+
await provider!.authenticate(headers);
|
|
39
|
+
expect(headers["Authorization"]).toBe("Bearer sk_test_abc");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns undefined when no credentials are available", () => {
|
|
43
|
+
vi.unstubAllEnvs();
|
|
44
|
+
|
|
45
|
+
const authConfigs: UtdkAuthConfig[] = [
|
|
46
|
+
{
|
|
47
|
+
auth_type: "api_key",
|
|
48
|
+
api_key: "Bearer ${GITHUB_TOKEN}",
|
|
49
|
+
var_name: "Authorization",
|
|
50
|
+
location: "header",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const provider = buildAuthProvider("github", authConfigs);
|
|
55
|
+
expect(provider).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("prefers PROVIDER_TOKEN over utdk.auth config", async () => {
|
|
59
|
+
vi.stubEnv("GITHUB_TOKEN", "ghp_from_env");
|
|
60
|
+
vi.stubEnv("SOME_OTHER_TOKEN", "other_token");
|
|
61
|
+
|
|
62
|
+
const authConfigs: UtdkAuthConfig[] = [
|
|
63
|
+
{
|
|
64
|
+
auth_type: "api_key",
|
|
65
|
+
api_key: "Bearer ${SOME_OTHER_TOKEN}",
|
|
66
|
+
var_name: "Authorization",
|
|
67
|
+
location: "header",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const provider = buildAuthProvider("github", authConfigs);
|
|
72
|
+
const headers: Record<string, string> = {};
|
|
73
|
+
await provider!.authenticate(headers);
|
|
74
|
+
|
|
75
|
+
// GITHUB_TOKEN takes priority
|
|
76
|
+
expect(headers["Authorization"]).toBe("Bearer ghp_from_env");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { describe, expect, it, vi, afterEach, beforeEach } from "vitest";
|
|
2
|
+
import { parseProviderNames, loadProviders, executeTool } from "../loader.js";
|
|
3
|
+
import type { ProviderTool } from "../loader.js";
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
vi.unstubAllEnvs();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// parseProviderNames
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
describe("parseProviderNames", () => {
|
|
15
|
+
it("parses comma-separated provider names", () => {
|
|
16
|
+
expect(parseProviderNames("github,slack,stripe")).toEqual(["github", "slack", "stripe"]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("trims whitespace", () => {
|
|
20
|
+
expect(parseProviderNames("github, slack , stripe")).toEqual(["github", "slack", "stripe"]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("lowercases names", () => {
|
|
24
|
+
expect(parseProviderNames("GitHub,Slack")).toEqual(["github", "slack"]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns empty array for undefined", () => {
|
|
28
|
+
expect(parseProviderNames(undefined)).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns empty array for empty string", () => {
|
|
32
|
+
expect(parseProviderNames("")).toEqual([]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("filters empty entries from double commas", () => {
|
|
36
|
+
expect(parseProviderNames("github,,slack")).toEqual(["github", "slack"]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// loadProviders
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
describe("loadProviders", () => {
|
|
45
|
+
it("returns an empty array for an unknown provider", async () => {
|
|
46
|
+
const tools = await loadProviders(["__nonexistent_provider__"]);
|
|
47
|
+
expect(tools).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns an empty array for an empty provider list", async () => {
|
|
51
|
+
const tools = await loadProviders([]);
|
|
52
|
+
expect(tools).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("loads tools from github provider (integration)", async () => {
|
|
56
|
+
// This test requires the @utdk/github package to be present in the workspace.
|
|
57
|
+
// It verifies that the loader correctly converts the OpenAPI doc to MCP tools.
|
|
58
|
+
const tools = await loadProviders(["github"]);
|
|
59
|
+
|
|
60
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
61
|
+
|
|
62
|
+
// All tools should have required fields
|
|
63
|
+
for (const tool of tools) {
|
|
64
|
+
expect(tool.mcpName).toMatch(/^[a-zA-Z0-9_-]+$/);
|
|
65
|
+
expect(tool.providerName).toBe("github");
|
|
66
|
+
expect(tool.description).toBeTruthy();
|
|
67
|
+
expect(tool.method).toMatch(/^(GET|POST|PUT|PATCH|DELETE)$/);
|
|
68
|
+
expect(tool.routeTemplate).toMatch(/^https?:\/\//);
|
|
69
|
+
expect(tool.inputSchema).toBeDefined();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("loads tools from stripe provider (integration)", async () => {
|
|
74
|
+
const tools = await loadProviders(["stripe"]);
|
|
75
|
+
|
|
76
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
77
|
+
|
|
78
|
+
const tool = tools[0];
|
|
79
|
+
expect(tool).toBeDefined();
|
|
80
|
+
if (!tool) return;
|
|
81
|
+
|
|
82
|
+
expect(tool.providerName).toBe("stripe");
|
|
83
|
+
expect(tool.inputSchema["type"]).toBe("object");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("loads tools from multiple providers simultaneously", async () => {
|
|
87
|
+
const tools = await loadProviders(["github", "stripe"]);
|
|
88
|
+
|
|
89
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
90
|
+
|
|
91
|
+
const githubTools = tools.filter((t) => t.providerName === "github");
|
|
92
|
+
const stripeTools = tools.filter((t) => t.providerName === "stripe");
|
|
93
|
+
|
|
94
|
+
expect(githubTools.length).toBeGreaterThan(0);
|
|
95
|
+
expect(stripeTools.length).toBeGreaterThan(0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("tool names are MCP-safe (no dots or slashes)", async () => {
|
|
99
|
+
const tools = await loadProviders(["github"]);
|
|
100
|
+
for (const tool of tools) {
|
|
101
|
+
expect(tool.mcpName).not.toContain(".");
|
|
102
|
+
expect(tool.mcpName).not.toContain("/");
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("input schemas have type: object", async () => {
|
|
107
|
+
const tools = await loadProviders(["stripe"]);
|
|
108
|
+
for (const tool of tools) {
|
|
109
|
+
const schema = tool.inputSchema;
|
|
110
|
+
expect(schema["type"]).toBe("object");
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("populates tags from OpenAPI operation tags", async () => {
|
|
115
|
+
const tools = await loadProviders(["github"]);
|
|
116
|
+
expect(tools.length).toBeGreaterThan(0);
|
|
117
|
+
|
|
118
|
+
// Every tool should have a tags array (may be empty for untagged operations)
|
|
119
|
+
for (const tool of tools) {
|
|
120
|
+
expect(Array.isArray(tool.tags)).toBe(true);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// At least some tools should have non-empty tags
|
|
124
|
+
const taggedTools = tools.filter((t) => t.tags.length > 0);
|
|
125
|
+
expect(taggedTools.length).toBeGreaterThan(0);
|
|
126
|
+
|
|
127
|
+
// Tags should be strings
|
|
128
|
+
for (const tool of taggedTools) {
|
|
129
|
+
for (const tag of tool.tags) {
|
|
130
|
+
expect(typeof tag).toBe("string");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// executeTool — unit tests with mocked fetch
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
describe("executeTool", () => {
|
|
141
|
+
const mockTool: ProviderTool = {
|
|
142
|
+
mcpName: "github__repos_list_for_user",
|
|
143
|
+
utcpName: "github.repos/listForUser",
|
|
144
|
+
description: "List public repositories for the specified user.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
username: { type: "string", description: "The handle for the GitHub user account." },
|
|
149
|
+
per_page: { type: "integer" },
|
|
150
|
+
},
|
|
151
|
+
required: ["username"],
|
|
152
|
+
},
|
|
153
|
+
providerName: "github",
|
|
154
|
+
tags: ["repos"],
|
|
155
|
+
method: "GET",
|
|
156
|
+
routeTemplate: "https://api.github.com/users/{username}/repos",
|
|
157
|
+
contentType: "application/json",
|
|
158
|
+
pathParamKeys: ["username"],
|
|
159
|
+
queryParamKeys: ["per_page"],
|
|
160
|
+
auth: undefined,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
beforeEach(() => {
|
|
164
|
+
vi.stubGlobal("fetch", vi.fn());
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("substitutes path parameters and makes a GET request", async () => {
|
|
168
|
+
const mockFetch = vi.mocked(fetch);
|
|
169
|
+
mockFetch.mockResolvedValueOnce(
|
|
170
|
+
new Response(JSON.stringify([{ id: 1, name: "repo1" }]), {
|
|
171
|
+
status: 200,
|
|
172
|
+
headers: { "Content-Type": "application/json" },
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const result = await executeTool(mockTool, { username: "octocat", per_page: 10 });
|
|
177
|
+
|
|
178
|
+
expect(mockFetch).toHaveBeenCalledOnce();
|
|
179
|
+
const [calledUrl, calledInit] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
180
|
+
|
|
181
|
+
expect(calledUrl).toContain("octocat");
|
|
182
|
+
expect(calledUrl).not.toContain("{username}");
|
|
183
|
+
expect(calledInit.method).toBe("GET");
|
|
184
|
+
expect(result).toEqual([{ id: 1, name: "repo1" }]);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("appends non-path args as query parameters for GET requests", async () => {
|
|
188
|
+
const mockFetch = vi.mocked(fetch);
|
|
189
|
+
mockFetch.mockResolvedValueOnce(
|
|
190
|
+
new Response("[]", {
|
|
191
|
+
status: 200,
|
|
192
|
+
headers: { "Content-Type": "application/json" },
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
await executeTool(mockTool, { username: "octocat", per_page: 5 });
|
|
197
|
+
|
|
198
|
+
const [calledUrl] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
199
|
+
expect(calledUrl).toContain("per_page=5");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("applies auth headers when auth provider is present", async () => {
|
|
203
|
+
const mockFetch = vi.mocked(fetch);
|
|
204
|
+
mockFetch.mockResolvedValueOnce(
|
|
205
|
+
new Response("{}", {
|
|
206
|
+
status: 200,
|
|
207
|
+
headers: { "Content-Type": "application/json" },
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const toolWithAuth: ProviderTool = {
|
|
212
|
+
...mockTool,
|
|
213
|
+
auth: {
|
|
214
|
+
authenticate: async (headers) => {
|
|
215
|
+
headers["Authorization"] = "Bearer test-token";
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
await executeTool(toolWithAuth, { username: "octocat" });
|
|
221
|
+
|
|
222
|
+
const [, calledInit] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
223
|
+
const headers = calledInit.headers as Record<string, string>;
|
|
224
|
+
expect(headers["Authorization"]).toBe("Bearer test-token");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("sends JSON body for POST requests", async () => {
|
|
228
|
+
const postTool: ProviderTool = {
|
|
229
|
+
...mockTool,
|
|
230
|
+
method: "POST",
|
|
231
|
+
routeTemplate: "https://api.github.com/user/repos",
|
|
232
|
+
pathParamKeys: [],
|
|
233
|
+
queryParamKeys: [],
|
|
234
|
+
mcpName: "github__repos_create_for_authenticated_user",
|
|
235
|
+
utcpName: "github.repos/createForAuthenticatedUser",
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const mockFetch = vi.mocked(fetch);
|
|
239
|
+
mockFetch.mockResolvedValueOnce(
|
|
240
|
+
new Response(JSON.stringify({ id: 42, name: "new-repo" }), {
|
|
241
|
+
status: 201,
|
|
242
|
+
headers: { "Content-Type": "application/json" },
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const result = await executeTool(postTool, { name: "new-repo", private: true });
|
|
247
|
+
|
|
248
|
+
const [, calledInit] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
249
|
+
expect(calledInit.method).toBe("POST");
|
|
250
|
+
expect(calledInit.body).toBe(JSON.stringify({ name: "new-repo", private: true }));
|
|
251
|
+
expect(result).toEqual({ id: 42, name: "new-repo" });
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("throws on non-ok HTTP responses", async () => {
|
|
255
|
+
const mockFetch = vi.mocked(fetch);
|
|
256
|
+
mockFetch.mockResolvedValueOnce(
|
|
257
|
+
new Response("Not Found", { status: 404 }),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
await expect(executeTool(mockTool, { username: "nonexistent" })).rejects.toThrow(
|
|
261
|
+
"Tool call failed: 404",
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { tokenize, buildTfIdf, searchTools, groupTools } from "../search.js";
|
|
3
|
+
import type { ProviderTool } from "../loader.js";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Shared test fixtures
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
function makeTool(overrides: Partial<ProviderTool> & { mcpName: string }): ProviderTool {
|
|
10
|
+
return {
|
|
11
|
+
utcpName: overrides.mcpName.replace(/__/g, "."),
|
|
12
|
+
description: "",
|
|
13
|
+
inputSchema: { type: "object" },
|
|
14
|
+
providerName: "testprovider",
|
|
15
|
+
tags: [],
|
|
16
|
+
method: "GET",
|
|
17
|
+
routeTemplate: "https://api.example.com/test",
|
|
18
|
+
contentType: "application/json",
|
|
19
|
+
pathParamKeys: [],
|
|
20
|
+
queryParamKeys: [],
|
|
21
|
+
auth: undefined,
|
|
22
|
+
...overrides,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const reposTool = makeTool({
|
|
27
|
+
mcpName: "github__repos_list",
|
|
28
|
+
providerName: "github",
|
|
29
|
+
description: "List public repositories for the specified user.",
|
|
30
|
+
tags: ["repos"],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const issuesTool = makeTool({
|
|
34
|
+
mcpName: "github__issues_list",
|
|
35
|
+
providerName: "github",
|
|
36
|
+
description: "List issues assigned to the authenticated user across all visible repositories.",
|
|
37
|
+
tags: ["issues"],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const prTool = makeTool({
|
|
41
|
+
mcpName: "github__pulls_list",
|
|
42
|
+
providerName: "github",
|
|
43
|
+
description: "List pull requests in a repository.",
|
|
44
|
+
tags: ["pulls"],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const slackTool = makeTool({
|
|
48
|
+
mcpName: "slack__messages_send",
|
|
49
|
+
providerName: "slack",
|
|
50
|
+
description: "Send a message to a Slack channel.",
|
|
51
|
+
tags: ["messaging"],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const ALL_TOOLS = [reposTool, issuesTool, prTool, slackTool];
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// tokenize
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
describe("tokenize", () => {
|
|
61
|
+
it("lowercases and splits on non-alphanumeric chars", () => {
|
|
62
|
+
expect(tokenize("hello world")).toEqual(["hello", "world"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("splits snake_case / double-underscore MCP names", () => {
|
|
66
|
+
expect(tokenize("github__repos_list")).toEqual(["github", "repos", "list"]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("splits camelCase boundaries", () => {
|
|
70
|
+
expect(tokenize("listForUser")).toEqual(["list", "for", "user"]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("strips punctuation", () => {
|
|
74
|
+
expect(tokenize("security-advisories")).toEqual(["security", "advisories"]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("returns empty array for empty string", () => {
|
|
78
|
+
expect(tokenize("")).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("filters empty tokens", () => {
|
|
82
|
+
expect(tokenize(" hello world ")).toEqual(["hello", "world"]);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// buildTfIdf
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
describe("buildTfIdf", () => {
|
|
91
|
+
it("returns zero score for empty corpus", () => {
|
|
92
|
+
const score = buildTfIdf([]);
|
|
93
|
+
expect(score(reposTool, ["repos"])).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("scores tool with name match higher than description-only match", () => {
|
|
97
|
+
const tools = [reposTool, issuesTool];
|
|
98
|
+
const score = buildTfIdf(tools);
|
|
99
|
+
// "repos" is in reposTool name AND tags but NOT in issuesTool name or tags
|
|
100
|
+
const reposScore = score(reposTool, ["repos"]);
|
|
101
|
+
const issuesScore = score(issuesTool, ["repos"]);
|
|
102
|
+
expect(reposScore).toBeGreaterThan(0);
|
|
103
|
+
expect(issuesScore).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("returns higher score for rare term (IDF effect)", () => {
|
|
107
|
+
// "list" appears in all github tools but "pull" only in prTool
|
|
108
|
+
const tools = [reposTool, issuesTool, prTool];
|
|
109
|
+
const score = buildTfIdf(tools);
|
|
110
|
+
const scoreOnPullRare = score(prTool, ["pull"]);
|
|
111
|
+
const scoreOnListCommon = score(prTool, ["list"]);
|
|
112
|
+
// "pull" is rarer than "list" so IDF is higher → pull score > list score
|
|
113
|
+
expect(scoreOnPullRare).toBeGreaterThan(scoreOnListCommon);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns positive score for a matching term in description", () => {
|
|
117
|
+
const tools = [slackTool];
|
|
118
|
+
const score = buildTfIdf(tools);
|
|
119
|
+
expect(score(slackTool, ["channel"])).toBeGreaterThan(0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// searchTools
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
describe("searchTools", () => {
|
|
128
|
+
it("returns empty array for empty query", () => {
|
|
129
|
+
expect(searchTools(ALL_TOOLS, "", undefined, 10)).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("returns empty array for blank query", () => {
|
|
133
|
+
expect(searchTools(ALL_TOOLS, " ", undefined, 10)).toEqual([]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("finds tools by name keyword", () => {
|
|
137
|
+
const results = searchTools(ALL_TOOLS, "repos", undefined, 10);
|
|
138
|
+
expect(results.map((r) => r.name)).toContain("github__repos_list");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("finds tools by tag", () => {
|
|
142
|
+
const results = searchTools(ALL_TOOLS, "issues", undefined, 10);
|
|
143
|
+
expect(results.map((r) => r.name)).toContain("github__issues_list");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("finds tools by description keyword", () => {
|
|
147
|
+
const results = searchTools(ALL_TOOLS, "channel", undefined, 10);
|
|
148
|
+
expect(results.map((r) => r.name)).toContain("slack__messages_send");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("excludes tools where a query word does not appear anywhere", () => {
|
|
152
|
+
const results = searchTools(ALL_TOOLS, "nonexistent_xyz", undefined, 10);
|
|
153
|
+
expect(results).toHaveLength(0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("requires ALL query words to match", () => {
|
|
157
|
+
// "repos" matches reposTool, "channel" matches slackTool → no tool matches both
|
|
158
|
+
const results = searchTools(ALL_TOOLS, "repos channel", undefined, 10);
|
|
159
|
+
expect(results).toHaveLength(0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("respects provider filter", () => {
|
|
163
|
+
const results = searchTools(ALL_TOOLS, "list", "slack", 10);
|
|
164
|
+
// slackTool description has "list" via "List" — but depends on description
|
|
165
|
+
// More importantly, github tools should NOT appear
|
|
166
|
+
expect(results.every((r) => r.name.startsWith("slack__"))).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("respects limit", () => {
|
|
170
|
+
const results = searchTools(ALL_TOOLS, "list", undefined, 2);
|
|
171
|
+
expect(results.length).toBeLessThanOrEqual(2);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("returns tags in results", () => {
|
|
175
|
+
const results = searchTools(ALL_TOOLS, "repos", undefined, 10);
|
|
176
|
+
const reposResult = results.find((r) => r.name === "github__repos_list");
|
|
177
|
+
expect(reposResult?.tags).toEqual(["repos"]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("ranks name matches above description-only matches", () => {
|
|
181
|
+
// "repos" is in the name of reposTool but only in description of issuesTool
|
|
182
|
+
const tools = [
|
|
183
|
+
reposTool,
|
|
184
|
+
makeTool({
|
|
185
|
+
mcpName: "github__something_else",
|
|
186
|
+
providerName: "github",
|
|
187
|
+
description: "Access repos for any organization.",
|
|
188
|
+
tags: [],
|
|
189
|
+
}),
|
|
190
|
+
];
|
|
191
|
+
const results = searchTools(tools, "repos", undefined, 10);
|
|
192
|
+
// reposTool (name match) should come before the description-only match
|
|
193
|
+
expect(results[0]?.name).toBe("github__repos_list");
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// groupTools
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
describe("groupTools", () => {
|
|
202
|
+
it("groups by provider", () => {
|
|
203
|
+
const grouped = groupTools(ALL_TOOLS, "provider");
|
|
204
|
+
expect(grouped["github"]).toEqual(
|
|
205
|
+
expect.arrayContaining(["github__repos_list", "github__issues_list", "github__pulls_list"]),
|
|
206
|
+
);
|
|
207
|
+
expect(grouped["slack"]).toEqual(["slack__messages_send"]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("groups by tag", () => {
|
|
211
|
+
const grouped = groupTools(ALL_TOOLS, "tag");
|
|
212
|
+
expect(grouped["repos"]).toEqual(["github__repos_list"]);
|
|
213
|
+
expect(grouped["issues"]).toEqual(["github__issues_list"]);
|
|
214
|
+
expect(grouped["pulls"]).toEqual(["github__pulls_list"]);
|
|
215
|
+
expect(grouped["messaging"]).toEqual(["slack__messages_send"]);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("falls back to provider name for tools with no tags when grouping by tag", () => {
|
|
219
|
+
const noTagTool = makeTool({
|
|
220
|
+
mcpName: "provider__notag_tool",
|
|
221
|
+
providerName: "myprovider",
|
|
222
|
+
tags: [],
|
|
223
|
+
});
|
|
224
|
+
const grouped = groupTools([noTagTool], "tag");
|
|
225
|
+
expect(grouped["myprovider"]).toEqual(["provider__notag_tool"]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("returns empty object for empty tool list", () => {
|
|
229
|
+
expect(groupTools([], "provider")).toEqual({});
|
|
230
|
+
expect(groupTools([], "tag")).toEqual({});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("assigns multi-tag tool to all its tag groups", () => {
|
|
234
|
+
const multiTagTool = makeTool({
|
|
235
|
+
mcpName: "github__multi_tool",
|
|
236
|
+
providerName: "github",
|
|
237
|
+
tags: ["repos", "admin"],
|
|
238
|
+
});
|
|
239
|
+
const grouped = groupTools([multiTagTool], "tag");
|
|
240
|
+
expect(grouped["repos"]).toContain("github__multi_tool");
|
|
241
|
+
expect(grouped["admin"]).toContain("github__multi_tool");
|
|
242
|
+
});
|
|
243
|
+
});
|