Skip to content

Commit 4c80e87

Browse files
feat: add server tool-selection instructions (#563)
Co-authored-by: CharlieHelps <charlie@charlielabs.ai>
1 parent 209a7d4 commit 4c80e87

3 files changed

Lines changed: 78 additions & 3 deletions

File tree

.changeset/bright-otters-guide.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"hevy-mcp": patch
3+
---
4+
5+
Provide MCP clients with concise server-level guidance for safe tool selection,
6+
recommended workout workflows, pagination, retries, and API-key setup.

src/index.test.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,47 @@ describe("Server entry", () => {
173173
);
174174
});
175175

176-
it("advertises logging capability and injects one client logger", () => {
176+
it("advertises logging, rich instructions, and one client logger", () => {
177177
createServer({ config: { apiKey: "test-key" } });
178178

179179
expect(testDoubles.mcpServerConstructor).toHaveBeenCalledWith(
180180
{ name: "hevy-mcp", version: "dev" },
181-
{ capabilities: { logging: {} } },
181+
{
182+
capabilities: { logging: {} },
183+
instructions: expect.any(String),
184+
},
182185
);
186+
187+
const serverOptions = testDoubles.mcpServerConstructor.mock.calls[0]?.[1];
188+
expect(serverOptions).toBeDefined();
189+
if (
190+
!serverOptions ||
191+
typeof serverOptions !== "object" ||
192+
!("instructions" in serverOptions) ||
193+
typeof serverOptions.instructions !== "string"
194+
) {
195+
throw new Error("MCP server instructions were not provided");
196+
}
197+
198+
const instructions = serverOptions.instructions;
199+
expect(instructions.trim()).not.toBe("");
200+
expect(instructions).toMatch(/Hevy[\s\S]*workout-tracking data/i);
201+
expect(instructions).toMatch(/HEVY_API_KEY/);
202+
expect(instructions).toMatch(/get-\*[\s\S]*search-\*[\s\S]*read-only/i);
203+
expect(instructions).toMatch(/create-\*[\s\S]*update-\*[\s\S]*mutate/i);
204+
expect(instructions).toMatch(/delete[\s\S]*not available/i);
205+
expect(instructions).toMatch(/search[\s\S]*templates[\s\S]*template IDs/i);
206+
expect(instructions).toMatch(/routine[\s\S]*actual completed sets/i);
207+
expect(instructions).toMatch(/page 1/i);
208+
expect(instructions).toMatch(/pageSize[\s\S]*10/i);
209+
expect(instructions).toMatch(/get-exercise-templates[\s\S]*100/i);
210+
expect(instructions).toMatch(/HTTP 429/i);
211+
expect(instructions).toMatch(/read requests[\s\S]*retry automatically/i);
212+
expect(instructions).toMatch(/write requests[\s\S]*do not/i);
213+
// A 6,000-character cap is conservative for the sub-2,000-token goal;
214+
// character count is intentionally not treated as an exact token count.
215+
expect(instructions.length).toBeLessThan(6_000);
216+
183217
expect(createClient).toHaveBeenCalledWith(
184218
"test-key",
185219
"https://api.hevyapp.com",

src/index.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,38 @@ import { createInstrumentedStdioTransport } from "./utils/stdio-observability.js
3030
const name = serviceName;
3131
const version = serviceVersion;
3232

33+
const SERVER_INSTRUCTIONS = [
34+
[
35+
"Hevy MCP connects clients to the authenticated user's Hevy",
36+
"workout-tracking data, including workouts, routines, exercise templates,",
37+
"routine folders, body measurements, and profile information.",
38+
"HEVY_API_KEY must contain a valid Hevy API key.",
39+
].join(" "),
40+
[
41+
"Safety: all get-* and search-* tools are read-only. create-* and",
42+
"update-* tools mutate Hevy data. Creates are additive and",
43+
"non-idempotent, so repeating one can create duplicates. Updates can",
44+
"overwrite existing data. Delete operations are not available.",
45+
].join(" "),
46+
[
47+
"Workflow: search exercise templates first, then use the returned",
48+
"template IDs when creating workouts or routines. To create a completed",
49+
"workout from a routine, fetch the routine as a plan, then obtain the",
50+
"actual completed sets and end time from the user; never invent completion",
51+
"data. Use the built-in workflow prompts when they match the task.",
52+
].join(" "),
53+
[
54+
"Pagination: start at page 1 and fetch only the pages needed. Most list",
55+
"tools allow pageSize up to 10; get-exercise-templates allows up to 100.",
56+
].join(" "),
57+
[
58+
"Rate limits and retries: minimize repeated calls. If Hevy returns HTTP",
59+
"429, follow its retry guidance. Transient read requests retry",
60+
"automatically, but write requests do not; confirm uncertain write",
61+
"outcomes before trying again.",
62+
].join(" "),
63+
].join("\n\n");
64+
3365
const HELP_TEXT = [
3466
"Usage:",
3567
" hevy-mcp [options]",
@@ -144,7 +176,10 @@ function buildServer(apiKey: string) {
144176
name,
145177
version,
146178
},
147-
{ capabilities: { logging: {} } },
179+
{
180+
capabilities: { logging: {} },
181+
instructions: SERVER_INSTRUCTIONS,
182+
},
148183
);
149184
const server = Sentry.wrapMcpServerWithSentry(baseServer);
150185
const clientLogger = createMcpClientLogger(server);

0 commit comments

Comments
 (0)