@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.
@@ -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
+ });