@ekoindia/eps-context-mcp 0.1.3 → 0.1.5
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 +61 -25
- package/data/eps.json +44 -6
- package/dist/index.js +139 -27
- package/package.json +3 -2
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
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": {
|
|
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": {
|
|
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": {
|
|
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
|
-
|
|
151
|
-
|
|
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": "
|
|
5
|
+
"bundleVersion": "2f801127",
|
|
6
6
|
"environments": [
|
|
7
7
|
{
|
|
8
8
|
"id": "sandbox",
|
|
@@ -188,6 +188,44 @@
|
|
|
188
188
|
"note": "Credentials issued after organizational KYC."
|
|
189
189
|
}
|
|
190
190
|
]
|
|
191
|
+
},
|
|
192
|
+
"getting-started": {
|
|
193
|
+
"id": "getting-started",
|
|
194
|
+
"summary": "Start testing Eko verification APIs in ~10 minutes: sign up, verify identity, load your wallet, and test live before integrating.",
|
|
195
|
+
"steps": [
|
|
196
|
+
{
|
|
197
|
+
"title": "Sign up",
|
|
198
|
+
"detail": "Sign up with your mobile number (OTP verified).",
|
|
199
|
+
"url": "https://ekostore.app/eps"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"title": "Verify identity",
|
|
203
|
+
"detail": "Submit PAN + bank account to verify your account."
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"title": "Test live",
|
|
207
|
+
"detail": "Load wallet funds; call the verification APIs live to evaluate before integrating."
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"title": "Integrate",
|
|
211
|
+
"detail": "Free AI plugins/tools + MCP & SDKs to integrate faster.",
|
|
212
|
+
"url": "https://eps.eko.in/ai"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"title": "Go live",
|
|
216
|
+
"detail": "Welcome email lists the KYC docs for production; reply with docs to get production keys."
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
"links": [
|
|
220
|
+
{
|
|
221
|
+
"label": "Sign up",
|
|
222
|
+
"url": "https://ekostore.app/eps"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"label": "AI integration hub",
|
|
226
|
+
"url": "https://eps.eko.in/ai"
|
|
227
|
+
}
|
|
228
|
+
]
|
|
191
229
|
}
|
|
192
230
|
},
|
|
193
231
|
"apis": [
|
|
@@ -1856,8 +1894,8 @@
|
|
|
1856
1894
|
"in": "header",
|
|
1857
1895
|
"type": "string",
|
|
1858
1896
|
"required": true,
|
|
1859
|
-
"description": "
|
|
1860
|
-
"example": "
|
|
1897
|
+
"description": "multipart/form-data — let your HTTP client set this header itself (it generates the required boundary); do not hardcode the value.",
|
|
1898
|
+
"example": "multipart/form-data"
|
|
1861
1899
|
}
|
|
1862
1900
|
],
|
|
1863
1901
|
"requestParams": [
|
|
@@ -1921,7 +1959,7 @@
|
|
|
1921
1959
|
},
|
|
1922
1960
|
{
|
|
1923
1961
|
"name": "pan_card",
|
|
1924
|
-
"type": "
|
|
1962
|
+
"type": "file",
|
|
1925
1963
|
"required": true,
|
|
1926
1964
|
"description": "PAN card document upload (multipart/form-data). Accepted formats: JPEG, JPG, PDF. Max size: 1 MB. PNG not accepted.",
|
|
1927
1965
|
"example": "<binary file>",
|
|
@@ -1929,7 +1967,7 @@
|
|
|
1929
1967
|
},
|
|
1930
1968
|
{
|
|
1931
1969
|
"name": "aadhar_front",
|
|
1932
|
-
"type": "
|
|
1970
|
+
"type": "file",
|
|
1933
1971
|
"required": true,
|
|
1934
1972
|
"description": "Front side of the Aadhaar card (multipart/form-data). Accepted formats: JPEG, JPG, PDF. Max size: 1 MB.",
|
|
1935
1973
|
"example": "<binary file>",
|
|
@@ -1937,7 +1975,7 @@
|
|
|
1937
1975
|
},
|
|
1938
1976
|
{
|
|
1939
1977
|
"name": "aadhar_back",
|
|
1940
|
-
"type": "
|
|
1978
|
+
"type": "file",
|
|
1941
1979
|
"required": true,
|
|
1942
1980
|
"description": "Back side of the Aadhaar card (multipart/form-data). Accepted formats: JPEG, JPG, PDF. Max size: 1 MB.",
|
|
1943
1981
|
"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
|
|
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
|
-
|
|
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
|
|
159
|
+
content: [{ type: "text", text: JSON.stringify(value) }]
|
|
150
160
|
});
|
|
151
|
-
var
|
|
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).
|
|
161
|
-
inputSchema: {
|
|
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,31 +213,51 @@ var createEpsServer = (bundle, source) => {
|
|
|
184
213
|
"search",
|
|
185
214
|
{
|
|
186
215
|
title: "Search APIs",
|
|
187
|
-
description:
|
|
188
|
-
inputSchema: {
|
|
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
|
-
|
|
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(
|
|
205
247
|
"get_topic",
|
|
206
248
|
{
|
|
207
249
|
title: "Get topic",
|
|
208
|
-
description: "One topic: auth | errors | pricing | environments.",
|
|
250
|
+
description: "One topic: getting-started | auth | errors | pricing | environments.",
|
|
209
251
|
inputSchema: {
|
|
210
|
-
topic: z.enum([
|
|
211
|
-
|
|
252
|
+
topic: z.enum([
|
|
253
|
+
"getting-started",
|
|
254
|
+
"auth",
|
|
255
|
+
"errors",
|
|
256
|
+
"pricing",
|
|
257
|
+
"environments"
|
|
258
|
+
])
|
|
259
|
+
},
|
|
260
|
+
annotations: READ_ONLY
|
|
212
261
|
},
|
|
213
262
|
async ({ topic }) => json(getTopic(bundle, topic))
|
|
214
263
|
);
|
|
@@ -217,11 +266,16 @@ var createEpsServer = (bundle, source) => {
|
|
|
217
266
|
{
|
|
218
267
|
title: "Get recipe",
|
|
219
268
|
description: "One multi-step recipe (steps + branches) by id.",
|
|
220
|
-
inputSchema: { id: z.string() }
|
|
269
|
+
inputSchema: { id: z.string() },
|
|
270
|
+
annotations: READ_ONLY
|
|
221
271
|
},
|
|
222
272
|
async ({ id }) => {
|
|
223
273
|
const recipe = getRecipe(bundle, id);
|
|
224
|
-
|
|
274
|
+
if (recipe) return json(recipe);
|
|
275
|
+
const valid = listRecipes(bundle).map((r) => r.id).join(", ");
|
|
276
|
+
return notFound(
|
|
277
|
+
`Unknown recipe "${id}". Valid recipe ids: ${valid}. Use list_recipes for details.`
|
|
278
|
+
);
|
|
225
279
|
}
|
|
226
280
|
);
|
|
227
281
|
server.registerTool(
|
|
@@ -229,7 +283,8 @@ var createEpsServer = (bundle, source) => {
|
|
|
229
283
|
{
|
|
230
284
|
title: "Get signing snippet",
|
|
231
285
|
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) }
|
|
286
|
+
inputSchema: { language: z.enum(SIGNING_LANGUAGES) },
|
|
287
|
+
annotations: READ_ONLY
|
|
233
288
|
},
|
|
234
289
|
async ({ language }) => ({
|
|
235
290
|
content: [{ type: "text", text: getSigningSnippet(language) }]
|
|
@@ -239,18 +294,75 @@ var createEpsServer = (bundle, source) => {
|
|
|
239
294
|
"get_meta",
|
|
240
295
|
{
|
|
241
296
|
title: "Get meta",
|
|
242
|
-
description: "Bundle org/version + data source.",
|
|
243
|
-
inputSchema: {}
|
|
297
|
+
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`.",
|
|
298
|
+
inputSchema: {},
|
|
299
|
+
annotations: READ_ONLY
|
|
244
300
|
},
|
|
245
|
-
async () => json({
|
|
301
|
+
async () => json({
|
|
302
|
+
...bundle.meta,
|
|
303
|
+
source,
|
|
304
|
+
packageVersion: versionState?.current,
|
|
305
|
+
latestVersion: versionState?.latest,
|
|
306
|
+
updateAvailable: versionState?.updateAvailable
|
|
307
|
+
})
|
|
246
308
|
);
|
|
247
309
|
return server;
|
|
248
310
|
};
|
|
249
311
|
|
|
312
|
+
// src/update-check.ts
|
|
313
|
+
var REGISTRY_URL = "https://registry.npmjs.org/@ekoindia/eps-context-mcp/latest";
|
|
314
|
+
var compareVersions = (a, b) => {
|
|
315
|
+
const parse = (v) => {
|
|
316
|
+
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v.trim());
|
|
317
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
318
|
+
};
|
|
319
|
+
const pa = parse(a);
|
|
320
|
+
const pb = parse(b);
|
|
321
|
+
if (!pa || !pb) return null;
|
|
322
|
+
for (let i = 0; i < 3; i++) {
|
|
323
|
+
if (pa[i] !== pb[i]) return pa[i] > pb[i] ? 1 : -1;
|
|
324
|
+
}
|
|
325
|
+
return 0;
|
|
326
|
+
};
|
|
327
|
+
var checkForUpdate = async (state) => {
|
|
328
|
+
if (process.env.EPS_NO_UPDATE_CHECK) return;
|
|
329
|
+
try {
|
|
330
|
+
const res = await fetch(REGISTRY_URL, {
|
|
331
|
+
signal: AbortSignal.timeout(3e3)
|
|
332
|
+
});
|
|
333
|
+
if (!res.ok) return;
|
|
334
|
+
const data = await res.json();
|
|
335
|
+
if (typeof data.version !== "string") return;
|
|
336
|
+
const cmp = compareVersions(data.version, state.current);
|
|
337
|
+
if (cmp === null) return;
|
|
338
|
+
state.latest = data.version;
|
|
339
|
+
state.updateAvailable = cmp > 0;
|
|
340
|
+
if (state.updateAvailable) {
|
|
341
|
+
console.error(
|
|
342
|
+
`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.`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
250
349
|
// src/index.ts
|
|
350
|
+
function readPackageVersion() {
|
|
351
|
+
try {
|
|
352
|
+
const here2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
353
|
+
const pkg = JSON.parse(
|
|
354
|
+
readFileSync(path2.resolve(here2, "../package.json"), "utf8")
|
|
355
|
+
);
|
|
356
|
+
return pkg.version ?? "0.0.0";
|
|
357
|
+
} catch {
|
|
358
|
+
return "0.0.0";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
251
361
|
async function main() {
|
|
252
362
|
const { bundle, source } = await loadBundle();
|
|
253
|
-
const
|
|
363
|
+
const versionState = { current: readPackageVersion() };
|
|
364
|
+
void checkForUpdate(versionState);
|
|
365
|
+
const server = createEpsServer(bundle, source, versionState);
|
|
254
366
|
const transport = new StdioServerTransport();
|
|
255
367
|
await server.connect(transport);
|
|
256
368
|
console.error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekoindia/eps-context-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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": "^
|
|
31
|
+
"zod": "^4.0.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"tsup": "^8.0.0",
|