@ekoindia/eps-transact-mcp 0.1.0 → 0.1.2
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 +7 -3
- package/data/eps.json +44 -6
- package/dist/{chunk-AAUR66A3.js → chunk-XLZLL6DZ.js} +77 -9
- package/dist/index.js +1 -1
- package/dist/stdio.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -55,13 +55,15 @@ Optional env: `EKO_ENV` (`uat` default | `production`), `EKO_ALLOWED_APIS` (defa
|
|
|
55
55
|
|
|
56
56
|
## Staying up to date
|
|
57
57
|
|
|
58
|
-
- **Remote server** — nothing to do. It's hosted
|
|
58
|
+
- **Remote server** — nothing to do. It's hosted and auto-redeploys on every published update (its poller pulls the new `:prod`), so every client is instantly current. `GET /healthz` reports the live `bundleVersion`.
|
|
59
59
|
- **Local stdio** — the `@latest` in the install command re-resolves the newest published version on every launch, so `npx` always fetches current. `@latest` does a registry lookup at start; the server runs fully offline after that. Offline/air-gapped? Pin a version: `npx --offline -y @ekoindia/eps-transact-mcp@<version>`.
|
|
60
60
|
- **Update check** — on startup the stdio bin does one best-effort `GET registry.npmjs.org/@ekoindia/eps-transact-mcp/latest` (3s timeout, silent on any failure) and, if your config pinned an older version, prints a one-line stderr nudge. It never blocks startup, sends no data, and touches nothing but stderr. Disable with `EPS_NO_UPDATE_CHECK=1` (corporate/no-egress). The remote server does **not** run this check.
|
|
61
61
|
|
|
62
62
|
## Tools
|
|
63
63
|
|
|
64
|
-
One tool per verification endpoint, named `eps_<slug with underscores>` — e.g. `eps_pan_lite`, `eps_bank_account_verification`, `eps_verify_gstin`, `eps_driving_license`. Every tool description carries the billing reminder; input schemas (required params, types, examples) are generated from the API specs. Multi-step flows (mobile OTP, DigiLocker) are exposed as their individual steps — the server is stateless; your agent carries intermediate ids between calls.
|
|
64
|
+
One tool per verification endpoint, named `eps_<slug with underscores>` — e.g. `eps_pan_lite`, `eps_bank_account_verification`, `eps_verify_gstin`, `eps_driving_license`. Every tool description carries the billing reminder; input schemas (required params, types, examples — including item shapes for array params like bulk `entries`) are generated from the API specs. Multi-step flows (mobile OTP, DigiLocker, bulk verify) are exposed as their individual steps — the server is stateless; your agent carries intermediate ids between calls, and each step's description says which tool comes next and which field carries over.
|
|
65
|
+
|
|
66
|
+
Every tool declares MCP annotations: `openWorldHint: true` on all (they call a paid external API), and `readOnlyHint: false` + `idempotentHint: false` on the side-effecting ones — `eps_bank_account_verification` (live, non-refundable ₹1 penny-drop), the two bulk-submit tools (enqueue async batches), `eps_mobile_otp_send`/`eps_mobile_otp_verify` (send/consume an OTP), and `eps_digilocker_create_url` (creates a consent session). Their descriptions state the side effect instead of "Read-only verification". Hosts should gate side-effecting tools accordingly; billing applies to **all** successful calls either way.
|
|
65
67
|
|
|
66
68
|
Errors come back sanitized as `{ code, message }`: `VALIDATION` (names the missing/invalid params — never their values), `TOOL_NOT_ALLOWED`, `UNKNOWN_TOOL`, `UPSTREAM_TIMEOUT`, `UPSTREAM_ERROR`. Successful upstream envelopes (including business failures like "PAN not found") are returned verbatim — they are your data.
|
|
67
69
|
|
|
@@ -84,7 +86,9 @@ npm run transact:dev # HTTP server on :8788 with tsx watch
|
|
|
84
86
|
npm run transact:typecheck
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
Four utils (`fetchTimeout`, `requestId`, `accessLog`, `update-check`) are hand-copied/adapted from eps-backend / eps-context-mcp; `parity.copied-utils.test.ts` pins a content hash of both sides of each pair, so editing either file fails the suite until you diff the pair, port what applies, and paste the replacement hash printed in the failure.
|
|
90
|
+
|
|
91
|
+
Tests never call Eko; upstream fetch is injected. The one exception is the opt-in live UAT smoke (`uat-smoke.test.ts`), which self-skips unless real UAT credentials are set. Copy `.env.example` to `.env` in this package (gitignored; vitest loads `EPS_UAT_*` from it) or pass the vars inline — inline wins:
|
|
88
92
|
|
|
89
93
|
```sh
|
|
90
94
|
EPS_UAT_DEVELOPER_KEY=… EPS_UAT_ACCESS_KEY=… EPS_UAT_INITIATOR_ID=… \
|
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>",
|
|
@@ -26,12 +26,62 @@ var EXECUTOR_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
|
26
26
|
"content-type"
|
|
27
27
|
]);
|
|
28
28
|
var IDENTITY_PARAMS = /* @__PURE__ */ new Set(["initiator_id", "user_code"]);
|
|
29
|
+
var SIDE_EFFECTS = {
|
|
30
|
+
"bank-account-verification": "Performs a live, non-refundable \u20B91 penny-drop to the target account",
|
|
31
|
+
"bulk-bank-account-verification": "Enqueues an async penny-drop verification batch",
|
|
32
|
+
"pan-bulk-verify": "Enqueues an async PAN verification batch",
|
|
33
|
+
"mobile-otp-send": "Sends an OTP SMS to the target mobile number",
|
|
34
|
+
"mobile-otp-verify": "Consumes the OTP and advances the verification session",
|
|
35
|
+
"digilocker-create-url": "Creates a DigiLocker consent session"
|
|
36
|
+
};
|
|
37
|
+
var FLOW_GUIDANCE = {
|
|
38
|
+
"pan-bulk-verify": "Then poll eps_pan_bulk_status with the returned reference_id.",
|
|
39
|
+
"pan-bulk-status": "Poll step: pass the reference_id returned by eps_pan_bulk_verify.",
|
|
40
|
+
"bulk-bank-account-verification": "Then poll eps_bulk_bank_account_verification_status with the returned bulk_reference_id.",
|
|
41
|
+
"bulk-bank-account-verification-status": "Poll step: pass the bulk_reference_id returned by eps_bulk_bank_account_verification.",
|
|
42
|
+
"digilocker-create-url": "Flow step 1/3: after user consent, call eps_digilocker_get_document with the returned verification_id and reference_id.",
|
|
43
|
+
"digilocker-get-document": "Flow step 2/3: pass the verification_id and reference_id from eps_digilocker_create_url.",
|
|
44
|
+
"digilocker-verification-status": "Flow step 3/3: pass the reference_id from eps_digilocker_create_url.",
|
|
45
|
+
"mobile-otp-send": "Flow step 1/3: then call eps_mobile_otp_verify with the OTP the user received.",
|
|
46
|
+
"mobile-otp-verify": "Flow step 2/3: use the same mobile as eps_mobile_otp_send; returns an otp_verification_token.",
|
|
47
|
+
"mobile-otp-validate-token": "Flow step 3/3: pass the otp_verification_token returned by eps_mobile_otp_verify."
|
|
48
|
+
};
|
|
29
49
|
var toToolName = (slug) => `eps_${slug.replace(/-/g, "_")}`;
|
|
30
50
|
var verificationApis = (bundle) => bundle.apis.filter((a) => a.category === "verification" && !a.financial);
|
|
31
51
|
var JSON_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean"]);
|
|
32
|
-
var
|
|
52
|
+
var scalarTypeOf = (value) => {
|
|
53
|
+
if (typeof value === "string") return "string";
|
|
54
|
+
if (typeof value === "number") return "number";
|
|
55
|
+
if (typeof value === "boolean") return "boolean";
|
|
56
|
+
return void 0;
|
|
57
|
+
};
|
|
58
|
+
var arrayItems = (p, children) => {
|
|
59
|
+
if (children.length) {
|
|
60
|
+
const properties = {};
|
|
61
|
+
for (const c of children)
|
|
62
|
+
properties[c.name.slice(p.name.length + 3)] = paramToJsonSchema(c);
|
|
63
|
+
const required = children.filter((c) => c.required).map((c) => c.name.slice(p.name.length + 3));
|
|
64
|
+
return { type: "object", properties, required };
|
|
65
|
+
}
|
|
66
|
+
const first = Array.isArray(p.example) ? p.example[0] : void 0;
|
|
67
|
+
if (first !== null && typeof first === "object") {
|
|
68
|
+
const properties = {};
|
|
69
|
+
for (const [key, value] of Object.entries(first)) {
|
|
70
|
+
const type2 = scalarTypeOf(value);
|
|
71
|
+
properties[key] = type2 ? { type: type2 } : {};
|
|
72
|
+
}
|
|
73
|
+
return { type: "object", properties };
|
|
74
|
+
}
|
|
75
|
+
const type = scalarTypeOf(first);
|
|
76
|
+
return type ? { type } : {};
|
|
77
|
+
};
|
|
78
|
+
var paramToJsonSchema = (p, arrayChildren = []) => {
|
|
33
79
|
const schema = {};
|
|
34
80
|
if (JSON_SCHEMA_TYPES.has(p.type)) schema.type = p.type;
|
|
81
|
+
else if (p.type === "array") {
|
|
82
|
+
schema.type = "array";
|
|
83
|
+
schema.items = arrayItems(p, arrayChildren);
|
|
84
|
+
}
|
|
35
85
|
const description = [
|
|
36
86
|
p.description ?? p.label ?? p.name,
|
|
37
87
|
p.example !== void 0 ? `Example: ${JSON.stringify(p.example)}` : ""
|
|
@@ -47,15 +97,33 @@ var buildToolDefs = (bundle) => verificationApis(bundle).map((api) => {
|
|
|
47
97
|
throw new Error(
|
|
48
98
|
`Spec "${api.slug}" requires header(s) ${unrepresentable.map((h) => h.name).join(", ")} which the transactional executor cannot send. Add header support to the executor before exposing this tool.`
|
|
49
99
|
);
|
|
100
|
+
const topLevel = api.requestParams.filter((p) => !p.name.includes("[]."));
|
|
101
|
+
const childrenOf = (parent) => api.requestParams.filter((p) => p.name.startsWith(`${parent.name}[].`));
|
|
50
102
|
const properties = {};
|
|
51
|
-
for (const p of
|
|
52
|
-
properties[p.name] = paramToJsonSchema(p);
|
|
53
|
-
const required =
|
|
103
|
+
for (const p of topLevel)
|
|
104
|
+
properties[p.name] = paramToJsonSchema(p, childrenOf(p));
|
|
105
|
+
const required = topLevel.filter((p) => p.required).map((p) => p.name);
|
|
106
|
+
const sideEffect = SIDE_EFFECTS[api.slug];
|
|
107
|
+
const description = [
|
|
108
|
+
api.summary,
|
|
109
|
+
sideEffect ? `${sideEffect} via Eko EPS (${api.method} ${api.path}).` : `Read-only verification via Eko EPS (${api.method} ${api.path}).`,
|
|
110
|
+
"Each successful call is billed per EPS pricing.",
|
|
111
|
+
FLOW_GUIDANCE[api.slug]
|
|
112
|
+
].filter(Boolean).join(" ");
|
|
54
113
|
return {
|
|
55
114
|
name: toToolName(api.slug),
|
|
56
115
|
slug: api.slug,
|
|
57
|
-
|
|
58
|
-
|
|
116
|
+
title: api.name,
|
|
117
|
+
description,
|
|
118
|
+
inputSchema: { type: "object", properties, required },
|
|
119
|
+
annotations: {
|
|
120
|
+
readOnlyHint: !sideEffect,
|
|
121
|
+
// Meaningful only when readOnlyHint is false (MCP spec); every
|
|
122
|
+
// side-effecting call here does something new (new penny-drop,
|
|
123
|
+
// new SMS, new batch, new session).
|
|
124
|
+
...sideEffect && { idempotentHint: false },
|
|
125
|
+
openWorldHint: true
|
|
126
|
+
}
|
|
59
127
|
};
|
|
60
128
|
});
|
|
61
129
|
|
|
@@ -112,7 +180,9 @@ var createTransactServer = (tools, ctx, version) => {
|
|
|
112
180
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
113
181
|
tools: visibleTools.map((t) => ({
|
|
114
182
|
name: t.name,
|
|
183
|
+
title: t.title,
|
|
115
184
|
description: t.description,
|
|
185
|
+
annotations: t.annotations,
|
|
116
186
|
// Identity params covered by a server-side default are demoted from
|
|
117
187
|
// `required` so schema-validating hosts don't force the model to
|
|
118
188
|
// invent them; EpsClient still enforces presence after merging.
|
|
@@ -151,9 +221,7 @@ var createTransactServer = (tools, ctx, version) => {
|
|
|
151
221
|
req.params.arguments ?? {}
|
|
152
222
|
);
|
|
153
223
|
return {
|
|
154
|
-
content: [
|
|
155
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
156
|
-
]
|
|
224
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
157
225
|
};
|
|
158
226
|
} catch (err) {
|
|
159
227
|
return errorResult(sanitizeError(err));
|
package/dist/index.js
CHANGED
package/dist/stdio.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekoindia/eps-transact-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Transactional MCP server for Eko Platform Services (EPS) verification APIs — remote (streamable HTTP) and local (stdio).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"prepublishOnly": "npm run bake && npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ekoindia/eps-sdk": "
|
|
32
|
+
"@ekoindia/eps-sdk": "^0.1.1",
|
|
33
33
|
"@hono/node-server": "^1.0.0",
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
35
35
|
"hono": "^4.0.0"
|