Skip to content

Commit bae0ffb

Browse files
fix: report registered MCP tool count (#504)
Co-authored-by: CharlieHelps <charlie@charlielabs.ai>
1 parent fb00d27 commit bae0ffb

3 files changed

Lines changed: 85 additions & 22 deletions

File tree

.changeset/bright-tools-count.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hevy-mcp": patch
3+
---
4+
5+
Correct the MCP registration span tool count to reflect successful tool registrations.

src/index.test.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
import * as stdioModule from "@modelcontextprotocol/sdk/server/stdio.js";
23
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
34
import createServer, {
@@ -22,6 +23,9 @@ const testDoubles = vi.hoisted(() => ({
2223
end: vi.fn(),
2324
},
2425
connect: vi.fn().mockResolvedValue(undefined),
26+
tool: vi.fn(),
27+
registerTool: vi.fn(),
28+
directRegisterToolCalls: 0,
2529
sentry: {
2630
init: vi.fn(() => ({})),
2731
setUser: vi.fn(),
@@ -68,6 +72,13 @@ vi.mock("./utils/metrics.js", () => ({
6872
serverStartups: { add: vi.fn() },
6973
}));
7074

75+
vi.mock("./tools/user.js", () => ({
76+
registerUserTools: vi.fn((server: McpServer) => {
77+
testDoubles.directRegisterToolCalls += 1;
78+
server.registerTool("get-user-info", {}, vi.fn());
79+
}),
80+
}));
81+
7182
vi.mock("@opentelemetry/api", () => ({
7283
SpanStatusCode: { OK: 1, ERROR: 2 },
7384
trace: { getTracer: vi.fn(() => ({ startActiveSpan: vi.fn() })) },
@@ -82,8 +93,8 @@ vi.mock("@opentelemetry/api", () => ({
8293
vi.mock("@modelcontextprotocol/sdk/server/mcp.js", () => {
8394
class MockMcpServer {
8495
connect = testDoubles.connect;
85-
registerTool = vi.fn();
86-
tool = vi.fn();
96+
tool = testDoubles.tool;
97+
registerTool = testDoubles.registerTool;
8798
registerResource = vi.fn();
8899
}
89100

@@ -111,6 +122,12 @@ describe("Server entry", () => {
111122
process.env = { ...originalEnv };
112123
process.argv = [...originalArgv];
113124
vi.clearAllMocks();
125+
testDoubles.directRegisterToolCalls = 0;
126+
testDoubles.tool.mockImplementation(
127+
function (this: { registerTool: () => void }) {
128+
this.registerTool();
129+
},
130+
);
114131
const anyStdioModule = stdioModule as { __transports?: unknown[] };
115132
if (anyStdioModule.__transports) {
116133
anyStdioModule.__transports.length = 0;
@@ -142,6 +159,17 @@ describe("Server entry", () => {
142159
);
143160
});
144161

162+
it("reports the number of tool registration calls on the registration span", () => {
163+
createServer({ config: { apiKey: "test-key" } });
164+
165+
const registrationCount = testDoubles.registerTool.mock.calls.length;
166+
expect(registrationCount).toBeGreaterThan(0);
167+
expect(testDoubles.span.setAttribute).toHaveBeenCalledWith(
168+
"mcp.tools.count",
169+
registrationCount,
170+
);
171+
});
172+
145173
it("exports createServer as both default and named exports", () => {
146174
expect(namedCreateServer).toBe(createServer);
147175
const server = namedCreateServer({ config: { apiKey: "named-key" } });

src/index.ts

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,42 @@ const serverConfigSchema = z.object({
8383
export const configSchema = serverConfigSchema;
8484
type ServerConfig = z.infer<typeof serverConfigSchema>;
8585

86+
function createToolCountingServer(server: McpServer) {
87+
let count = 0;
88+
89+
const countingServer = new Proxy(server, {
90+
get(target, property, receiver) {
91+
if (property === "tool") {
92+
return (...args: Parameters<McpServer["tool"]>) => {
93+
const registeredTool = target.tool(...args);
94+
count += 1;
95+
return registeredTool;
96+
};
97+
}
98+
99+
if (property === "registerTool") {
100+
const registerTool: McpServer["registerTool"] = (
101+
name,
102+
config,
103+
callback,
104+
) => {
105+
const registeredTool = target.registerTool(name, config, callback);
106+
count += 1;
107+
return registeredTool;
108+
};
109+
return registerTool;
110+
}
111+
112+
return Reflect.get(target, property, receiver);
113+
},
114+
});
115+
116+
return {
117+
server: countingServer,
118+
getCount: () => count,
119+
};
120+
}
121+
86122
function buildServer(apiKey: string) {
87123
const userId = fingerprintApiKey(apiKey);
88124

@@ -119,26 +155,20 @@ function buildServer(apiKey: string) {
119155
);
120156
console.error("Hevy client initialized with API key");
121157

122-
tracer.startActiveSpan(
123-
"mcp.tools.register",
124-
{
125-
attributes: {
126-
"mcp.tools.count": 6,
127-
},
128-
},
129-
(toolsSpan) => {
130-
try {
131-
registerWorkoutTools(server, hevyClient);
132-
registerRoutineTools(server, hevyClient);
133-
registerTemplateTools(server, hevyClient);
134-
registerFolderTools(server, hevyClient);
135-
registerBodyMeasurementTools(server, hevyClient);
136-
registerUserTools(server, hevyClient);
137-
} finally {
138-
toolsSpan.end();
139-
}
140-
},
141-
);
158+
tracer.startActiveSpan("mcp.tools.register", (toolsSpan) => {
159+
try {
160+
const counting = createToolCountingServer(server);
161+
registerWorkoutTools(counting.server, hevyClient);
162+
registerRoutineTools(counting.server, hevyClient);
163+
registerTemplateTools(counting.server, hevyClient);
164+
registerFolderTools(counting.server, hevyClient);
165+
registerBodyMeasurementTools(counting.server, hevyClient);
166+
registerUserTools(counting.server, hevyClient);
167+
toolsSpan.setAttribute("mcp.tools.count", counting.getCount());
168+
} finally {
169+
toolsSpan.end();
170+
}
171+
});
142172

143173
tracer.startActiveSpan(
144174
"mcp.resources.register",

0 commit comments

Comments
 (0)