@ekoindia/eps-context-mcp 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -15,9 +15,13 @@ fetch full detail when it needs it.
15
15
  No install step required — run it on demand with `npx`:
16
16
 
17
17
  ```bash
18
- npx -y @ekoindia/eps-context-mcp
18
+ npx -y @ekoindia/eps-context-mcp@latest
19
19
  ```
20
20
 
21
+ The `@latest` tag makes `npx` re-resolve the newest published version on each
22
+ launch, so you always run the current server + API bundle without editing your
23
+ config (see [Staying up to date](#staying-up-to-date)).
24
+
21
25
  The server speaks MCP over **stdio**, so it is meant to be launched by an MCP
22
26
  client (see per-harness config below) rather than run by hand. It logs its
23
27
  status to **stderr** and waits for an MCP client on stdin/stdout.
@@ -27,30 +31,37 @@ Requires **Node.js ≥ 18**.
27
31
  ## Tools
28
32
 
29
33
  All tools are read-only and **secret-free** — none of them accept an
30
- `access_key` or any other credential.
31
-
32
- | Tool | Arguments | Returns |
33
- | --- | --- | --- |
34
- | `list_apis` | `category?` | Compact index of EPS endpoints (no request/response bodies). Optional category filter. |
35
- | `list_topics` | — | Documentation topic ids: `auth`, `errors`, `pricing`, `environments`. |
36
- | `list_recipes` | — | Multi-step recipe ids + names (e.g. `dmt-send-money`, `aeps-cash-withdrawal`). |
37
- | `search` | `query` | Ranked endpoint matches for a query (ids only, no bodies). |
38
- | `get_api` | `slug` | Full detail for one endpoint (params, response fields, errors, examples). |
39
- | `get_topic` | `topic` (`auth` \| `errors` \| `pricing` \| `environments`) | One documentation topic. |
40
- | `get_recipe` | `id` | One multi-step recipe (steps + branches). |
41
- | `get_signing_snippet` | `language` (`php` \| `java` \| `csharp` \| `javascript` \| `python` \| `go`) | Paste-ready **backend** code to compute the request `secret-key`. |
42
- | `get_meta` | — | Bundle org/version + which data source is in use (`baked` or `remote`). |
34
+ `access_key` or any other credential. Every tool also declares MCP annotations
35
+ (`readOnlyHint: true`, `idempotentHint: true`, `openWorldHint: false`) so
36
+ clients can see this programmatically.
37
+
38
+ | Tool | Arguments | Returns |
39
+ | --------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
40
+ | `list_apis` | `category?`, `limit?` | Compact index of EPS endpoints (no request/response bodies). `category` is validated against the bundle's categories; all entries by default. |
41
+ | `list_topics` | — | Documentation topic ids: `auth`, `errors`, `pricing`, `environments`. |
42
+ | `list_recipes` | — | Multi-step recipe ids + names (e.g. `dmt-send-money`, `aeps-cash-withdrawal`). |
43
+ | `search` | `query`, `limit?` | Ranked endpoint matches for a query (ids only, no bodies). Top 10 by default; raise `limit` for more. |
44
+ | `get_api` | `slug` | Full detail for one endpoint (params, response fields, errors, examples). |
45
+ | `get_topic` | `topic` (`auth` \| `errors` \| `pricing` \| `environments`) | One documentation topic. |
46
+ | `get_recipe` | `id` | One multi-step recipe (steps + branches). |
47
+ | `get_signing_snippet` | `language` (`php` \| `java` \| `csharp` \| `javascript` \| `python` \| `go`) | Paste-ready **backend** code to compute the request `secret-key`. |
48
+ | `get_meta` | — | Bundle org/version, data source (`baked` or `remote`), this server's `packageVersion`, and `updateAvailable` (whether a newer npm release exists). |
43
49
 
44
50
  **Tiered usage:** start with `list_apis` / `search` (cheap, compact), then call
45
51
  `get_api` only for the endpoint(s) you actually need. Same pattern for
46
52
  `list_topics` → `get_topic` and `list_recipes` → `get_recipe`.
47
53
 
54
+ **Errors are actionable:** an unknown `slug`/`id` returns an MCP error result
55
+ (`isError: true`) with "did you mean" suggestions and a pointer to
56
+ `search`/`list_apis`; an invalid `category` fails schema validation with the
57
+ valid values listed.
58
+
48
59
  ## Client configuration
49
60
 
50
61
  ### Claude Code
51
62
 
52
63
  ```bash
53
- claude mcp add eps --scope project -- npx -y @ekoindia/eps-context-mcp
64
+ claude mcp add eps --scope project -- npx -y @ekoindia/eps-context-mcp@latest
54
65
  ```
55
66
 
56
67
  `--scope project` writes a shared `.mcp.json` committed with the repo. Use
@@ -62,7 +73,10 @@ Or add to your MCP config (project `.mcp.json`, or `~/.claude.json` for user sco
62
73
  ```json
63
74
  {
64
75
  "mcpServers": {
65
- "eps": { "command": "npx", "args": ["-y", "@ekoindia/eps-context-mcp"] }
76
+ "eps": {
77
+ "command": "npx",
78
+ "args": ["-y", "@ekoindia/eps-context-mcp@latest"]
79
+ }
66
80
  }
67
81
  }
68
82
  ```
@@ -74,7 +88,10 @@ Or add to your MCP config (project `.mcp.json`, or `~/.claude.json` for user sco
74
88
  ```json
75
89
  {
76
90
  "mcpServers": {
77
- "eps": { "command": "npx", "args": ["-y", "@ekoindia/eps-context-mcp"] }
91
+ "eps": {
92
+ "command": "npx",
93
+ "args": ["-y", "@ekoindia/eps-context-mcp@latest"]
94
+ }
78
95
  }
79
96
  }
80
97
  ```
@@ -88,7 +105,7 @@ Or add to your MCP config (project `.mcp.json`, or `~/.claude.json` for user sco
88
105
  "mcp": {
89
106
  "eps": {
90
107
  "type": "local",
91
- "command": ["npx", "-y", "@ekoindia/eps-context-mcp"],
108
+ "command": ["npx", "-y", "@ekoindia/eps-context-mcp@latest"],
92
109
  "enabled": true
93
110
  }
94
111
  }
@@ -103,7 +120,7 @@ Or add to your MCP config (project `.mcp.json`, or `~/.claude.json` for user sco
103
120
  mcpServers:
104
121
  - name: eps
105
122
  command: npx
106
- args: ["-y", "@ekoindia/eps-context-mcp"]
123
+ args: ["-y", "@ekoindia/eps-context-mcp@latest"]
107
124
  ```
108
125
 
109
126
  ### Codex CLI
@@ -113,7 +130,7 @@ mcpServers:
113
130
  ```toml
114
131
  [mcp_servers.eps]
115
132
  command = "npx"
116
- args = ["-y", "@ekoindia/eps-context-mcp"]
133
+ args = ["-y", "@ekoindia/eps-context-mcp@latest"]
117
134
  ```
118
135
 
119
136
  ### Gemini CLI
@@ -123,11 +140,30 @@ args = ["-y", "@ekoindia/eps-context-mcp"]
123
140
  ```json
124
141
  {
125
142
  "mcpServers": {
126
- "eps": { "command": "npx", "args": ["-y", "@ekoindia/eps-context-mcp"] }
143
+ "eps": {
144
+ "command": "npx",
145
+ "args": ["-y", "@ekoindia/eps-context-mcp@latest"]
146
+ }
127
147
  }
128
148
  }
129
149
  ```
130
150
 
151
+ ## Staying up to date
152
+
153
+ The package is auto-published on every merge to `main`, and each publish carries
154
+ the freshest baked API bundle. To keep users current:
155
+
156
+ - **Use `@latest` in your config** (all snippets above do). `npx` then
157
+ re-resolves the newest publish on each launch — no manual updates. Offline
158
+ launches fall back to the npx cache; pin `@<version>` if you need a frozen
159
+ build.
160
+ - **Update check.** On startup the server does one best-effort request to the
161
+ npm registry (3s timeout). If a newer version is published, it logs a nudge to
162
+ **stderr** and sets `updateAvailable` on `get_meta` — so an agent can tell you
163
+ to switch to `@latest`. The check is silent on any failure (offline, proxy)
164
+ and never blocks startup. Set `EPS_NO_UPDATE_CHECK=1` to disable it entirely
165
+ (no network request).
166
+
131
167
  ## Configuration
132
168
 
133
169
  ### `EPS_BUNDLE_URL` (optional)
@@ -140,16 +176,16 @@ bundle at startup (e.g. the latest generated `eps.json`), set `EPS_BUNDLE_URL`:
140
176
  "mcpServers": {
141
177
  "eps": {
142
178
  "command": "npx",
143
- "args": ["-y", "@ekoindia/eps-context-mcp"],
179
+ "args": ["-y", "@ekoindia/eps-context-mcp@latest"],
144
180
  "env": { "EPS_BUNDLE_URL": "https://eps.eko.in/agent/eps.json" }
145
181
  }
146
182
  }
147
183
  }
148
184
  ```
149
185
 
150
- If the fetch fails for any reason, the server transparently falls back to the
151
- baked bundle. `get_meta` reports which source is in effect (`baked` or
152
- `remote`).
186
+ The remote fetch is capped at **5 seconds**; if it fails or times out for any
187
+ reason, the server transparently falls back to the baked bundle. `get_meta`
188
+ reports which source is in effect (`baked` or `remote`).
153
189
 
154
190
  ## Security: backend-only signing
155
191
 
package/data/eps.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "meta": {
3
3
  "org": "ekoindia",
4
4
  "apiVersion": "v3",
5
- "bundleVersion": "5f1b3ac4",
5
+ "bundleVersion": "1e83ecd3",
6
6
  "environments": [
7
7
  {
8
8
  "id": "sandbox",
@@ -1856,8 +1856,8 @@
1856
1856
  "in": "header",
1857
1857
  "type": "string",
1858
1858
  "required": true,
1859
- "description": "application/json",
1860
- "example": "application/json"
1859
+ "description": "multipart/form-data — let your HTTP client set this header itself (it generates the required boundary); do not hardcode the value.",
1860
+ "example": "multipart/form-data"
1861
1861
  }
1862
1862
  ],
1863
1863
  "requestParams": [
@@ -1921,7 +1921,7 @@
1921
1921
  },
1922
1922
  {
1923
1923
  "name": "pan_card",
1924
- "type": "string",
1924
+ "type": "file",
1925
1925
  "required": true,
1926
1926
  "description": "PAN card document upload (multipart/form-data). Accepted formats: JPEG, JPG, PDF. Max size: 1 MB. PNG not accepted.",
1927
1927
  "example": "<binary file>",
@@ -1929,7 +1929,7 @@
1929
1929
  },
1930
1930
  {
1931
1931
  "name": "aadhar_front",
1932
- "type": "string",
1932
+ "type": "file",
1933
1933
  "required": true,
1934
1934
  "description": "Front side of the Aadhaar card (multipart/form-data). Accepted formats: JPEG, JPG, PDF. Max size: 1 MB.",
1935
1935
  "example": "<binary file>",
@@ -1937,7 +1937,7 @@
1937
1937
  },
1938
1938
  {
1939
1939
  "name": "aadhar_back",
1940
- "type": "string",
1940
+ "type": "file",
1941
1941
  "required": true,
1942
1942
  "description": "Back side of the Aadhaar card (multipart/form-data). Accepted formats: JPEG, JPG, PDF. Max size: 1 MB.",
1943
1943
  "example": "<binary file>",
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import { readFileSync } from "fs";
5
+ import path2 from "path";
6
+ import { fileURLToPath as fileURLToPath2 } from "url";
4
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
8
 
6
9
  // src/load-bundle.ts
@@ -13,7 +16,7 @@ var loadBundle = async () => {
13
16
  const url = process.env.EPS_BUNDLE_URL;
14
17
  if (url) {
15
18
  try {
16
- const res = await fetch(url);
19
+ const res = await fetch(url, { signal: AbortSignal.timeout(5e3) });
17
20
  if (res.ok)
18
21
  return { bundle: await res.json(), source: "remote" };
19
22
  } catch {
@@ -39,19 +42,26 @@ var toIndexEntry = (a) => ({
39
42
  category: a.category,
40
43
  relevance: a.relevance
41
44
  });
42
- var listApis = (bundle, category) => bundle.apis.filter((a) => !category || a.category === category).map(toIndexEntry);
45
+ var listApis = (bundle, category, limit) => {
46
+ const entries = bundle.apis.filter((a) => !category || a.category === category).map(toIndexEntry);
47
+ return limit !== void 0 ? entries.slice(0, limit) : entries;
48
+ };
49
+ var listCategories = (bundle) => [
50
+ ...new Set(bundle.apis.map((a) => a.category))
51
+ ];
43
52
  var listTopics = (bundle) => Object.keys(bundle.topics);
44
53
  var listRecipes = (bundle) => bundle.recipes.map((r) => ({ id: r.id, name: r.name, summary: r.summary }));
45
- var searchApis = (bundle, query) => {
54
+ var searchApis = (bundle, query, limit) => {
46
55
  const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
47
- if (!terms.length) return listApis(bundle);
56
+ if (!terms.length) return listApis(bundle, void 0, limit);
48
57
  const scored = bundle.apis.map((a) => {
49
58
  const hay = `${a.name} ${a.summary} ${a.path} ${a.category} ${a.productName}`.toLowerCase();
50
59
  let score = 0;
51
60
  for (const t of terms) if (hay.includes(t)) score += 1;
52
61
  return { a, score };
53
62
  });
54
- return scored.filter((s) => s.score > 0).sort((x, y) => y.score - x.score).map((s) => toIndexEntry(s.a));
63
+ const ranked = scored.filter((s) => s.score > 0).sort((x, y) => y.score - x.score).map((s) => toIndexEntry(s.a));
64
+ return limit !== void 0 ? ranked.slice(0, limit) : ranked;
55
65
  };
56
66
  var getApi = (bundle, slug) => bundle.apis.find((a) => a.slug === slug);
57
67
  var getTopic = (bundle, topic) => bundle.topics[topic];
@@ -146,28 +156,46 @@ var getSigningSnippet = (language) => SNIPPETS[language] ?? `Unsupported languag
146
156
 
147
157
  // src/server.ts
148
158
  var json = (value) => ({
149
- content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
159
+ content: [{ type: "text", text: JSON.stringify(value) }]
150
160
  });
151
- var createEpsServer = (bundle, source) => {
161
+ var notFound = (message) => ({
162
+ isError: true,
163
+ content: [{ type: "text", text: message }]
164
+ });
165
+ var READ_ONLY = {
166
+ readOnlyHint: true,
167
+ idempotentHint: true,
168
+ openWorldHint: false
169
+ };
170
+ var DEFAULT_SEARCH_LIMIT = 10;
171
+ var createEpsServer = (bundle, source, versionState) => {
152
172
  const server = new McpServer({
153
173
  name: "eps-context-mcp",
154
174
  version: bundle.meta.bundleVersion
155
175
  });
176
+ const categories = listCategories(bundle);
177
+ const categorySchema = categories.length ? z.enum(categories) : z.string();
178
+ const limitSchema = z.number().int().positive().optional();
156
179
  server.registerTool(
157
180
  "list_apis",
158
181
  {
159
182
  title: "List EPS APIs",
160
- description: "Compact index of EPS API endpoints (no request/response bodies). Optionally filter by category.",
161
- inputSchema: { category: z.string().optional() }
183
+ description: "Compact index of EPS API endpoints (no request/response bodies). Unfiltered, all ~99 entries are returned (the full tiered index); narrow with category and/or limit when you don't need everything.",
184
+ inputSchema: {
185
+ category: categorySchema.optional().describe(`One of: ${categories.join(", ")}`),
186
+ limit: limitSchema.describe("Max entries to return (default: all)")
187
+ },
188
+ annotations: READ_ONLY
162
189
  },
163
- async ({ category }) => json(listApis(bundle, category))
190
+ async ({ category, limit }) => json(listApis(bundle, category, limit))
164
191
  );
165
192
  server.registerTool(
166
193
  "list_topics",
167
194
  {
168
195
  title: "List topics",
169
196
  description: "List documentation topic ids.",
170
- inputSchema: {}
197
+ inputSchema: {},
198
+ annotations: READ_ONLY
171
199
  },
172
200
  async () => json(listTopics(bundle))
173
201
  );
@@ -176,7 +204,8 @@ var createEpsServer = (bundle, source) => {
176
204
  {
177
205
  title: "List recipes",
178
206
  description: "List multi-step recipe ids + names.",
179
- inputSchema: {}
207
+ inputSchema: {},
208
+ annotations: READ_ONLY
180
209
  },
181
210
  async () => json(listRecipes(bundle))
182
211
  );
@@ -184,21 +213,34 @@ var createEpsServer = (bundle, source) => {
184
213
  "search",
185
214
  {
186
215
  title: "Search APIs",
187
- description: "Ranked endpoint matches for a query (ids only, no bodies).",
188
- inputSchema: { query: z.string() }
216
+ description: `Ranked endpoint matches for a query (ids only, no bodies). Returns the top ${DEFAULT_SEARCH_LIMIT} by default; raise limit for more.`,
217
+ inputSchema: {
218
+ query: z.string(),
219
+ limit: limitSchema.describe(
220
+ `Max results (default ${DEFAULT_SEARCH_LIMIT})`
221
+ )
222
+ },
223
+ annotations: READ_ONLY
189
224
  },
190
- async ({ query }) => json(searchApis(bundle, query))
225
+ async ({ query, limit }) => json(searchApis(bundle, query, limit ?? DEFAULT_SEARCH_LIMIT))
191
226
  );
192
227
  server.registerTool(
193
228
  "get_api",
194
229
  {
195
230
  title: "Get API detail",
196
231
  description: "Full detail for one endpoint by slug.",
197
- inputSchema: { slug: z.string() }
232
+ inputSchema: { slug: z.string() },
233
+ annotations: READ_ONLY
198
234
  },
199
235
  async ({ slug }) => {
200
236
  const api = getApi(bundle, slug);
201
- return api ? json(api) : json({ error: `Unknown slug "${slug}"` });
237
+ if (api) return json(api);
238
+ const suggestions = searchApis(bundle, slug.replace(/[-_]/g, " "), 3).map(
239
+ (a) => a.slug
240
+ );
241
+ return notFound(
242
+ `Unknown slug "${slug}".` + (suggestions.length ? ` Did you mean: ${suggestions.join(", ")}?` : "") + ` Use search or list_apis to find valid slugs.`
243
+ );
202
244
  }
203
245
  );
204
246
  server.registerTool(
@@ -208,7 +250,8 @@ var createEpsServer = (bundle, source) => {
208
250
  description: "One topic: auth | errors | pricing | environments.",
209
251
  inputSchema: {
210
252
  topic: z.enum(["auth", "errors", "pricing", "environments"])
211
- }
253
+ },
254
+ annotations: READ_ONLY
212
255
  },
213
256
  async ({ topic }) => json(getTopic(bundle, topic))
214
257
  );
@@ -217,11 +260,16 @@ var createEpsServer = (bundle, source) => {
217
260
  {
218
261
  title: "Get recipe",
219
262
  description: "One multi-step recipe (steps + branches) by id.",
220
- inputSchema: { id: z.string() }
263
+ inputSchema: { id: z.string() },
264
+ annotations: READ_ONLY
221
265
  },
222
266
  async ({ id }) => {
223
267
  const recipe = getRecipe(bundle, id);
224
- return recipe ? json(recipe) : json({ error: `Unknown recipe "${id}"` });
268
+ if (recipe) return json(recipe);
269
+ const valid = listRecipes(bundle).map((r) => r.id).join(", ");
270
+ return notFound(
271
+ `Unknown recipe "${id}". Valid recipe ids: ${valid}. Use list_recipes for details.`
272
+ );
225
273
  }
226
274
  );
227
275
  server.registerTool(
@@ -229,7 +277,8 @@ var createEpsServer = (bundle, source) => {
229
277
  {
230
278
  title: "Get signing snippet",
231
279
  description: "Paste-ready BACKEND code to compute the secret-key. Secret-free: access_key comes from your secret store.",
232
- inputSchema: { language: z.enum(SIGNING_LANGUAGES) }
280
+ inputSchema: { language: z.enum(SIGNING_LANGUAGES) },
281
+ annotations: READ_ONLY
233
282
  },
234
283
  async ({ language }) => ({
235
284
  content: [{ type: "text", text: getSigningSnippet(language) }]
@@ -239,18 +288,75 @@ var createEpsServer = (bundle, source) => {
239
288
  "get_meta",
240
289
  {
241
290
  title: "Get meta",
242
- description: "Bundle org/version + data source.",
243
- inputSchema: {}
291
+ description: "Bundle org/version + data source, plus this server's package version and whether a newer npm release is available (updateAvailable). If an update is available, tell the user to run this server via `npx -y @ekoindia/eps-context-mcp@latest`.",
292
+ inputSchema: {},
293
+ annotations: READ_ONLY
244
294
  },
245
- async () => json({ ...bundle.meta, source })
295
+ async () => json({
296
+ ...bundle.meta,
297
+ source,
298
+ packageVersion: versionState?.current,
299
+ latestVersion: versionState?.latest,
300
+ updateAvailable: versionState?.updateAvailable
301
+ })
246
302
  );
247
303
  return server;
248
304
  };
249
305
 
306
+ // src/update-check.ts
307
+ var REGISTRY_URL = "https://registry.npmjs.org/@ekoindia/eps-context-mcp/latest";
308
+ var compareVersions = (a, b) => {
309
+ const parse = (v) => {
310
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v.trim());
311
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
312
+ };
313
+ const pa = parse(a);
314
+ const pb = parse(b);
315
+ if (!pa || !pb) return null;
316
+ for (let i = 0; i < 3; i++) {
317
+ if (pa[i] !== pb[i]) return pa[i] > pb[i] ? 1 : -1;
318
+ }
319
+ return 0;
320
+ };
321
+ var checkForUpdate = async (state) => {
322
+ if (process.env.EPS_NO_UPDATE_CHECK) return;
323
+ try {
324
+ const res = await fetch(REGISTRY_URL, {
325
+ signal: AbortSignal.timeout(3e3)
326
+ });
327
+ if (!res.ok) return;
328
+ const data = await res.json();
329
+ if (typeof data.version !== "string") return;
330
+ const cmp = compareVersions(data.version, state.current);
331
+ if (cmp === null) return;
332
+ state.latest = data.version;
333
+ state.updateAvailable = cmp > 0;
334
+ if (state.updateAvailable) {
335
+ console.error(
336
+ `eps-context-mcp ${data.version} available (running ${state.current}). Use "npx -y @ekoindia/eps-context-mcp@latest" in your MCP config to always run the newest version.`
337
+ );
338
+ }
339
+ } catch {
340
+ }
341
+ };
342
+
250
343
  // src/index.ts
344
+ function readPackageVersion() {
345
+ try {
346
+ const here2 = path2.dirname(fileURLToPath2(import.meta.url));
347
+ const pkg = JSON.parse(
348
+ readFileSync(path2.resolve(here2, "../package.json"), "utf8")
349
+ );
350
+ return pkg.version ?? "0.0.0";
351
+ } catch {
352
+ return "0.0.0";
353
+ }
354
+ }
251
355
  async function main() {
252
356
  const { bundle, source } = await loadBundle();
253
- const server = createEpsServer(bundle, source);
357
+ const versionState = { current: readPackageVersion() };
358
+ void checkForUpdate(versionState);
359
+ const server = createEpsServer(bundle, source, versionState);
254
360
  const transport = new StdioServerTransport();
255
361
  await server.connect(transport);
256
362
  console.error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekoindia/eps-context-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Local MCP server giving AI coding agents context for Eko Platform Services (EPS) APIs.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,11 +23,12 @@
23
23
  "bake": "node scripts/bake-bundle.mjs",
24
24
  "build": "tsup",
25
25
  "test": "vitest run",
26
+ "typecheck": "tsc --noEmit",
26
27
  "prepublishOnly": "npm run bake && npm run build"
27
28
  },
28
29
  "dependencies": {
29
30
  "@modelcontextprotocol/sdk": "^1.0.0",
30
- "zod": "^3.25.76"
31
+ "zod": "^4.0.0"
31
32
  },
32
33
  "devDependencies": {
33
34
  "tsup": "^8.0.0",