@ekoindia/eps-context-mcp 0.1.2 → 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 +66 -26
- package/data/eps.json +22 -22
- package/dist/index.js +131 -25
- 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,38 +31,52 @@ 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 -- 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
|
|
68
|
+
`--scope user` for every project on this machine, or drop the flag for local
|
|
69
|
+
scope (private to this checkout, not shared).
|
|
70
|
+
|
|
71
|
+
Or add to your MCP config (project `.mcp.json`, or `~/.claude.json` for user scope):
|
|
57
72
|
|
|
58
73
|
```json
|
|
59
74
|
{
|
|
60
75
|
"mcpServers": {
|
|
61
|
-
"eps": {
|
|
76
|
+
"eps": {
|
|
77
|
+
"command": "npx",
|
|
78
|
+
"args": ["-y", "@ekoindia/eps-context-mcp@latest"]
|
|
79
|
+
}
|
|
62
80
|
}
|
|
63
81
|
}
|
|
64
82
|
```
|
|
@@ -70,7 +88,10 @@ Or add to your MCP config (`~/.claude.json` / project `.mcp.json`):
|
|
|
70
88
|
```json
|
|
71
89
|
{
|
|
72
90
|
"mcpServers": {
|
|
73
|
-
"eps": {
|
|
91
|
+
"eps": {
|
|
92
|
+
"command": "npx",
|
|
93
|
+
"args": ["-y", "@ekoindia/eps-context-mcp@latest"]
|
|
94
|
+
}
|
|
74
95
|
}
|
|
75
96
|
}
|
|
76
97
|
```
|
|
@@ -84,7 +105,7 @@ Or add to your MCP config (`~/.claude.json` / project `.mcp.json`):
|
|
|
84
105
|
"mcp": {
|
|
85
106
|
"eps": {
|
|
86
107
|
"type": "local",
|
|
87
|
-
"command": ["npx", "-y", "@ekoindia/eps-context-mcp"],
|
|
108
|
+
"command": ["npx", "-y", "@ekoindia/eps-context-mcp@latest"],
|
|
88
109
|
"enabled": true
|
|
89
110
|
}
|
|
90
111
|
}
|
|
@@ -99,7 +120,7 @@ Or add to your MCP config (`~/.claude.json` / project `.mcp.json`):
|
|
|
99
120
|
mcpServers:
|
|
100
121
|
- name: eps
|
|
101
122
|
command: npx
|
|
102
|
-
args: ["-y", "@ekoindia/eps-context-mcp"]
|
|
123
|
+
args: ["-y", "@ekoindia/eps-context-mcp@latest"]
|
|
103
124
|
```
|
|
104
125
|
|
|
105
126
|
### Codex CLI
|
|
@@ -109,7 +130,7 @@ mcpServers:
|
|
|
109
130
|
```toml
|
|
110
131
|
[mcp_servers.eps]
|
|
111
132
|
command = "npx"
|
|
112
|
-
args = ["-y", "@ekoindia/eps-context-mcp"]
|
|
133
|
+
args = ["-y", "@ekoindia/eps-context-mcp@latest"]
|
|
113
134
|
```
|
|
114
135
|
|
|
115
136
|
### Gemini CLI
|
|
@@ -119,11 +140,30 @@ args = ["-y", "@ekoindia/eps-context-mcp"]
|
|
|
119
140
|
```json
|
|
120
141
|
{
|
|
121
142
|
"mcpServers": {
|
|
122
|
-
"eps": {
|
|
143
|
+
"eps": {
|
|
144
|
+
"command": "npx",
|
|
145
|
+
"args": ["-y", "@ekoindia/eps-context-mcp@latest"]
|
|
146
|
+
}
|
|
123
147
|
}
|
|
124
148
|
}
|
|
125
149
|
```
|
|
126
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
|
+
|
|
127
167
|
## Configuration
|
|
128
168
|
|
|
129
169
|
### `EPS_BUNDLE_URL` (optional)
|
|
@@ -136,16 +176,16 @@ bundle at startup (e.g. the latest generated `eps.json`), set `EPS_BUNDLE_URL`:
|
|
|
136
176
|
"mcpServers": {
|
|
137
177
|
"eps": {
|
|
138
178
|
"command": "npx",
|
|
139
|
-
"args": ["-y", "@ekoindia/eps-context-mcp"],
|
|
179
|
+
"args": ["-y", "@ekoindia/eps-context-mcp@latest"],
|
|
140
180
|
"env": { "EPS_BUNDLE_URL": "https://eps.eko.in/agent/eps.json" }
|
|
141
181
|
}
|
|
142
182
|
}
|
|
143
183
|
}
|
|
144
184
|
```
|
|
145
185
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
`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`).
|
|
149
189
|
|
|
150
190
|
## Security: backend-only signing
|
|
151
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": "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": "
|
|
1860
|
-
"example": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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>",
|
|
@@ -4901,7 +4901,7 @@
|
|
|
4901
4901
|
"status": 0,
|
|
4902
4902
|
"response_status_id": 0,
|
|
4903
4903
|
"message": "Bill payment successful",
|
|
4904
|
-
"response_type_id":
|
|
4904
|
+
"response_type_id": 333,
|
|
4905
4905
|
"tx_status": "0",
|
|
4906
4906
|
"txstatus_desc": "Success",
|
|
4907
4907
|
"data": {
|
|
@@ -9673,11 +9673,11 @@
|
|
|
9673
9673
|
"name": "Get Aadhaar KYC Consent Languages",
|
|
9674
9674
|
"method": "GET",
|
|
9675
9675
|
"path": "/customer/payment/ppi-digikhata/sender/{customer_id}/aadhaar/consent/languages",
|
|
9676
|
-
"summary": "List the languages available for the DigiKhata Aadhaar
|
|
9676
|
+
"summary": "List the languages available for the DigiKhata Aadhaar eKYC consent.",
|
|
9677
9677
|
"category": "bc",
|
|
9678
9678
|
"relevance": "L",
|
|
9679
|
-
"description": "Returns the supported consent languages (with their `pkid`) for DigiKhata Aadhaar
|
|
9680
|
-
"bestFor": "Presenting Aadhaar
|
|
9679
|
+
"description": "Returns the supported consent languages (with their `pkid`) for DigiKhata Aadhaar eKYC. Pass the chosen `pkid` as `consent_language` to Get Aadhaar KYC Consent Details.",
|
|
9680
|
+
"bestFor": "Presenting Aadhaar eKYC consent in the customer's language.",
|
|
9681
9681
|
"docsUrl": "https://eps.eko.in/docs/ppi-digikhata-consent-languages",
|
|
9682
9682
|
"headers": [
|
|
9683
9683
|
{
|
|
@@ -9870,11 +9870,11 @@
|
|
|
9870
9870
|
"name": "Get Aadhaar KYC Consent Details",
|
|
9871
9871
|
"method": "GET",
|
|
9872
9872
|
"path": "/customer/payment/ppi-digikhata/sender/{customer_id}/aadhaar/consent/details",
|
|
9873
|
-
"summary": "Fetch the DigiKhata Aadhaar
|
|
9873
|
+
"summary": "Fetch the DigiKhata Aadhaar eKYC consent text and audio for a chosen language.",
|
|
9874
9874
|
"category": "bc",
|
|
9875
9875
|
"relevance": "L",
|
|
9876
|
-
"description": "Returns the full Aadhaar
|
|
9877
|
-
"bestFor": "Showing the mandatory Aadhaar
|
|
9876
|
+
"description": "Returns the full Aadhaar eKYC consent content, short consent statement, and an audio URL for the language selected (via `consent_language` = the `pkid` from Get Aadhaar KYC Consent Languages). Display/play this consent before collecting Aadhaar OTP.",
|
|
9877
|
+
"bestFor": "Showing the mandatory Aadhaar eKYC consent before OTP capture.",
|
|
9878
9878
|
"docsUrl": "https://eps.eko.in/docs/ppi-digikhata-consent-details",
|
|
9879
9879
|
"headers": [
|
|
9880
9880
|
{
|
|
@@ -9989,7 +9989,7 @@
|
|
|
9989
9989
|
{
|
|
9990
9990
|
"name": "consentContent",
|
|
9991
9991
|
"type": "string",
|
|
9992
|
-
"description": "Full Aadhaar
|
|
9992
|
+
"description": "Full Aadhaar eKYC consent text.",
|
|
9993
9993
|
"example": "Use my Aadhaar / Virtual ID details …"
|
|
9994
9994
|
},
|
|
9995
9995
|
{
|
|
@@ -10026,10 +10026,10 @@
|
|
|
10026
10026
|
"response_status_id": 0,
|
|
10027
10027
|
"data": {
|
|
10028
10028
|
"consent_detail": {
|
|
10029
|
-
"consentContent": "Use my Aadhaar / Virtual ID details (as applicable) for the purpose of
|
|
10029
|
+
"consentContent": "Use my Aadhaar / Virtual ID details (as applicable) for the purpose of eKYC for/with PayPoint India to authenticate my identity through the Aadhaar Authentication system (Aadhaar based eKYC services of UIDAI) in accordance with the provisions of the Aadhaar (Targeted Delivery of Financial and other Subsidies, Benefits and Services) Act, 2016 and the allied rules and regulations notified thereunder and for no other purpose.\r\n Authenticate my Aadhaar/Virtual ID through OTP or Biometric for authenticating my identity through the Aadhaar Authentication system for obtaining my eKYC through Aadhaar based eKYC services of UIDAI and use my Photo and Demographic details (Name, Gender, Date of Birth and Address) for the purpose of eKYC for/with PayPoint India.\r\n I understand that Security and confidentiality of personal identity data provided, for the purpose of Aadhaar based authentication is ensured by PayPoint and the data will be stored by PayPoint till such time as mentioned in guidelines from UIDAI from time to time.",
|
|
10030
10030
|
"audioUrl": "https://paypointindia.co.in/audio/Consent_English.mp3",
|
|
10031
10031
|
"consentId": 1,
|
|
10032
|
-
"consent": "Consent for Authentication: I, the holder of Aadhaar number, hereby give my consent to Paypoint India Network Private Limited to perform authentication and obtain my
|
|
10032
|
+
"consent": "Consent for Authentication: I, the holder of Aadhaar number, hereby give my consent to Paypoint India Network Private Limited to perform authentication and obtain my eKYC with UIDAI for the purpose of creating my wallet.",
|
|
10033
10033
|
"consentLanguage": "English"
|
|
10034
10034
|
}
|
|
10035
10035
|
},
|
|
@@ -10049,8 +10049,8 @@
|
|
|
10049
10049
|
"summary": "Validate a DigiKhata sender's Aadhaar number and trigger an OTP to the linked mobile.",
|
|
10050
10050
|
"category": "bc",
|
|
10051
10051
|
"relevance": "M",
|
|
10052
|
-
"description": "Submits the sender's Aadhaar number and dispatches an OTP to the Aadhaar-linked mobile for
|
|
10053
|
-
"bestFor": "Starting DigiKhata Aadhaar
|
|
10052
|
+
"description": "Submits the sender's Aadhaar number and dispatches an OTP to the Aadhaar-linked mobile for eKYC. Returns an `otp_ref_id` to pass into Validate Sender Aadhaar OTP.",
|
|
10053
|
+
"bestFor": "Starting DigiKhata Aadhaar eKYC for a sender.",
|
|
10054
10054
|
"docsUrl": "https://eps.eko.in/docs/ppi-digikhata-generate-aadhaar-otp",
|
|
10055
10055
|
"headers": [
|
|
10056
10056
|
{
|
|
@@ -10188,11 +10188,11 @@
|
|
|
10188
10188
|
"name": "Validate Sender Aadhaar OTP",
|
|
10189
10189
|
"method": "POST",
|
|
10190
10190
|
"path": "/customer/payment/ppi-digikhata/sender/{customer_id}/aadhaar/otp/verify",
|
|
10191
|
-
"summary": "Verify the Aadhaar OTP to complete DigiKhata sender Aadhaar
|
|
10191
|
+
"summary": "Verify the Aadhaar OTP to complete DigiKhata sender Aadhaar eKYC.",
|
|
10192
10192
|
"category": "bc",
|
|
10193
10193
|
"relevance": "M",
|
|
10194
10194
|
"description": "Validates the OTP dispatched during Generate Sender Aadhaar OTP. On success the Aadhaar identity is confirmed and the flow proceeds to PAN validation (Pan Number Required).",
|
|
10195
|
-
"bestFor": "Completing DigiKhata sender Aadhaar
|
|
10195
|
+
"bestFor": "Completing DigiKhata sender Aadhaar eKYC.",
|
|
10196
10196
|
"docsUrl": "https://eps.eko.in/docs/ppi-digikhata-verify-aadhaar-otp",
|
|
10197
10197
|
"headers": [
|
|
10198
10198
|
{
|
|
@@ -14324,9 +14324,9 @@
|
|
|
14324
14324
|
},
|
|
14325
14325
|
{
|
|
14326
14326
|
"name": "value",
|
|
14327
|
-
"type": "
|
|
14327
|
+
"type": "number",
|
|
14328
14328
|
"description": "Parameter value.",
|
|
14329
|
-
"example":
|
|
14329
|
+
"example": 400
|
|
14330
14330
|
}
|
|
14331
14331
|
]
|
|
14332
14332
|
}
|
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,21 +213,34 @@ 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(
|
|
@@ -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
|
-
|
|
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({
|
|
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
|
|
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
|
+
"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": "^
|
|
31
|
+
"zod": "^4.0.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"tsup": "^8.0.0",
|