Skip to content

Commit fc0b650

Browse files
authored
Merge pull request #133 from currents-dev/fix/sanity-tests
feat(tests): add unit tests for MCP tool registration and validation
2 parents cd8062e + cfb14e1 commit fc0b650

2 files changed

Lines changed: 100 additions & 2 deletions

File tree

mcp-server/src/server.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
const { registeredTools } = vi.hoisted(() => {
4+
const registeredTools: Array<{ name: string; description: string }> = [];
5+
return { registeredTools };
6+
});
7+
8+
vi.mock("@modelcontextprotocol/sdk/server/mcp.js", () => ({
9+
McpServer: class {
10+
registerTool(
11+
name: string,
12+
opts: { description: string; inputSchema: unknown },
13+
_handler: unknown
14+
) {
15+
registeredTools.push({ name, description: opts.description });
16+
}
17+
},
18+
}));
19+
20+
vi.mock("@modelcontextprotocol/sdk/server/stdio.js", () => ({
21+
StdioServerTransport: class {},
22+
}));
23+
24+
// Triggers all registerTool calls at module scope
25+
import "./server.js";
26+
27+
// SEP-986 (Final): tool names SHOULD be 1–64 characters
28+
const TOOL_NAME_MAX_LENGTH = 64;
29+
// Allowed: a-zA-Z0-9 _ - . /
30+
const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_\-./]+$/;
31+
// No hard spec limit; 1024 is the practical ceiling across major LLM providers
32+
const TOOL_DESCRIPTION_MAX_LENGTH = 1024;
33+
// Cursor IDE prefixes tool names with "extension-<server>:" when displaying them.
34+
// Combined length must stay ≤ 60 to avoid filtering warnings.
35+
const CURSOR_SERVER_PREFIX = "extension-currents:";
36+
const CURSOR_COMBINED_MAX_LENGTH = 60;
37+
38+
describe("MCP tool best practices", () => {
39+
it("has at least one registered tool", () => {
40+
expect(registeredTools.length).toBeGreaterThan(0);
41+
});
42+
43+
it("tool names are unique", () => {
44+
const names = registeredTools.map((t) => t.name);
45+
const dupes = names.filter((n, i) => names.indexOf(n) !== i);
46+
expect(dupes, `duplicate tool names: ${dupes.join(", ")}`).toHaveLength(0);
47+
});
48+
49+
describe.each(registeredTools)("$name", ({ name, description }) => {
50+
// ── name constraints (SEP-986) ──────────────────────────────
51+
it(`name length ≤ ${TOOL_NAME_MAX_LENGTH}`, () => {
52+
expect(
53+
name.length,
54+
`"${name}" is ${name.length} chars`
55+
).toBeLessThanOrEqual(TOOL_NAME_MAX_LENGTH);
56+
});
57+
58+
it("name contains only allowed characters", () => {
59+
expect(name).toMatch(TOOL_NAME_PATTERN);
60+
});
61+
62+
it("name is not empty", () => {
63+
expect(name.length).toBeGreaterThan(0);
64+
});
65+
66+
it(`combined Cursor name length ≤ ${CURSOR_COMBINED_MAX_LENGTH}`, () => {
67+
const combined = `${CURSOR_SERVER_PREFIX}${name}`;
68+
expect(
69+
combined.length,
70+
`"${combined}" is ${combined.length} chars`
71+
).toBeLessThanOrEqual(CURSOR_COMBINED_MAX_LENGTH);
72+
});
73+
74+
// ── description constraints ─────────────────────────────────
75+
it("description is not empty", () => {
76+
expect(description.length).toBeGreaterThan(0);
77+
});
78+
79+
it(`description length ≤ ${TOOL_DESCRIPTION_MAX_LENGTH}`, () => {
80+
expect(
81+
description.length,
82+
`description is ${description.length} chars`
83+
).toBeLessThanOrEqual(TOOL_DESCRIPTION_MAX_LENGTH);
84+
});
85+
86+
it("description has no leading/trailing whitespace", () => {
87+
expect(description).toBe(description.trim());
88+
});
89+
90+
it("description starts with a capital letter", () => {
91+
expect(description).toMatch(/^[A-Z]/);
92+
});
93+
94+
it("description ends with a period", () => {
95+
expect(description.at(-1)).toBe(".");
96+
});
97+
});
98+
});

mcp-server/src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ server.registerTool(
124124
"currents-list-affected-tests",
125125
{
126126
description:
127-
"List tests affected by actions (quarantine, skip, tag) for a project within a date range. Returns aggregated data grouped by test signature. Supports filtering by action types, action ID, status, and search. Requires projectId, date_start, and date_end. (Preview endpoint: fields and path may change.)",
127+
"List tests affected by actions (quarantine, skip, tag) for a project within a date range. Returns aggregated data grouped by test signature. Supports filtering by action types, action ID, status, and search. Requires projectId, date_start, and date_end. Preview endpoint: fields and path may change.",
128128
inputSchema: listAffectedTestsTool.schema,
129129
},
130130
listAffectedTestsTool.handler
@@ -141,7 +141,7 @@ server.registerTool(
141141
);
142142

143143
server.registerTool(
144-
"currents-get-affected-test-executions-by-action",
144+
"currents-get-affected-executions",
145145
{
146146
description:
147147
"List test executions where a specific action/rule was applied, within a date range. Uses cursor-based pagination. Requires actionId, date_start, and date_end.",

0 commit comments

Comments
 (0)