@esimfly/mcp 0.1.0
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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/SECURITY.md +15 -0
- package/dist/chunk-Q3UI3CYF.js +422 -0
- package/dist/chunk-Q3UI3CYF.js.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.js +9 -0
- package/dist/server.js.map +1 -0
- package/package.json +75 -0
- package/server.json +23 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 eSIMfly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# @esimfly/mcp — eSIMfly MCP server
|
|
2
|
+
|
|
3
|
+
Give your AI assistant hands on the eSIMfly Business API. With this
|
|
4
|
+
[Model Context Protocol](https://modelcontextprotocol.io) server, Claude, Cursor, ChatGPT and other
|
|
5
|
+
MCP clients can search eSIM plans with your wholesale prices, check balances and usage, diagnose
|
|
6
|
+
"no data" problems from live network data — and, if you enable it, place orders and top-ups with an
|
|
7
|
+
explicit confirmation step.
|
|
8
|
+
|
|
9
|
+
- **Read-only by default.** Nothing can spend your balance unless you opt in.
|
|
10
|
+
- **Two-step writes.** Every write tool returns a preview (what, cost, balance) until called with `confirm: true`.
|
|
11
|
+
- **Built on the official [`@esimfly/sdk`](https://www.npmjs.com/package/@esimfly/sdk)** — signing, retries and error codes handled.
|
|
12
|
+
- Ships two prompts: the complete integration guide and an eSIM diagnosis workflow.
|
|
13
|
+
|
|
14
|
+
Docs: **https://docs.esimfly.net** · Credentials: Business Dashboard → Settings → API Keys.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
The server runs locally over stdio; your API key never leaves your machine.
|
|
19
|
+
|
|
20
|
+
### Claude Desktop
|
|
21
|
+
|
|
22
|
+
`claude_desktop_config.json` → `mcpServers`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"esimfly": {
|
|
28
|
+
"command": "npx",
|
|
29
|
+
"args": ["-y", "@esimfly/mcp"],
|
|
30
|
+
"env": {
|
|
31
|
+
"ESIMFLY_ACCESS_CODE": "esf_...",
|
|
32
|
+
"ESIMFLY_SECRET_KEY": "sk_..."
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Claude Code
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
claude mcp add esimfly -e ESIMFLY_ACCESS_CODE=esf_... -e ESIMFLY_SECRET_KEY=sk_... -- npx -y @esimfly/mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Cursor / Windsurf / other MCP clients
|
|
46
|
+
|
|
47
|
+
`.cursor/mcp.json` (or the client's equivalent):
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"esimfly": {
|
|
53
|
+
"command": "npx",
|
|
54
|
+
"args": ["-y", "@esimfly/mcp"],
|
|
55
|
+
"env": { "ESIMFLY_ACCESS_CODE": "esf_...", "ESIMFLY_SECRET_KEY": "sk_..." }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Enable write tools
|
|
62
|
+
|
|
63
|
+
Add `"ESIMFLY_MCP_ALLOW_WRITES": "true"` to `env`. Without it the ordering, top-up, cancel,
|
|
64
|
+
suspend, SMS and webhook tools are not even registered.
|
|
65
|
+
|
|
66
|
+
## Try it
|
|
67
|
+
|
|
68
|
+
- "Find the cheapest 5 GB plan for Turkey and tell me my margin at €9.99."
|
|
69
|
+
- "Which of my eSIMs are active and below 200 MB?"
|
|
70
|
+
- "ICCID 8948010010036785060 says no data — diagnose it." *(uses the `diagnose_esim` prompt)*
|
|
71
|
+
- "Set up webhooks for installed / status / low-data events at https://my.app/hooks." *(write tool, previews first)*
|
|
72
|
+
- "Build me a Node.js integration." *(uses the `esimfly_integration_guide` prompt)*
|
|
73
|
+
|
|
74
|
+
## Tools
|
|
75
|
+
|
|
76
|
+
| Tool | What it does | Mode |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `search_packages` | Catalogue search by destination / type with your cost price | read |
|
|
79
|
+
| `get_balance` | Account (or enterprise) balance | read |
|
|
80
|
+
| `list_esims` | Your eSIMs with status, data left, validity | read |
|
|
81
|
+
| `get_esim_usage` | Stored usage for one eSIM (cheap) | read |
|
|
82
|
+
| `get_esim_live_status` | Live status from the network: install state, last network, device, usage | read (expensive) |
|
|
83
|
+
| `get_network_events` | Last 7 days of attach / data-session events, wrong-network flag | read |
|
|
84
|
+
| `get_usage_report` | Daily usage by country and operator (up to 90 days) | read |
|
|
85
|
+
| `list_orders` / `get_order` | Order history and one order with its eSIM | read |
|
|
86
|
+
| `get_topup_packages` | Top-up options for one eSIM | read |
|
|
87
|
+
| `get_webhook_settings` | Webhook URL, events, recent deliveries | read |
|
|
88
|
+
| `create_order` | Buy eSIMs — preview → `confirm: true` + idempotency key | write |
|
|
89
|
+
| `topup_esim` | Add data to an eSIM — preview shows package and cost | write |
|
|
90
|
+
| `cancel_esim` | Cancel an unused eSIM and refund to balance | write (destructive) |
|
|
91
|
+
| `suspend_esim` / `activate_esim` | Block / restore network access | write |
|
|
92
|
+
| `send_sms` | Text the device holding the eSIM | write |
|
|
93
|
+
| `set_webhook` | Configure webhook URL and events | write |
|
|
94
|
+
|
|
95
|
+
Prompts: `esimfly_integration_guide` (optional `stack`), `diagnose_esim` (`iccid`).
|
|
96
|
+
|
|
97
|
+
## Safety model
|
|
98
|
+
|
|
99
|
+
- Read-only unless `ESIMFLY_MCP_ALLOW_WRITES=true`.
|
|
100
|
+
- Write tools are two-step: a call without `confirm` returns a preview and makes no mutable API call;
|
|
101
|
+
`create_order` additionally requires the `idempotency_key` from its own preview, so an agent
|
|
102
|
+
cannot place the same order twice.
|
|
103
|
+
- Tool annotations mark reads as `readOnlyHint` and cancel/suspend as `destructiveHint`, so hosts
|
|
104
|
+
that ask for user approval on risky tools do so.
|
|
105
|
+
- The server never logs credentials and never writes to stdout except the MCP protocol.
|
|
106
|
+
- Give the agent a dedicated API key with the smallest rate limits you are comfortable with, and
|
|
107
|
+
rotate it from the dashboard if in doubt.
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
| Variable | Required | Description |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `ESIMFLY_ACCESS_CODE` | yes | API access code (`esf_…`) |
|
|
114
|
+
| `ESIMFLY_SECRET_KEY` | yes | API secret key (`sk_…`) |
|
|
115
|
+
| `ESIMFLY_MCP_ALLOW_WRITES` | no | `true` to register write tools |
|
|
116
|
+
| `ESIMFLY_BASE_URL` | no | Override the API base URL |
|
|
117
|
+
|
|
118
|
+
## Embedding
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { createEsimflyMcpServer } from '@esimfly/mcp';
|
|
122
|
+
const server = createEsimflyMcpServer({ config: { accessCode, secretKey }, allowWrites: false });
|
|
123
|
+
// connect it to any MCP transport
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Releasing (maintainers)
|
|
127
|
+
|
|
128
|
+
Bump `version` in `package.json`, `server.json` and `MCP_VERSION` in `src/server.ts`, add a
|
|
129
|
+
CHANGELOG entry, push, then publish a GitHub Release tagged `vX.Y.Z` — the workflow publishes to
|
|
130
|
+
npm with Trusted Publishing (OIDC).
|
|
131
|
+
|
|
132
|
+
## Support
|
|
133
|
+
|
|
134
|
+
support@esimfly.net · https://docs.esimfly.net
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Email **support@esimfly.net** with the subject "MCP security". Please do not open a public issue.
|
|
6
|
+
|
|
7
|
+
## Design
|
|
8
|
+
|
|
9
|
+
- The server runs locally (stdio) with credentials from environment variables; they are used only by
|
|
10
|
+
the bundled `@esimfly/sdk` to sign requests and are never logged or written to stdout.
|
|
11
|
+
- Read-only by default. Write tools require `ESIMFLY_MCP_ALLOW_WRITES=true` and a second call with
|
|
12
|
+
`confirm: true`; `create_order` also requires the idempotency key from its own preview.
|
|
13
|
+
- Cancel and suspend are annotated `destructiveHint` so MCP hosts can require user approval.
|
|
14
|
+
- Do not expose this server over the network. If you need remote access, put it behind your own
|
|
15
|
+
authenticated gateway and a dedicated, rate-limited API key.
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ESIMfly, ESIMflyError } from "@esimfly/sdk";
|
|
6
|
+
var MCP_VERSION = "0.1.0";
|
|
7
|
+
var FULL_PROMPT_URL = "https://docs.esimfly.net/llm/esimfly-api-full-prompt.txt";
|
|
8
|
+
var DOCS_URL = "https://docs.esimfly.net";
|
|
9
|
+
var text = (value) => ({
|
|
10
|
+
content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }]
|
|
11
|
+
});
|
|
12
|
+
var failure = (err) => {
|
|
13
|
+
if (err instanceof ESIMflyError) {
|
|
14
|
+
return {
|
|
15
|
+
isError: true,
|
|
16
|
+
content: [{
|
|
17
|
+
type: "text",
|
|
18
|
+
text: JSON.stringify({ error: err.code, message: err.message, status: err.status ?? null, details: err.response ?? null }, null, 2)
|
|
19
|
+
}]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return { isError: true, content: [{ type: "text", text: `Unexpected error: ${err?.message ?? String(err)}` }] };
|
|
23
|
+
};
|
|
24
|
+
var run = async (fn) => {
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
27
|
+
} catch (err) {
|
|
28
|
+
return failure(err);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
32
|
+
var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
|
|
33
|
+
var DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true };
|
|
34
|
+
var iccidArg = z.string().min(15).max(22).describe("ICCID of the eSIM (19-20 digits)");
|
|
35
|
+
function compactPackage(p) {
|
|
36
|
+
return {
|
|
37
|
+
package_code: p.package_code,
|
|
38
|
+
name: p.name,
|
|
39
|
+
region: p.region,
|
|
40
|
+
type: p.type,
|
|
41
|
+
data_gb: p.data_amount_gb,
|
|
42
|
+
validity_days: p.validity_days,
|
|
43
|
+
cost: p.cost,
|
|
44
|
+
currency: p.currency,
|
|
45
|
+
is_unlimited: p.is_unlimited,
|
|
46
|
+
has_voice: p.has_voice ?? false,
|
|
47
|
+
has_sms: p.has_sms ?? false,
|
|
48
|
+
countries: p.countries ? p.countries.length > 12 ? [...p.countries.slice(0, 12), `+${p.countries.length - 12} more`] : p.countries : void 0,
|
|
49
|
+
networks: p.networks?.length ?? p.locationNetworkList?.reduce((n, l) => n + (l.operatorList?.length ?? 0), 0)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createEsimflyMcpServer(options = {}) {
|
|
53
|
+
const client = options.client ?? new ESIMfly(options.config ?? {});
|
|
54
|
+
const allowWrites = options.allowWrites ?? false;
|
|
55
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
56
|
+
const server = new McpServer(
|
|
57
|
+
{ name: "esimfly", version: MCP_VERSION },
|
|
58
|
+
{
|
|
59
|
+
instructions: [
|
|
60
|
+
"Tools for the eSIMfly Business API (wholesale eSIM data plans for 200+ countries).",
|
|
61
|
+
"Package codes are opaque strings: pass them back exactly as returned. Prices (cost) are the partner buy price in the account currency.",
|
|
62
|
+
"get_esim_usage is cheap; get_esim_live_status and get_network_events query the mobile network live \u2014 use them for diagnosis, not routinely.",
|
|
63
|
+
allowWrites ? "Write tools are enabled. Every write is two-step: call without confirm to get a preview (cost, balance), then call again with confirm: true. Never confirm on the user's behalf without telling them the cost." : "This server is read-only: ordering, top-ups, cancel, suspend, SMS and webhook changes are disabled (start with ESIMFLY_MCP_ALLOW_WRITES=true to enable).",
|
|
64
|
+
`Documentation: ${DOCS_URL}`
|
|
65
|
+
].join("\n")
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
server.registerTool(
|
|
69
|
+
"search_packages",
|
|
70
|
+
{
|
|
71
|
+
title: "Search eSIM packages",
|
|
72
|
+
description: "Find eSIM data packages you can sell, with your wholesale price. Filter by destination name (search), type (local / regional / global) and page. Returns compact rows; use limit up to 100.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
search: z.string().optional().describe('Country or region name, e.g. "Turkey", "Europe"'),
|
|
75
|
+
type: z.enum(["local", "regional", "global"]).optional(),
|
|
76
|
+
page: z.number().int().min(1).optional(),
|
|
77
|
+
limit: z.number().int().min(1).max(100).optional().describe("Default 20")
|
|
78
|
+
},
|
|
79
|
+
annotations: READ
|
|
80
|
+
},
|
|
81
|
+
async ({ search, type, page, limit }) => run(async () => {
|
|
82
|
+
const res = await client.packages.list({ search, type, page, limit: limit ?? 20 });
|
|
83
|
+
return text({ pagination: res.pagination, packages: res.packages.map(compactPackage) });
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
server.registerTool(
|
|
87
|
+
"get_balance",
|
|
88
|
+
{ title: "Get account balance", description: "Current prepaid balance (or enterprise balance) and currency.", inputSchema: {}, annotations: READ },
|
|
89
|
+
async () => run(async () => text(await client.balance.get()))
|
|
90
|
+
);
|
|
91
|
+
server.registerTool(
|
|
92
|
+
"list_esims",
|
|
93
|
+
{
|
|
94
|
+
title: "List eSIMs",
|
|
95
|
+
description: "eSIMs on the account with status, data left and validity. Search by ICCID, package name or code; filter by status.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
search: z.string().optional(),
|
|
98
|
+
status: z.enum(["all", "NEW", "ACTIVE", "EXPIRED", "CANCELLED", "DEPLETED", "DELETED"]).optional(),
|
|
99
|
+
page: z.number().int().min(1).optional(),
|
|
100
|
+
limit: z.number().int().min(1).max(100).optional().describe("Default 20")
|
|
101
|
+
},
|
|
102
|
+
annotations: READ
|
|
103
|
+
},
|
|
104
|
+
async ({ search, status, page, limit }) => run(async () => {
|
|
105
|
+
const res = await client.esims.list({ search, status, page, limit: limit ?? 20 });
|
|
106
|
+
return text({
|
|
107
|
+
pagination: res.pagination,
|
|
108
|
+
esims: res.esims.map((e) => ({
|
|
109
|
+
id: e.id,
|
|
110
|
+
iccid: e.iccid,
|
|
111
|
+
package_name: e.package_name,
|
|
112
|
+
package_code: e.package_code,
|
|
113
|
+
status: e.status,
|
|
114
|
+
data: e.data,
|
|
115
|
+
validity: e.validity,
|
|
116
|
+
countries: e.countries,
|
|
117
|
+
created_at: e.created_at,
|
|
118
|
+
esim_status: e.esim_status ?? null,
|
|
119
|
+
profile_status: e.profile_status ?? null
|
|
120
|
+
}))
|
|
121
|
+
});
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
server.registerTool(
|
|
125
|
+
"get_esim_usage",
|
|
126
|
+
{
|
|
127
|
+
title: "Get eSIM usage",
|
|
128
|
+
description: "Stored data usage and validity for one eSIM (cheap; fresh as of the last sync). Identify by ICCID or order reference.",
|
|
129
|
+
inputSchema: { iccid: z.string().optional(), order_reference: z.string().optional() },
|
|
130
|
+
annotations: READ
|
|
131
|
+
},
|
|
132
|
+
async ({ iccid, order_reference }) => run(async () => {
|
|
133
|
+
if (!iccid && !order_reference) throw new ESIMflyError("Provide iccid or order_reference", { code: "MISSING_IDENTIFIER" });
|
|
134
|
+
return text(await client.esims.usage(iccid ? { iccid } : { orderId: order_reference }));
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
server.registerTool(
|
|
138
|
+
"get_esim_live_status",
|
|
139
|
+
{
|
|
140
|
+
title: "Get live eSIM status (network query)",
|
|
141
|
+
description: 'LIVE status straight from the mobile network: lifecycle status, profile install state, last network (operator, country, MCC/MNC, 4G/5G), device model and IMEI, activation and last-usage dates, data used. Expensive \u2014 use for diagnosis ("no data", "is it installed?"), not routinely.',
|
|
142
|
+
inputSchema: { iccid: iccidArg },
|
|
143
|
+
annotations: READ
|
|
144
|
+
},
|
|
145
|
+
async ({ iccid }) => run(async () => text(await client.esims.status({ iccid })))
|
|
146
|
+
);
|
|
147
|
+
server.registerTool(
|
|
148
|
+
"get_network_events",
|
|
149
|
+
{
|
|
150
|
+
title: "Get network events (last 7 days)",
|
|
151
|
+
description: 'Attach and data-session events for one eSIM, newest first, each flagged is_allowed. wrong_network_count > 0 means the device latched onto a network outside the plan \u2014 the usual cause of "connected but no data" (fix: airplane-mode toggle or manual network selection).',
|
|
152
|
+
inputSchema: { iccid: iccidArg },
|
|
153
|
+
annotations: READ
|
|
154
|
+
},
|
|
155
|
+
async ({ iccid }) => run(async () => text(await client.esims.networkEvents({ iccid })))
|
|
156
|
+
);
|
|
157
|
+
server.registerTool(
|
|
158
|
+
"get_usage_report",
|
|
159
|
+
{
|
|
160
|
+
title: "Get daily usage report",
|
|
161
|
+
description: "Daily data usage for one eSIM over the last N days (default 7, max 90) with per-country and per-operator breakdown.",
|
|
162
|
+
inputSchema: { iccid: iccidArg, days: z.number().int().min(1).max(90).optional() },
|
|
163
|
+
annotations: READ
|
|
164
|
+
},
|
|
165
|
+
async ({ iccid, days }) => run(async () => text(await client.esims.usageReport({ iccid }, days)))
|
|
166
|
+
);
|
|
167
|
+
server.registerTool(
|
|
168
|
+
"list_orders",
|
|
169
|
+
{
|
|
170
|
+
title: "List orders",
|
|
171
|
+
description: "Order history with filters (status, date range ISO 8601, search by reference / package) and a revenue summary.",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
status: z.enum(["all", "pending", "completed", "failed", "cancelled"]).optional(),
|
|
174
|
+
from_date: z.string().optional(),
|
|
175
|
+
to_date: z.string().optional(),
|
|
176
|
+
search: z.string().optional(),
|
|
177
|
+
page: z.number().int().min(1).optional(),
|
|
178
|
+
limit: z.number().int().min(1).max(100).optional().describe("Default 20")
|
|
179
|
+
},
|
|
180
|
+
annotations: READ
|
|
181
|
+
},
|
|
182
|
+
async ({ status, from_date, to_date, search, page, limit }) => run(async () => text(await client.orders.list({ status, from_date, to_date, search, page, limit: limit ?? 20 })))
|
|
183
|
+
);
|
|
184
|
+
server.registerTool(
|
|
185
|
+
"get_order",
|
|
186
|
+
{
|
|
187
|
+
title: "Get order",
|
|
188
|
+
description: "One order by reference, including its eSIM (ICCID, install links, pending state).",
|
|
189
|
+
inputSchema: { order_reference: z.string() },
|
|
190
|
+
annotations: READ
|
|
191
|
+
},
|
|
192
|
+
async ({ order_reference }) => run(async () => text(await client.orders.get(order_reference)))
|
|
193
|
+
);
|
|
194
|
+
server.registerTool(
|
|
195
|
+
"get_topup_packages",
|
|
196
|
+
{
|
|
197
|
+
title: "Get top-up packages for an eSIM",
|
|
198
|
+
description: "Top-up options for ONE eSIM (they depend on its provider and location), with your price. Returns ESIM_NOT_TOPPABLE when the eSIM state does not allow top-ups.",
|
|
199
|
+
inputSchema: { iccid: iccidArg, limit: z.number().int().min(1).max(100).optional() },
|
|
200
|
+
annotations: READ
|
|
201
|
+
},
|
|
202
|
+
async ({ iccid, limit }) => run(async () => text(await client.topups.packages({ iccid, limit: limit ?? 50 })))
|
|
203
|
+
);
|
|
204
|
+
server.registerTool(
|
|
205
|
+
"get_webhook_settings",
|
|
206
|
+
{
|
|
207
|
+
title: "Get webhook settings",
|
|
208
|
+
description: "Configured webhook URL, subscribed events, available events and the last deliveries.",
|
|
209
|
+
inputSchema: {},
|
|
210
|
+
annotations: READ
|
|
211
|
+
},
|
|
212
|
+
async () => run(async () => text(await client.webhooks.get()))
|
|
213
|
+
);
|
|
214
|
+
if (allowWrites) {
|
|
215
|
+
const confirmArg = z.boolean().optional().describe("Omit to get a preview; pass true to execute");
|
|
216
|
+
server.registerTool(
|
|
217
|
+
"create_order",
|
|
218
|
+
{
|
|
219
|
+
title: "Create eSIM order (spends balance)",
|
|
220
|
+
description: "Buy one or more eSIMs of a package from the account balance. Two-step: first call returns a preview with the current balance and a generated idempotency_key; call again with confirm: true AND that idempotency_key to execute. Tell the user the cost before confirming.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
package_code: z.string().describe("Exact package_code from search_packages"),
|
|
223
|
+
quantity: z.number().int().min(1).max(10).optional(),
|
|
224
|
+
idempotency_key: z.string().max(200).optional().describe("From the preview; required with confirm: true"),
|
|
225
|
+
confirm: confirmArg
|
|
226
|
+
},
|
|
227
|
+
annotations: WRITE
|
|
228
|
+
},
|
|
229
|
+
async ({ package_code, quantity, idempotency_key, confirm }) => run(async () => {
|
|
230
|
+
if (!confirm) {
|
|
231
|
+
const balance = await client.balance.get();
|
|
232
|
+
return text({
|
|
233
|
+
preview: true,
|
|
234
|
+
action: "create_order",
|
|
235
|
+
package_code,
|
|
236
|
+
quantity: quantity ?? 1,
|
|
237
|
+
balance_before: balance,
|
|
238
|
+
idempotency_key: `mcp-${randomUUID()}`,
|
|
239
|
+
next_step: "Call create_order again with confirm: true and this idempotency_key to place the order. The API charges the current package price."
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (!idempotency_key) {
|
|
243
|
+
throw new ESIMflyError("confirm: true requires the idempotency_key returned by the preview", { code: "MISSING_FIELDS" });
|
|
244
|
+
}
|
|
245
|
+
const order = await client.orders.create({ packageCode: package_code, quantity, idempotencyKey: idempotency_key });
|
|
246
|
+
return text({
|
|
247
|
+
orderReference: order.orderReference,
|
|
248
|
+
status: order.status,
|
|
249
|
+
duplicate: order.duplicate ?? false,
|
|
250
|
+
amount: order.amount,
|
|
251
|
+
currency: order.currency,
|
|
252
|
+
newBalance: order.newBalance,
|
|
253
|
+
esims: order.esims.map((e) => ({
|
|
254
|
+
iccid: e.iccid,
|
|
255
|
+
lpaString: e.lpaString,
|
|
256
|
+
directAppleInstallUrl: e.directAppleInstallUrl,
|
|
257
|
+
directAndroidInstallUrl: e.directAndroidInstallUrl,
|
|
258
|
+
expired_time: e.expired_time,
|
|
259
|
+
isPending: e.isPending
|
|
260
|
+
}))
|
|
261
|
+
});
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
server.registerTool(
|
|
265
|
+
"topup_esim",
|
|
266
|
+
{
|
|
267
|
+
title: "Top up an eSIM (spends balance)",
|
|
268
|
+
description: "Add a package to an existing eSIM. Two-step: without confirm returns the package name, cost and current balance; with confirm: true executes.",
|
|
269
|
+
inputSchema: { iccid: iccidArg, package_code: z.string().describe("From get_topup_packages"), confirm: confirmArg },
|
|
270
|
+
annotations: WRITE
|
|
271
|
+
},
|
|
272
|
+
async ({ iccid, package_code, confirm }) => run(async () => {
|
|
273
|
+
if (!confirm) {
|
|
274
|
+
const [pkgs, balance] = await Promise.all([client.topups.packages({ iccid, limit: 100 }), client.balance.get()]);
|
|
275
|
+
const pkg = pkgs.packages.find((p) => p.package_code === package_code) ?? null;
|
|
276
|
+
return text({
|
|
277
|
+
preview: true,
|
|
278
|
+
action: "topup_esim",
|
|
279
|
+
iccid,
|
|
280
|
+
package: pkg ? { package_code: pkg.package_code, name: pkg.name, data_gb: pkg.data_amount_gb, validity_days: pkg.validity_days, cost: pkg.cost, currency: pkg.currency } : null,
|
|
281
|
+
warning: pkg ? void 0 : "package_code is not in the top-up list for this eSIM; the call will fail",
|
|
282
|
+
balance_before: balance,
|
|
283
|
+
next_step: "Call topup_esim again with confirm: true to execute."
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return text(await client.topups.create({ iccid, packageCode: package_code }));
|
|
287
|
+
})
|
|
288
|
+
);
|
|
289
|
+
server.registerTool(
|
|
290
|
+
"cancel_esim",
|
|
291
|
+
{
|
|
292
|
+
title: "Cancel an unused eSIM (refund to balance)",
|
|
293
|
+
description: "Cancel an eSIM that has never been installed / activated and refund it to the balance. Cancels EVERY eligible eSIM in the same order. Two-step: confirm: true to execute.",
|
|
294
|
+
inputSchema: { iccid: iccidArg, confirm: confirmArg },
|
|
295
|
+
annotations: DESTRUCTIVE
|
|
296
|
+
},
|
|
297
|
+
async ({ iccid, confirm }) => run(async () => {
|
|
298
|
+
if (!confirm) {
|
|
299
|
+
const usage = await client.esims.usage({ iccid }).catch(() => null);
|
|
300
|
+
return text({
|
|
301
|
+
preview: true,
|
|
302
|
+
action: "cancel_esim",
|
|
303
|
+
iccid,
|
|
304
|
+
current: usage ? { status: usage.esim.status, order_id: usage.esim.order_id, activated_at: usage.validity.activated_at } : null,
|
|
305
|
+
warning: "All eligible eSIMs in the same order will be cancelled together. Installed or activated eSIMs are not eligible.",
|
|
306
|
+
next_step: "Call cancel_esim again with confirm: true to execute."
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return text(await client.esims.cancel({ iccid }));
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
server.registerTool(
|
|
313
|
+
"suspend_esim",
|
|
314
|
+
{
|
|
315
|
+
title: "Suspend an eSIM (block network access)",
|
|
316
|
+
description: "Block network access for an eSIMfly-network eSIM (reversible with activate_esim). Two-step: confirm: true to execute.",
|
|
317
|
+
inputSchema: { iccid: iccidArg, confirm: confirmArg },
|
|
318
|
+
annotations: DESTRUCTIVE
|
|
319
|
+
},
|
|
320
|
+
async ({ iccid, confirm }) => run(async () => {
|
|
321
|
+
if (!confirm) return text({ preview: true, action: "suspend_esim", iccid, next_step: "Call again with confirm: true to block network access." });
|
|
322
|
+
return text(await client.esims.suspend({ iccid }));
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
server.registerTool(
|
|
326
|
+
"activate_esim",
|
|
327
|
+
{
|
|
328
|
+
title: "Re-activate a suspended eSIM",
|
|
329
|
+
description: "Restore network access after suspend_esim. Two-step: confirm: true to execute.",
|
|
330
|
+
inputSchema: { iccid: iccidArg, confirm: confirmArg },
|
|
331
|
+
annotations: WRITE
|
|
332
|
+
},
|
|
333
|
+
async ({ iccid, confirm }) => run(async () => {
|
|
334
|
+
if (!confirm) return text({ preview: true, action: "activate_esim", iccid, next_step: "Call again with confirm: true to restore network access." });
|
|
335
|
+
return text(await client.esims.activate({ iccid }));
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
server.registerTool(
|
|
339
|
+
"send_sms",
|
|
340
|
+
{
|
|
341
|
+
title: "Send an SMS to an eSIM",
|
|
342
|
+
description: "Send a text (max 500 characters) to the device holding the eSIM. Two-step: confirm: true to send.",
|
|
343
|
+
inputSchema: { iccid: iccidArg, message: z.string().min(1).max(500), confirm: confirmArg },
|
|
344
|
+
annotations: WRITE
|
|
345
|
+
},
|
|
346
|
+
async ({ iccid, message, confirm }) => run(async () => {
|
|
347
|
+
if (!confirm) return text({ preview: true, action: "send_sms", iccid, message, next_step: "Call again with confirm: true to send." });
|
|
348
|
+
await client.esims.sendSms({ iccid }, message);
|
|
349
|
+
return text({ sent: true, iccid });
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
server.registerTool(
|
|
353
|
+
"set_webhook",
|
|
354
|
+
{
|
|
355
|
+
title: "Set webhook URL and events",
|
|
356
|
+
description: "Configure (or rotate) the webhook URL and subscribed events. The response contains the signing secret \u2014 shown once. Two-step: confirm: true to apply.",
|
|
357
|
+
inputSchema: {
|
|
358
|
+
webhook_url: z.string().url(),
|
|
359
|
+
events: z.array(z.enum(["esim.installed", "esim.profile.updated", "esim.status.changed", "esim.usage.threshold", "esim.provisioned", "order.completed"])).optional(),
|
|
360
|
+
confirm: confirmArg
|
|
361
|
+
},
|
|
362
|
+
annotations: WRITE
|
|
363
|
+
},
|
|
364
|
+
async ({ webhook_url, events, confirm }) => run(async () => {
|
|
365
|
+
if (!confirm) return text({ preview: true, action: "set_webhook", webhook_url, events: events ?? null, next_step: "Call again with confirm: true to apply. Store the returned secret securely." });
|
|
366
|
+
return text(await client.webhooks.set({ webhookUrl: webhook_url, events }));
|
|
367
|
+
})
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
server.registerPrompt(
|
|
371
|
+
"esimfly_integration_guide",
|
|
372
|
+
{
|
|
373
|
+
title: "eSIMfly integration guide",
|
|
374
|
+
description: "The complete, always-current prompt for building an eSIMfly integration (all endpoints, recommended architecture, webhooks, SDK). Fetched from docs.esimfly.net.",
|
|
375
|
+
argsSchema: { stack: z.string().optional().describe('Your language / framework / database, e.g. "Node.js + Prisma + Postgres"') }
|
|
376
|
+
},
|
|
377
|
+
async ({ stack }) => {
|
|
378
|
+
let guide;
|
|
379
|
+
try {
|
|
380
|
+
const res = await fetchImpl(FULL_PROMPT_URL);
|
|
381
|
+
guide = res.ok ? await res.text() : "";
|
|
382
|
+
} catch {
|
|
383
|
+
guide = "";
|
|
384
|
+
}
|
|
385
|
+
if (!guide) guide = `(Could not fetch the guide. Read it at ${FULL_PROMPT_URL})`;
|
|
386
|
+
const extra = stack ? `
|
|
387
|
+
|
|
388
|
+
My stack: ${stack}. Start with the shared client and the catalogue sync job.` : "";
|
|
389
|
+
return { messages: [{ role: "user", content: { type: "text", text: guide + extra } }] };
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
server.registerPrompt(
|
|
393
|
+
"diagnose_esim",
|
|
394
|
+
{
|
|
395
|
+
title: "Diagnose an eSIM connectivity problem",
|
|
396
|
+
description: "Walks through the live status, network events and usage of one eSIM and explains what the customer should do.",
|
|
397
|
+
argsSchema: { iccid: z.string().describe("ICCID of the eSIM") }
|
|
398
|
+
},
|
|
399
|
+
async ({ iccid }) => ({
|
|
400
|
+
messages: [{
|
|
401
|
+
role: "user",
|
|
402
|
+
content: {
|
|
403
|
+
type: "text",
|
|
404
|
+
text: [
|
|
405
|
+
`Diagnose eSIM ${iccid}.`,
|
|
406
|
+
"1. Call get_esim_usage to see status, data left and validity.",
|
|
407
|
+
"2. Call get_esim_live_status: is the profile installed (profile = Enabled)? which device? which network did it last attach to?",
|
|
408
|
+
"3. Call get_network_events: any is_allowed = false events (wrong network)? recent data sessions?",
|
|
409
|
+
"Then explain in plain language what is wrong (not installed / wrong network / depleted / expired / never connected) and the exact steps the customer should take (install, enable data roaming, airplane-mode toggle, manual network selection, top-up)."
|
|
410
|
+
].join("\n")
|
|
411
|
+
}
|
|
412
|
+
}]
|
|
413
|
+
})
|
|
414
|
+
);
|
|
415
|
+
return server;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export {
|
|
419
|
+
MCP_VERSION,
|
|
420
|
+
createEsimflyMcpServer
|
|
421
|
+
};
|
|
422
|
+
//# sourceMappingURL=chunk-Q3UI3CYF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * eSIMfly MCP server — exposes the eSIMfly Business API to AI agents as tools.\n *\n * Read tools are always available. Write tools (orders, top-ups, cancel,\n * suspend, SMS, webhook config) exist only when `allowWrites` is on, and each\n * one is two-step: a call without `confirm: true` returns a preview (what it\n * will do, what it costs, your balance) and never touches the API mutably.\n */\nimport { randomUUID } from 'node:crypto';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport { ESIMfly, ESIMflyError, type ESIMflyConfig, type Package } from '@esimfly/sdk';\n\nexport const MCP_VERSION = '0.1.0';\nconst FULL_PROMPT_URL = 'https://docs.esimfly.net/llm/esimfly-api-full-prompt.txt';\nconst DOCS_URL = 'https://docs.esimfly.net';\n\nexport interface EsimflyMcpOptions {\n /** Pre-built client (tests, embedding). Takes precedence over `config`. */\n client?: ESIMfly;\n /** Credentials used to build the client when `client` is not given. */\n config?: ESIMflyConfig;\n /** Register the money-moving / state-changing tools. Default false. */\n allowWrites?: boolean;\n /** Used to fetch the integration prompt for the `esimfly_integration_guide` prompt. */\n fetch?: typeof fetch;\n}\n\ntype ToolResult = CallToolResult;\n\nconst text = (value: unknown): ToolResult => ({\n content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],\n});\n\nconst failure = (err: unknown): ToolResult => {\n if (err instanceof ESIMflyError) {\n return {\n isError: true,\n content: [{\n type: 'text',\n text: JSON.stringify({ error: err.code, message: err.message, status: err.status ?? null, details: err.response ?? null }, null, 2),\n }],\n };\n }\n return { isError: true, content: [{ type: 'text', text: `Unexpected error: ${(err as Error)?.message ?? String(err)}` }] };\n};\n\nconst run = async (fn: () => Promise<ToolResult>): Promise<ToolResult> => {\n try {\n return await fn();\n } catch (err) {\n return failure(err);\n }\n};\n\nconst READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true } as const;\nconst WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true } as const;\nconst DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true } as const;\n\nconst iccidArg = z.string().min(15).max(22).describe('ICCID of the eSIM (19-20 digits)');\n\nfunction compactPackage(p: Package) {\n return {\n package_code: p.package_code,\n name: p.name,\n region: p.region,\n type: p.type,\n data_gb: p.data_amount_gb,\n validity_days: p.validity_days,\n cost: p.cost,\n currency: p.currency,\n is_unlimited: p.is_unlimited,\n has_voice: p.has_voice ?? false,\n has_sms: p.has_sms ?? false,\n countries: p.countries ? (p.countries.length > 12 ? [...p.countries.slice(0, 12), `+${p.countries.length - 12} more`] : p.countries) : undefined,\n networks: p.networks?.length ?? p.locationNetworkList?.reduce((n, l) => n + (l.operatorList?.length ?? 0), 0),\n };\n}\n\nexport function createEsimflyMcpServer(options: EsimflyMcpOptions = {}): McpServer {\n const client = options.client ?? new ESIMfly(options.config ?? ({} as ESIMflyConfig));\n const allowWrites = options.allowWrites ?? false;\n const fetchImpl = options.fetch ?? globalThis.fetch;\n\n const server = new McpServer(\n { name: 'esimfly', version: MCP_VERSION },\n {\n instructions: [\n 'Tools for the eSIMfly Business API (wholesale eSIM data plans for 200+ countries).',\n 'Package codes are opaque strings: pass them back exactly as returned. Prices (cost) are the partner buy price in the account currency.',\n 'get_esim_usage is cheap; get_esim_live_status and get_network_events query the mobile network live — use them for diagnosis, not routinely.',\n allowWrites\n ? 'Write tools are enabled. Every write is two-step: call without confirm to get a preview (cost, balance), then call again with confirm: true. Never confirm on the user\\'s behalf without telling them the cost.'\n : 'This server is read-only: ordering, top-ups, cancel, suspend, SMS and webhook changes are disabled (start with ESIMFLY_MCP_ALLOW_WRITES=true to enable).',\n `Documentation: ${DOCS_URL}`,\n ].join('\\n'),\n },\n );\n\n // ------------------------------------------------------------------ read tools\n server.registerTool(\n 'search_packages',\n {\n title: 'Search eSIM packages',\n description:\n 'Find eSIM data packages you can sell, with your wholesale price. Filter by destination name (search), type (local / regional / global) and page. Returns compact rows; use limit up to 100.',\n inputSchema: {\n search: z.string().optional().describe('Country or region name, e.g. \"Turkey\", \"Europe\"'),\n type: z.enum(['local', 'regional', 'global']).optional(),\n page: z.number().int().min(1).optional(),\n limit: z.number().int().min(1).max(100).optional().describe('Default 20'),\n },\n annotations: READ,\n },\n async ({ search, type, page, limit }) =>\n run(async () => {\n const res = await client.packages.list({ search, type, page, limit: limit ?? 20 });\n return text({ pagination: res.pagination, packages: res.packages.map(compactPackage) });\n }),\n );\n\n server.registerTool(\n 'get_balance',\n { title: 'Get account balance', description: 'Current prepaid balance (or enterprise balance) and currency.', inputSchema: {}, annotations: READ },\n async () => run(async () => text(await client.balance.get())),\n );\n\n server.registerTool(\n 'list_esims',\n {\n title: 'List eSIMs',\n description: 'eSIMs on the account with status, data left and validity. Search by ICCID, package name or code; filter by status.',\n inputSchema: {\n search: z.string().optional(),\n status: z.enum(['all', 'NEW', 'ACTIVE', 'EXPIRED', 'CANCELLED', 'DEPLETED', 'DELETED']).optional(),\n page: z.number().int().min(1).optional(),\n limit: z.number().int().min(1).max(100).optional().describe('Default 20'),\n },\n annotations: READ,\n },\n async ({ search, status, page, limit }) =>\n run(async () => {\n const res = await client.esims.list({ search, status, page, limit: limit ?? 20 });\n return text({\n pagination: res.pagination,\n esims: res.esims.map((e) => ({\n id: e.id,\n iccid: e.iccid,\n package_name: e.package_name,\n package_code: e.package_code,\n status: e.status,\n data: e.data,\n validity: e.validity,\n countries: e.countries,\n created_at: e.created_at,\n esim_status: e.esim_status ?? null,\n profile_status: e.profile_status ?? null,\n })),\n });\n }),\n );\n\n server.registerTool(\n 'get_esim_usage',\n {\n title: 'Get eSIM usage',\n description: 'Stored data usage and validity for one eSIM (cheap; fresh as of the last sync). Identify by ICCID or order reference.',\n inputSchema: { iccid: z.string().optional(), order_reference: z.string().optional() },\n annotations: READ,\n },\n async ({ iccid, order_reference }) =>\n run(async () => {\n if (!iccid && !order_reference) throw new ESIMflyError('Provide iccid or order_reference', { code: 'MISSING_IDENTIFIER' });\n return text(await client.esims.usage(iccid ? { iccid } : { orderId: order_reference! }));\n }),\n );\n\n server.registerTool(\n 'get_esim_live_status',\n {\n title: 'Get live eSIM status (network query)',\n description:\n 'LIVE status straight from the mobile network: lifecycle status, profile install state, last network (operator, country, MCC/MNC, 4G/5G), device model and IMEI, activation and last-usage dates, data used. Expensive — use for diagnosis (\"no data\", \"is it installed?\"), not routinely.',\n inputSchema: { iccid: iccidArg },\n annotations: READ,\n },\n async ({ iccid }) => run(async () => text(await client.esims.status({ iccid }))),\n );\n\n server.registerTool(\n 'get_network_events',\n {\n title: 'Get network events (last 7 days)',\n description:\n 'Attach and data-session events for one eSIM, newest first, each flagged is_allowed. wrong_network_count > 0 means the device latched onto a network outside the plan — the usual cause of \"connected but no data\" (fix: airplane-mode toggle or manual network selection).',\n inputSchema: { iccid: iccidArg },\n annotations: READ,\n },\n async ({ iccid }) => run(async () => text(await client.esims.networkEvents({ iccid }))),\n );\n\n server.registerTool(\n 'get_usage_report',\n {\n title: 'Get daily usage report',\n description: 'Daily data usage for one eSIM over the last N days (default 7, max 90) with per-country and per-operator breakdown.',\n inputSchema: { iccid: iccidArg, days: z.number().int().min(1).max(90).optional() },\n annotations: READ,\n },\n async ({ iccid, days }) => run(async () => text(await client.esims.usageReport({ iccid }, days))),\n );\n\n server.registerTool(\n 'list_orders',\n {\n title: 'List orders',\n description: 'Order history with filters (status, date range ISO 8601, search by reference / package) and a revenue summary.',\n inputSchema: {\n status: z.enum(['all', 'pending', 'completed', 'failed', 'cancelled']).optional(),\n from_date: z.string().optional(),\n to_date: z.string().optional(),\n search: z.string().optional(),\n page: z.number().int().min(1).optional(),\n limit: z.number().int().min(1).max(100).optional().describe('Default 20'),\n },\n annotations: READ,\n },\n async ({ status, from_date, to_date, search, page, limit }) =>\n run(async () => text(await client.orders.list({ status, from_date, to_date, search, page, limit: limit ?? 20 }))),\n );\n\n server.registerTool(\n 'get_order',\n {\n title: 'Get order',\n description: 'One order by reference, including its eSIM (ICCID, install links, pending state).',\n inputSchema: { order_reference: z.string() },\n annotations: READ,\n },\n async ({ order_reference }) => run(async () => text(await client.orders.get(order_reference))),\n );\n\n server.registerTool(\n 'get_topup_packages',\n {\n title: 'Get top-up packages for an eSIM',\n description: 'Top-up options for ONE eSIM (they depend on its provider and location), with your price. Returns ESIM_NOT_TOPPABLE when the eSIM state does not allow top-ups.',\n inputSchema: { iccid: iccidArg, limit: z.number().int().min(1).max(100).optional() },\n annotations: READ,\n },\n async ({ iccid, limit }) => run(async () => text(await client.topups.packages({ iccid, limit: limit ?? 50 }))),\n );\n\n server.registerTool(\n 'get_webhook_settings',\n {\n title: 'Get webhook settings',\n description: 'Configured webhook URL, subscribed events, available events and the last deliveries.',\n inputSchema: {},\n annotations: READ,\n },\n async () => run(async () => text(await client.webhooks.get())),\n );\n\n // ------------------------------------------------------------------ write tools (opt-in, two-step)\n if (allowWrites) {\n const confirmArg = z.boolean().optional().describe('Omit to get a preview; pass true to execute');\n\n server.registerTool(\n 'create_order',\n {\n title: 'Create eSIM order (spends balance)',\n description:\n 'Buy one or more eSIMs of a package from the account balance. Two-step: first call returns a preview with the current balance and a generated idempotency_key; call again with confirm: true AND that idempotency_key to execute. Tell the user the cost before confirming.',\n inputSchema: {\n package_code: z.string().describe('Exact package_code from search_packages'),\n quantity: z.number().int().min(1).max(10).optional(),\n idempotency_key: z.string().max(200).optional().describe('From the preview; required with confirm: true'),\n confirm: confirmArg,\n },\n annotations: WRITE,\n },\n async ({ package_code, quantity, idempotency_key, confirm }) =>\n run(async () => {\n if (!confirm) {\n const balance = await client.balance.get();\n return text({\n preview: true,\n action: 'create_order',\n package_code,\n quantity: quantity ?? 1,\n balance_before: balance,\n idempotency_key: `mcp-${randomUUID()}`,\n next_step: 'Call create_order again with confirm: true and this idempotency_key to place the order. The API charges the current package price.',\n });\n }\n if (!idempotency_key) {\n throw new ESIMflyError('confirm: true requires the idempotency_key returned by the preview', { code: 'MISSING_FIELDS' });\n }\n const order = await client.orders.create({ packageCode: package_code, quantity, idempotencyKey: idempotency_key });\n return text({\n orderReference: order.orderReference,\n status: order.status,\n duplicate: order.duplicate ?? false,\n amount: order.amount,\n currency: order.currency,\n newBalance: order.newBalance,\n esims: order.esims.map((e) => ({\n iccid: e.iccid,\n lpaString: e.lpaString,\n directAppleInstallUrl: e.directAppleInstallUrl,\n directAndroidInstallUrl: e.directAndroidInstallUrl,\n expired_time: e.expired_time,\n isPending: e.isPending,\n })),\n });\n }),\n );\n\n server.registerTool(\n 'topup_esim',\n {\n title: 'Top up an eSIM (spends balance)',\n description: 'Add a package to an existing eSIM. Two-step: without confirm returns the package name, cost and current balance; with confirm: true executes.',\n inputSchema: { iccid: iccidArg, package_code: z.string().describe('From get_topup_packages'), confirm: confirmArg },\n annotations: WRITE,\n },\n async ({ iccid, package_code, confirm }) =>\n run(async () => {\n if (!confirm) {\n const [pkgs, balance] = await Promise.all([client.topups.packages({ iccid, limit: 100 }), client.balance.get()]);\n const pkg = pkgs.packages.find((p) => p.package_code === package_code) ?? null;\n return text({\n preview: true,\n action: 'topup_esim',\n iccid,\n package: pkg ? { package_code: pkg.package_code, name: pkg.name, data_gb: pkg.data_amount_gb, validity_days: pkg.validity_days, cost: pkg.cost, currency: pkg.currency } : null,\n warning: pkg ? undefined : 'package_code is not in the top-up list for this eSIM; the call will fail',\n balance_before: balance,\n next_step: 'Call topup_esim again with confirm: true to execute.',\n });\n }\n return text(await client.topups.create({ iccid, packageCode: package_code }));\n }),\n );\n\n server.registerTool(\n 'cancel_esim',\n {\n title: 'Cancel an unused eSIM (refund to balance)',\n description: 'Cancel an eSIM that has never been installed / activated and refund it to the balance. Cancels EVERY eligible eSIM in the same order. Two-step: confirm: true to execute.',\n inputSchema: { iccid: iccidArg, confirm: confirmArg },\n annotations: DESTRUCTIVE,\n },\n async ({ iccid, confirm }) =>\n run(async () => {\n if (!confirm) {\n const usage = await client.esims.usage({ iccid }).catch(() => null);\n return text({\n preview: true,\n action: 'cancel_esim',\n iccid,\n current: usage ? { status: usage.esim.status, order_id: usage.esim.order_id, activated_at: usage.validity.activated_at } : null,\n warning: 'All eligible eSIMs in the same order will be cancelled together. Installed or activated eSIMs are not eligible.',\n next_step: 'Call cancel_esim again with confirm: true to execute.',\n });\n }\n return text(await client.esims.cancel({ iccid }));\n }),\n );\n\n server.registerTool(\n 'suspend_esim',\n {\n title: 'Suspend an eSIM (block network access)',\n description: 'Block network access for an eSIMfly-network eSIM (reversible with activate_esim). Two-step: confirm: true to execute.',\n inputSchema: { iccid: iccidArg, confirm: confirmArg },\n annotations: DESTRUCTIVE,\n },\n async ({ iccid, confirm }) =>\n run(async () => {\n if (!confirm) return text({ preview: true, action: 'suspend_esim', iccid, next_step: 'Call again with confirm: true to block network access.' });\n return text(await client.esims.suspend({ iccid }));\n }),\n );\n\n server.registerTool(\n 'activate_esim',\n {\n title: 'Re-activate a suspended eSIM',\n description: 'Restore network access after suspend_esim. Two-step: confirm: true to execute.',\n inputSchema: { iccid: iccidArg, confirm: confirmArg },\n annotations: WRITE,\n },\n async ({ iccid, confirm }) =>\n run(async () => {\n if (!confirm) return text({ preview: true, action: 'activate_esim', iccid, next_step: 'Call again with confirm: true to restore network access.' });\n return text(await client.esims.activate({ iccid }));\n }),\n );\n\n server.registerTool(\n 'send_sms',\n {\n title: 'Send an SMS to an eSIM',\n description: 'Send a text (max 500 characters) to the device holding the eSIM. Two-step: confirm: true to send.',\n inputSchema: { iccid: iccidArg, message: z.string().min(1).max(500), confirm: confirmArg },\n annotations: WRITE,\n },\n async ({ iccid, message, confirm }) =>\n run(async () => {\n if (!confirm) return text({ preview: true, action: 'send_sms', iccid, message, next_step: 'Call again with confirm: true to send.' });\n await client.esims.sendSms({ iccid }, message);\n return text({ sent: true, iccid });\n }),\n );\n\n server.registerTool(\n 'set_webhook',\n {\n title: 'Set webhook URL and events',\n description: 'Configure (or rotate) the webhook URL and subscribed events. The response contains the signing secret — shown once. Two-step: confirm: true to apply.',\n inputSchema: {\n webhook_url: z.string().url(),\n events: z.array(z.enum(['esim.installed', 'esim.profile.updated', 'esim.status.changed', 'esim.usage.threshold', 'esim.provisioned', 'order.completed'])).optional(),\n confirm: confirmArg,\n },\n annotations: WRITE,\n },\n async ({ webhook_url, events, confirm }) =>\n run(async () => {\n if (!confirm) return text({ preview: true, action: 'set_webhook', webhook_url, events: events ?? null, next_step: 'Call again with confirm: true to apply. Store the returned secret securely.' });\n return text(await client.webhooks.set({ webhookUrl: webhook_url, events }));\n }),\n );\n }\n\n // ------------------------------------------------------------------ prompts\n server.registerPrompt(\n 'esimfly_integration_guide',\n {\n title: 'eSIMfly integration guide',\n description: 'The complete, always-current prompt for building an eSIMfly integration (all endpoints, recommended architecture, webhooks, SDK). Fetched from docs.esimfly.net.',\n argsSchema: { stack: z.string().optional().describe('Your language / framework / database, e.g. \"Node.js + Prisma + Postgres\"') },\n },\n async ({ stack }) => {\n let guide: string;\n try {\n const res = await fetchImpl(FULL_PROMPT_URL);\n guide = res.ok ? await res.text() : '';\n } catch {\n guide = '';\n }\n if (!guide) guide = `(Could not fetch the guide. Read it at ${FULL_PROMPT_URL})`;\n const extra = stack ? `\\n\\nMy stack: ${stack}. Start with the shared client and the catalogue sync job.` : '';\n return { messages: [{ role: 'user', content: { type: 'text', text: guide + extra } }] };\n },\n );\n\n server.registerPrompt(\n 'diagnose_esim',\n {\n title: 'Diagnose an eSIM connectivity problem',\n description: 'Walks through the live status, network events and usage of one eSIM and explains what the customer should do.',\n argsSchema: { iccid: z.string().describe('ICCID of the eSIM') },\n },\n async ({ iccid }) => ({\n messages: [{\n role: 'user',\n content: {\n type: 'text',\n text: [\n `Diagnose eSIM ${iccid}.`,\n '1. Call get_esim_usage to see status, data left and validity.',\n '2. Call get_esim_live_status: is the profile installed (profile = Enabled)? which device? which network did it last attach to?',\n '3. Call get_network_events: any is_allowed = false events (wrong network)? recent data sessions?',\n 'Then explain in plain language what is wrong (not installed / wrong network / depleted / expired / never connected) and the exact steps the customer should take (install, enable data roaming, airplane-mode toggle, manual network selection, top-up).',\n ].join('\\n'),\n },\n }],\n }),\n );\n\n return server;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MCP_VERSION,
|
|
4
|
+
createEsimflyMcpServer
|
|
5
|
+
} from "./chunk-Q3UI3CYF.js";
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
var accessCode = process.env.ESIMFLY_ACCESS_CODE;
|
|
10
|
+
var secretKey = process.env.ESIMFLY_SECRET_KEY;
|
|
11
|
+
if (!accessCode || !secretKey) {
|
|
12
|
+
console.error(
|
|
13
|
+
"[esimfly-mcp] Missing credentials. Set ESIMFLY_ACCESS_CODE and ESIMFLY_SECRET_KEY (Business Dashboard \u2192 Settings \u2192 API Keys). See https://docs.esimfly.net"
|
|
14
|
+
);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
var allowWrites = /^(1|true|yes)$/i.test(process.env.ESIMFLY_MCP_ALLOW_WRITES ?? "");
|
|
18
|
+
var server = createEsimflyMcpServer({
|
|
19
|
+
config: {
|
|
20
|
+
accessCode,
|
|
21
|
+
secretKey,
|
|
22
|
+
baseUrl: process.env.ESIMFLY_BASE_URL,
|
|
23
|
+
userAgent: `esimfly-mcp/${MCP_VERSION}`
|
|
24
|
+
},
|
|
25
|
+
allowWrites
|
|
26
|
+
});
|
|
27
|
+
var transport = new StdioServerTransport();
|
|
28
|
+
await server.connect(transport);
|
|
29
|
+
console.error(`[esimfly-mcp] v${MCP_VERSION} ready (${allowWrites ? "read + write tools" : "read-only"})`);
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * CLI entry: `npx -y @esimfly/mcp` (stdio transport).\n *\n * Env: ESIMFLY_ACCESS_CODE, ESIMFLY_SECRET_KEY (required),\n * ESIMFLY_MCP_ALLOW_WRITES=true (optional), ESIMFLY_BASE_URL (optional).\n * Never write to stdout here — it is the MCP protocol channel.\n */\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createEsimflyMcpServer, MCP_VERSION } from './server.js';\n\nconst accessCode = process.env.ESIMFLY_ACCESS_CODE;\nconst secretKey = process.env.ESIMFLY_SECRET_KEY;\n\nif (!accessCode || !secretKey) {\n console.error(\n '[esimfly-mcp] Missing credentials. Set ESIMFLY_ACCESS_CODE and ESIMFLY_SECRET_KEY ' +\n '(Business Dashboard → Settings → API Keys). See https://docs.esimfly.net',\n );\n process.exit(1);\n}\n\nconst allowWrites = /^(1|true|yes)$/i.test(process.env.ESIMFLY_MCP_ALLOW_WRITES ?? '');\n\nconst server = createEsimflyMcpServer({\n config: {\n accessCode,\n secretKey,\n baseUrl: process.env.ESIMFLY_BASE_URL,\n userAgent: `esimfly-mcp/${MCP_VERSION}`,\n },\n allowWrites,\n});\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\nconsole.error(`[esimfly-mcp] v${MCP_VERSION} ready (${allowWrites ? 'read + write tools' : 'read-only'})`);\n"],"mappings":";;;;;;AAOA,SAAS,4BAA4B;AAGrC,IAAM,aAAa,QAAQ,IAAI;AAC/B,IAAM,YAAY,QAAQ,IAAI;AAE9B,IAAI,CAAC,cAAc,CAAC,WAAW;AAC7B,UAAQ;AAAA,IACN;AAAA,EAEF;AACA,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,cAAc,kBAAkB,KAAK,QAAQ,IAAI,4BAA4B,EAAE;AAErF,IAAM,SAAS,uBAAuB;AAAA,EACpC,QAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA,SAAS,QAAQ,IAAI;AAAA,IACrB,WAAW,eAAe,WAAW;AAAA,EACvC;AAAA,EACA;AACF,CAAC;AAED,IAAM,YAAY,IAAI,qBAAqB;AAC3C,MAAM,OAAO,QAAQ,SAAS;AAC9B,QAAQ,MAAM,kBAAkB,WAAW,WAAW,cAAc,uBAAuB,WAAW,GAAG;","names":[]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { ESIMfly, ESIMflyConfig } from '@esimfly/sdk';
|
|
3
|
+
|
|
4
|
+
declare const MCP_VERSION = "0.1.0";
|
|
5
|
+
interface EsimflyMcpOptions {
|
|
6
|
+
/** Pre-built client (tests, embedding). Takes precedence over `config`. */
|
|
7
|
+
client?: ESIMfly;
|
|
8
|
+
/** Credentials used to build the client when `client` is not given. */
|
|
9
|
+
config?: ESIMflyConfig;
|
|
10
|
+
/** Register the money-moving / state-changing tools. Default false. */
|
|
11
|
+
allowWrites?: boolean;
|
|
12
|
+
/** Used to fetch the integration prompt for the `esimfly_integration_guide` prompt. */
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
}
|
|
15
|
+
declare function createEsimflyMcpServer(options?: EsimflyMcpOptions): McpServer;
|
|
16
|
+
|
|
17
|
+
export { type EsimflyMcpOptions, MCP_VERSION, createEsimflyMcpServer };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@esimfly/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for the eSIMfly Business API — let Claude, ChatGPT, Cursor and other AI agents search eSIM plans, check usage, diagnose connectivity and (optionally) place orders",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"esim",
|
|
9
|
+
"esimfly",
|
|
10
|
+
"ai-agent",
|
|
11
|
+
"claude",
|
|
12
|
+
"cursor",
|
|
13
|
+
"travel",
|
|
14
|
+
"connectivity"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://docs.esimfly.net",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/eSimfly-Official/esimfly-mcp.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"email": "support@esimfly.net"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "eSIMfly <support@esimfly.net>",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"esimfly-mcp": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/server.js",
|
|
31
|
+
"types": "./dist/server.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/server.d.ts",
|
|
35
|
+
"import": "./dist/server.js"
|
|
36
|
+
},
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"CHANGELOG.md",
|
|
44
|
+
"SECURITY.md",
|
|
45
|
+
"server.json"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"start": "node dist/index.js",
|
|
58
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@esimfly/sdk": "^0.1.0",
|
|
62
|
+
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
63
|
+
"zod": "^3.25.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^20.19.43",
|
|
67
|
+
"tsup": "^8.5.1",
|
|
68
|
+
"typescript": "^5.9.3",
|
|
69
|
+
"vitest": "^4.1.11"
|
|
70
|
+
},
|
|
71
|
+
"overrides": {
|
|
72
|
+
"esbuild": ">=0.28.1"
|
|
73
|
+
},
|
|
74
|
+
"mcpName": "io.github.esimfly-official/esimfly-mcp"
|
|
75
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
|
|
3
|
+
"name": "io.github.esimfly-official/esimfly-mcp",
|
|
4
|
+
"description": "eSIMfly Business API: search eSIM plans with wholesale prices, check usage, diagnose connectivity, and optionally order and top up eSIMs (two-step confirmation).",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"websiteUrl": "https://docs.esimfly.net",
|
|
7
|
+
"repository": { "url": "https://github.com/eSimfly-Official/esimfly-mcp", "source": "github" },
|
|
8
|
+
"packages": [
|
|
9
|
+
{
|
|
10
|
+
"registryType": "npm",
|
|
11
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
12
|
+
"identifier": "@esimfly/mcp",
|
|
13
|
+
"version": "0.1.0",
|
|
14
|
+
"transport": { "type": "stdio" },
|
|
15
|
+
"runtimeHint": "npx",
|
|
16
|
+
"environmentVariables": [
|
|
17
|
+
{ "name": "ESIMFLY_ACCESS_CODE", "description": "eSIMfly API access code (esf_...)", "isRequired": true, "isSecret": true },
|
|
18
|
+
{ "name": "ESIMFLY_SECRET_KEY", "description": "eSIMfly API secret key (sk_...)", "isRequired": true, "isSecret": true },
|
|
19
|
+
{ "name": "ESIMFLY_MCP_ALLOW_WRITES", "description": "Set to true to enable ordering, top-up, cancel, suspend, SMS and webhook tools", "isRequired": false, "isSecret": false }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|