@carrierllc/mcp 0.13.14 → 0.13.16
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/dist/{chunk-DIHA6TVT.js → chunk-D6VGTHND.js} +2 -2
- package/dist/{chunk-DIHA6TVT.js.map → chunk-D6VGTHND.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/plugin/carrier/skills/issue-esim/SKILL.md +73 -8
- package/plugin/carrier/skills/issue-esim/references/glossary.md +14 -0
- package/plugin/carrier/skills/publish-storefront/SKILL.md +13 -5
- package/templates/storefront/src/vendor/carrier/client.ts +14 -2
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carrierllc/mcp",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.16",
|
|
4
4
|
"description": "Carrier MCP \u2014 natural-language control of MVNO/eSIM fleets via eSIMVault OCS. Stdio mode for direct integration with Claude Desktop, Cursor, Windsurf, and MCP-compatible clients. Ships the `carrier` CLI (plugin install + white-label eSIM storefront scaffold).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Carrier (Lifecycle Innovations Limited)",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: issue-esim
|
|
3
|
-
description: Issue one Carrier eSIM with POST /v1/connectivity/intents. Use when
|
|
3
|
+
description: Issue one Carrier eSIM with POST /v1/connectivity/intents. Use when a developer with no eSIMVault OCS reseller account wants global connectivity, a test eSIM, or test_global_1gb. Do not use for an existing OCS reseller, and do not use the operator OCS catalog for this.
|
|
4
4
|
argument-hint: "[test_global_1gb|test_eu_1gb|live plan slug] [idempotency key]"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ argument-hint: "[test_global_1gb|test_eu_1gb|live plan slug] [idempotency key]"
|
|
|
8
8
|
|
|
9
9
|
This is the developer path. It is one object, `connectivity.intent`, not the OCS method list.
|
|
10
10
|
|
|
11
|
-
The description above is the trigger. Load this skill when the task is connectivity. Do not load the operator MCP catalog first: clients inject every tool on that server before a skill can hide them.
|
|
11
|
+
The description above is the trigger. Load this skill when the task is connectivity for a developer who has no OCS reseller account. An existing OCS reseller already has an eSIMVault account and its own inventory tooling; do not steer that caller here. Do not load the operator MCP catalog first: clients inject every tool on that server before a skill can hide them.
|
|
12
12
|
|
|
13
13
|
## Install, if this skill is not already available
|
|
14
14
|
|
|
@@ -24,16 +24,59 @@ The same note, as a direct link:
|
|
|
24
24
|
npx skills add https://carrier.llc/.well-known/agent-skills/issue-esim -g -a cursor -a claude-code -y
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
`skills` 1.7.0 requires Node 22.20 or newer (`styleText` in a Node 20 runtime throws a SyntaxError before this even installs). On Node 20, run either command through `npx -p node@22` instead:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx -y -p node@22 -p skills -- skills add https://carrier.llc -g -a cursor -a claude-code -y
|
|
31
|
+
npx -y -p node@22 -p skills -- skills add https://carrier.llc/.well-known/agent-skills/issue-esim -g -a cursor -a claude-code -y
|
|
32
|
+
```
|
|
33
|
+
|
|
27
34
|
## Issue
|
|
28
35
|
|
|
29
36
|
`POST https://api.carrier.llc/v1/connectivity/intents`
|
|
30
37
|
|
|
31
38
|
Required header: `Idempotency-Key` (stable per order, max 255 characters).
|
|
32
39
|
|
|
33
|
-
Auth: `Authorization: Bearer` with an `ak_` key from the console. Do not put the key in the repo.
|
|
40
|
+
Auth: `Authorization: Bearer $CARRIER_API_KEY` with an `ak_` key. Mint one with `POST /v1/api-keys` (needs the write scope, so do this once from the console after upgrading — see Auth errors below), or from the console under Settings → API Keys. Do not put the key in the repo.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
curl -sS https://api.carrier.llc/v1/connectivity/intents \
|
|
44
|
+
-X POST \
|
|
45
|
+
-H "Authorization: Bearer $CARRIER_API_KEY" \
|
|
46
|
+
-H "Idempotency-Key: order_123" \
|
|
47
|
+
-H "Content-Type: application/json" \
|
|
48
|
+
-d '{ "plan": "test_global_1gb" }'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Response, `201 Created` on the first call (`200` on a replay of the same key and body):
|
|
34
52
|
|
|
35
53
|
```json
|
|
36
|
-
{
|
|
54
|
+
{
|
|
55
|
+
"id": "cni_5f9b1c2d3e4a4b6c8d7e9f0a1b2c3d4e",
|
|
56
|
+
"object": "connectivity.intent",
|
|
57
|
+
"status": "succeeded",
|
|
58
|
+
"livemode": false,
|
|
59
|
+
"plan": {
|
|
60
|
+
"id": "test_global_1gb",
|
|
61
|
+
"object": "connectivity.plan",
|
|
62
|
+
"name": "Test Global 1 GB",
|
|
63
|
+
"data_mb": 1024,
|
|
64
|
+
"validity_days": 7,
|
|
65
|
+
"countries": ["US", "GB", "DE", "FR", "JP", "SG", "BR", "AU"],
|
|
66
|
+
"livemode": false,
|
|
67
|
+
"price": null
|
|
68
|
+
},
|
|
69
|
+
"esim": {
|
|
70
|
+
"iccid": "8900000000000000001",
|
|
71
|
+
"activation_code": "LPA:1$test.smdp.carrier.llc$TEST",
|
|
72
|
+
"qr_code": null,
|
|
73
|
+
"smdp_address": "test.smdp.carrier.llc",
|
|
74
|
+
"matching_id": "TEST"
|
|
75
|
+
},
|
|
76
|
+
"metadata": {},
|
|
77
|
+
"error": null,
|
|
78
|
+
"created": 1798761600
|
|
79
|
+
}
|
|
37
80
|
```
|
|
38
81
|
|
|
39
82
|
Plans:
|
|
@@ -41,13 +84,28 @@ Plans:
|
|
|
41
84
|
- `test_global_1gb` and `test_eu_1gb` succeed immediately, set `livemode` false, and never call the network. They work on the free tier. The ICCID `8900000000000000001` and SM-DP+ `test.smdp.carrier.llc` are fixtures.
|
|
42
85
|
- Any other slug is a live catalogue plan. It draws one eSIM from the caller's own account and needs the write scope. Do not fall back to a platform stock account.
|
|
43
86
|
|
|
44
|
-
The same key and body return the original intent.
|
|
87
|
+
The same key and body return the original intent (`200`, same `id`). The same key with a different `plan` or `metadata` is refused with `409 idempotency_mismatch` and does not touch the first intent — retry with a new key instead of reusing one across two different orders:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "error": { "type": "invalid_request_error", "code": "idempotency_mismatch", "message": "This Idempotency-Key was already used with a different request." } }
|
|
91
|
+
```
|
|
45
92
|
|
|
46
93
|
Confirm a plan later with `POST /v1/connectivity/intents/:id/confirm` when the first call omitted `plan`.
|
|
47
94
|
|
|
95
|
+
### Auth errors
|
|
96
|
+
|
|
97
|
+
| Cause | Status | Body |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| No `Authorization` header, or not `Bearer ...` | 401 | `{ "error": "Missing or invalid Authorization header" }` |
|
|
100
|
+
| Bearer token invalid or expired | 401 | `{ "error": "Unauthorized" }` |
|
|
101
|
+
| Live plan (anything but `test_global_1gb`/`test_eu_1gb`) on the free tier | 402 | `{ "error": { "type": "invalid_request_error", "code": "write_required", "message": "Live plans require the write scope. Test plans such as test_global_1gb work on the free tier." } }` |
|
|
102
|
+
| Monthly quota exceeded (free: 1,000 calls/mo, pro: 50,000) | 429 | `{ "error": "Monthly quota exceeded", "limit": 1000, "used": 1000, "reset_at": "...", "upgrade_url": "https://mcp.carrier.llc/upgrade" }` |
|
|
103
|
+
|
|
104
|
+
The write scope (needed for any live, non-`test_` plan and for minting `ak_` keys) comes from the **Pro** or **Enterprise** subscription tier; the free tier only has the read scope. Test plans work on free with no subscription and no eSIMVault account. Get an `ak_` key from Console → Settings → API Keys after upgrading, or headlessly via `POST /v1/api-keys` once you already have write-scoped access from the console session.
|
|
105
|
+
|
|
48
106
|
## One-tool MCP
|
|
49
107
|
|
|
50
|
-
`carrier-intent` is a separate process with
|
|
108
|
+
`carrier-intent` is a separate process with two tools: `create_connectivity_intent` and `publish_storefront`. This skill uses `create_connectivity_intent`, which posts the intent above. `publish_storefront` belongs to the publish-storefront skill. It is not registered on `https://mcp.carrier.llc/mcp`.
|
|
51
109
|
|
|
52
110
|
```json
|
|
53
111
|
{
|
|
@@ -66,8 +124,13 @@ Put the `ak_` key in that env block on the machine. Call the tool with `plan` an
|
|
|
66
124
|
## After it succeeds
|
|
67
125
|
|
|
68
126
|
- Read `esim.iccid` and `esim.smdp_address` back to the user.
|
|
69
|
-
- Webhooks: `POST /v1/connectivity/webhook_endpoints` with an https URL. The secret is shown once.
|
|
70
|
-
-
|
|
127
|
+
- Webhooks: `POST /v1/connectivity/webhook_endpoints` with an https URL. The secret (`whsec_...`) is shown once, on create only. Events fire for `esim.issued` (a succeeded intent, including test plans), `esim.first_attach`, and `usage.threshold_reached`; a redelivery is `POST /v1/connectivity/events/:id/retry`, not a new event. Test-plan intents do fire `esim.issued` — it is not gated on `livemode`.
|
|
128
|
+
- Verify `Carrier-Signature`: HMAC-SHA256 over `${timestamp}.${body}` with the endpoint secret, header shaped `t=<unix>,v1=<hex>`. Recompute the hex over the raw body and compare to `v1`. Carrier does not currently enforce a delivery time margin on the send side, so an integration that wants replay protection should reject a `t` more than a few minutes from its own clock.
|
|
129
|
+
- Test usage: `POST /v1/connectivity/usage_records` with the intent id and `quantity_mb`. A live intent refuses a self-reported quantity with `400 network_metered` because the wallet meter already debits those bytes.
|
|
130
|
+
|
|
131
|
+
## Shared inventory with OCS resellers
|
|
132
|
+
|
|
133
|
+
A live (non-`test_`) plan draws its eSIM from the same eSIMVault account number as everything else on that account — the connectivity intent, the storefront checkout webhook, and a direct OCS `affectPackageToSubscriber` call all pull from one pool. Issuing through this skill does not create separate stock. Each draw is idempotency-locked per channel (`intent:<id>` for this path) so retries cannot double-issue, but two different channels against the same low-stock template can still race each other for the last unit — whichever wins the OCS call gets it, and the other side sees `NO_INVENTORY`/503, not a queued retry. If you also run OCS writes directly, keep template-level headroom rather than assuming this skill's calls are invisible to your other tooling.
|
|
71
134
|
|
|
72
135
|
## If the person is new, use these sentences
|
|
73
136
|
|
|
@@ -84,6 +147,8 @@ A country list is the countries someone typed on an offer. Carrier stores that l
|
|
|
84
147
|
Steering is which mobile networks a SIM is told to join. Changing it is a job for the person who runs the SIMs.
|
|
85
148
|
A green zone is a short list of sites that can stay reachable after the data is used up, so someone can get help, pay, or download the SIM again.
|
|
86
149
|
|
|
150
|
+
Longer explanations, plus when to use each tool and when to skip it: [references/glossary.md](references/glossary.md).
|
|
151
|
+
|
|
87
152
|
## Do not
|
|
88
153
|
|
|
89
154
|
- Do not register `https://mcp.carrier.llc/mcp` to issue this SIM. That server is the operator fleet.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Glossary, for someone new to this
|
|
2
|
+
|
|
3
|
+
A skill is a note your AI reads so it follows the same steps every time.
|
|
4
|
+
A plug lets an AI app press a button. This plug has one button: give this order a SIM.
|
|
5
|
+
This other plug opens every button a phone company uses to run many SIMs.
|
|
6
|
+
These are the same switchboard buttons, typed into a terminal instead of clicked. People call that a CLI.
|
|
7
|
+
An intent is your ask: please attach a SIM to this order. The same ask twice does not make two SIMs.
|
|
8
|
+
A product is the offer you sell: how much data, how many days, which countries, and the price. Saving it does not charge anyone.
|
|
9
|
+
A package is the data sitting on one SIM after it is issued. A code package, such as the library named @carrierllc/sdk, is a different thing.
|
|
10
|
+
A subscriber is one SIM line, and the person or device that line belongs to.
|
|
11
|
+
On this page a client is the small code library your app uses to ask for a SIM. A customer is the person who buys that SIM.
|
|
12
|
+
A country list is the countries someone typed on an offer. Carrier stores that list. It does not look up a live coverage map.
|
|
13
|
+
Steering is which mobile networks a SIM is told to join. Changing it is a job for the person who runs the SIMs.
|
|
14
|
+
A green zone is a short list of sites that can stay reachable after the data is used up, so someone can get help, pay, or download the SIM again.
|
|
@@ -18,10 +18,10 @@ Once per user, so every project sees it:
|
|
|
18
18
|
npx skills add https://carrier.llc -g -a cursor -a claude-code -y
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
`skills` 1.7.0 requires Node 22.20 or newer (`styleText` in a Node 20 runtime throws a SyntaxError before this even installs). On Node 20, run that command through `npx -p node@22` instead:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npx skills add https://
|
|
24
|
+
npx -y -p node@22 -p skills -- skills add https://carrier.llc -g -a cursor -a claude-code -y
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Publish
|
|
@@ -70,9 +70,9 @@ A replay of the same key returns the original store. Send theme and copy again o
|
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`price_cents` is the Stripe minor-unit amount. For EUR, 900 is 9.00. For JPY, 1000 is 1000 yen, not 100000.
|
|
73
|
+
`price_cents` is the Stripe minor-unit amount. For EUR, 900 is 9.00. For JPY, 1000 is 1000 yen, not 100000. `ocs_package_template_id` is the OCS package template that gets issued. Do not look it up in the operator OCS catalog. The default route is `product_id` (below): the connectivity plan already carries its template id, so you never type it. That id was supplied once, when the org created the plan with `POST /v1/connectivity/products` (`package_template_id`) or in the console under Connectivity products. Send `ocs_package_template_id` yourself only when you already hold a template id from your own OCS account. Changing this price does not change another slug. Leave out data, validity, countries, buyer text, or whether the plan is for sale, and those stored values stay. When the shop has one plan, a price alone is enough: the CLI and the intent tool reuse that plan's slug, name, currency, and template id. A new plan can omit currency when every saved plan uses the same code.
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
This is the standard route: to copy a connectivity plan the org already created, send its id instead of the template fields. Optional `price_cents` overrides the retail price. The shop address is derived from the plan name.
|
|
76
76
|
|
|
77
77
|
```json
|
|
78
78
|
{ "product_id": "cnp_abcdef0123456789abcdef0123456789", "price_cents": 1200 }
|
|
@@ -111,4 +111,12 @@ The CLI reads the existing store and merges color and copy before the PATCH. A s
|
|
|
111
111
|
|
|
112
112
|
`description` is the sentence buyers read under the plan name. Leave it out to keep the saved sentence. The intent tool field is `plan_description`. The CLI flag is `--plan-text`.
|
|
113
113
|
|
|
114
|
-
The
|
|
114
|
+
The intent server (`carrier-intent`, package `@carrierllc/mcp-intent`) has two tools: `create_connectivity_intent`, which the issue-esim skill covers, and `publish_storefront`, which this skill covers. Neither is an operator OCS tool. `publish_storefront` accepts `accent`, `hero`, `product_id`, `plan_slug`, `plan_name`, `price` (`9.00`, or `1000` with currency JPY) or `price_cents`, `currency`, `ocs_package_template_id`, `data_gb` (`1 GB`, or the text `Unlimited`), `unlimited` (boolean; the only way to sell an unlimited plan — a bare `data_gb: 0` without it is refused), `validity_days` (`7 days`), `regions` (France or DE), and `plan_description`. `product_id` copies a connectivity plan and does not need a template id.
|
|
115
|
+
|
|
116
|
+
## Who is paid
|
|
117
|
+
|
|
118
|
+
State of the code today, not a commercial agreement:
|
|
119
|
+
|
|
120
|
+
- A hosted shop sells through Stripe Checkout created by Carrier's checkout service with Carrier's own Stripe key. The session sets no connected account, application fee, or transfer, so the payment is collected on Carrier's Stripe account.
|
|
121
|
+
- Nothing in this API pays the buyer's money out to the brand, and it does not define a fee, a tax or VAT setting, or terms of sale. The `legalName` you send is printed in the shop footer. It is display text, not a merchant registration.
|
|
122
|
+
- Settlement, fees, VAT, and terms between the brand and Carrier are not set by this API. Confirm them with Carrier before you take live orders.
|
|
@@ -72,6 +72,18 @@ const MOCK_TEMPLATES: SkuTemplate[] = [
|
|
|
72
72
|
},
|
|
73
73
|
];
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* How long the storefront may keep serving a catalog it already fetched.
|
|
77
|
+
*
|
|
78
|
+
* A price or bundle change made in Carrier must be visible within the agreed
|
|
79
|
+
* ceiling (CAR-281: 3600s ISR). The unscoped `/api/templates` answer also
|
|
80
|
+
* carries `public, max-age=60` upstream, so revalidate + 60s is the real
|
|
81
|
+
* worst case. It used to be 3600 for the unscoped read, which put the worst
|
|
82
|
+
* case at 3660s. Keep one value for both reads and keep it well under the
|
|
83
|
+
* ceiling; the scoped read is also busted by tag on save.
|
|
84
|
+
*/
|
|
85
|
+
export const CATALOG_REVALIDATE_SECONDS = 60;
|
|
86
|
+
|
|
75
87
|
/** Why the catalog is not live. Absent when `source` is "live". */
|
|
76
88
|
export type CatalogDegradedReason = "unconfigured" | "upstream-error" | "network-error";
|
|
77
89
|
|
|
@@ -109,8 +121,8 @@ export async function fetchTemplateCatalog(): Promise<TemplateCatalog> {
|
|
|
109
121
|
: "/api/templates";
|
|
110
122
|
const res = await fetch(`${base}${templatesPath}`, {
|
|
111
123
|
next: slug
|
|
112
|
-
? { revalidate:
|
|
113
|
-
: { revalidate:
|
|
124
|
+
? { revalidate: CATALOG_REVALIDATE_SECONDS, tags: [`storefront:${slug}`] }
|
|
125
|
+
: { revalidate: CATALOG_REVALIDATE_SECONDS },
|
|
114
126
|
});
|
|
115
127
|
if (!res.ok) return sampleCatalog("upstream-error");
|
|
116
128
|
const templates = (await res.json()) as SkuTemplate[];
|