@carrierllc/mcp 0.13.24 → 0.13.26
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-YQQUV7AE.js → chunk-HTIPSHMH.js} +41 -5
- package/dist/chunk-HTIPSHMH.js.map +1 -0
- package/dist/cli.js +549 -508
- package/dist/cli.js.map +1 -1
- package/dist/index.js +49 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugin/carrier/skills/storefront/SKILL.md +1 -1
- package/templates/storefront/src/app/api/checkout/guest/route.ts +30 -1
- package/templates/storefront/src/vendor/carrier/client.ts +16 -2
- package/dist/chunk-YQQUV7AE.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carrierllc/mcp",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.26",
|
|
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)",
|
|
@@ -51,7 +51,7 @@ MCP over stdio (these drive host CLIs on your machine, so they are local-only):
|
|
|
51
51
|
|
|
52
52
|
```
|
|
53
53
|
list_deploy_targets dir: ./storefront
|
|
54
|
-
deploy_storefront dir: ./storefront [target, name, skip_build, push_secrets, verify, domain]
|
|
54
|
+
deploy_storefront dir: ./storefront [target, name, force, skip_build, push_secrets, verify, domain]
|
|
55
55
|
provision_storefront_clerk dir: ./storefront [name, production, no_create, url]
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -102,7 +102,32 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|
|
102
102
|
}
|
|
103
103
|
const channel = normalizeChannel(body.channel);
|
|
104
104
|
const stripeKey = process.env.STRIPE_SECRET_KEY;
|
|
105
|
-
|
|
105
|
+
// The deployment selects the public shop, never request-body scope. Read
|
|
106
|
+
// its canonical id and plans together so fulfilment cannot fall back to
|
|
107
|
+
// the legacy catalog or charge a stale/foreign tenant's price.
|
|
108
|
+
const slug = process.env.NEXT_PUBLIC_STOREFRONT_SLUG;
|
|
109
|
+
let storefrontId: string | null = null;
|
|
110
|
+
let catalog;
|
|
111
|
+
if (slug) {
|
|
112
|
+
const base = (process.env.NEXT_PUBLIC_CARRIER_API_URL ?? "").replace(/\/+$/, "");
|
|
113
|
+
if (!base || !/^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/.test(slug)) {
|
|
114
|
+
return NextResponse.json({ error: "Storefront unavailable" }, { status: 503 });
|
|
115
|
+
}
|
|
116
|
+
const response = await fetch(`${base}/api/hosted/${encodeURIComponent(slug)}`, { cache: "no-store" });
|
|
117
|
+
if (!response.ok) return NextResponse.json({ error: "Storefront unavailable" }, { status: 503 });
|
|
118
|
+
const shop = await response.json();
|
|
119
|
+
if (!shop || typeof shop !== "object" || Array.isArray(shop) ||
|
|
120
|
+
typeof shop.id !== "string" || !/^sf_[A-Za-z0-9_-]{20}$/.test(shop.id) ||
|
|
121
|
+
typeof shop.name !== "string" || !shop.name.trim() || typeof shop.url !== "string" ||
|
|
122
|
+
(shop.status !== undefined && shop.status !== "published") ||
|
|
123
|
+
shop.slug !== slug || !Array.isArray(shop.plans)) {
|
|
124
|
+
return NextResponse.json({ error: "Storefront unavailable" }, { status: 503 });
|
|
125
|
+
}
|
|
126
|
+
storefrontId = shop.id;
|
|
127
|
+
catalog = { source: "live" as const, templates: shop.plans };
|
|
128
|
+
} else {
|
|
129
|
+
catalog = await fetchTemplateCatalog();
|
|
130
|
+
}
|
|
106
131
|
|
|
107
132
|
const decision = decideGuestCheckout({
|
|
108
133
|
catalog,
|
|
@@ -179,6 +204,10 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
|
|
179
204
|
// session-level copy is what fulfilment keys off to provision the eSIM —
|
|
180
205
|
// without it the plan is lost and the order is stuck "activating".
|
|
181
206
|
"metadata[templateId]": templateId,
|
|
207
|
+
...(storefrontId ? {
|
|
208
|
+
"metadata[storefront_id]": storefrontId,
|
|
209
|
+
"payment_intent_data[metadata][storefront_id]": storefrontId,
|
|
210
|
+
} : {}),
|
|
182
211
|
"payment_intent_data[metadata][templateId]": templateId,
|
|
183
212
|
// Where the sale came from. Written alongside templateId and by the same
|
|
184
213
|
// reasoning: attribution recovered later from Stripe reports is guesswork,
|
|
@@ -102,6 +102,14 @@ function apiKey(): string {
|
|
|
102
102
|
return process.env.CARRIER_API_KEY ?? "";
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/** Order tenant scope comes only from deployment config, never buyer input. */
|
|
106
|
+
function orderPath(orderId: string, action = ""): string | null {
|
|
107
|
+
const path = `/api/orders/${encodeURIComponent(orderId)}${action}`;
|
|
108
|
+
const slug = process.env.NEXT_PUBLIC_STOREFRONT_SLUG;
|
|
109
|
+
if (slug && !/^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/.test(slug)) return null;
|
|
110
|
+
return slug ? `${path}?storefront=${encodeURIComponent(slug)}` : path;
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
function sampleCatalog(reason: CatalogDegradedReason): TemplateCatalog {
|
|
106
114
|
return { templates: MOCK_TEMPLATES, source: "sample", reason };
|
|
107
115
|
}
|
|
@@ -190,12 +198,15 @@ export async function fetchOrder(orderId: string): Promise<OrderLookup> {
|
|
|
190
198
|
if (!base) return { ok: false, reason: "unconfigured" };
|
|
191
199
|
if (!/^[A-Za-z0-9_-]{1,256}$/.test(orderId)) return { ok: false, reason: "not-found" };
|
|
192
200
|
|
|
201
|
+
const path = orderPath(orderId);
|
|
202
|
+
if (path === null) return { ok: false, reason: "unconfigured" };
|
|
203
|
+
|
|
193
204
|
const headers: Record<string, string> = { Accept: "application/json" };
|
|
194
205
|
const key = apiKey();
|
|
195
206
|
if (key) headers.Authorization = `Bearer ${key}`;
|
|
196
207
|
|
|
197
208
|
try {
|
|
198
|
-
const res = await fetch(`${base}
|
|
209
|
+
const res = await fetch(`${base}${path}`, {
|
|
199
210
|
headers,
|
|
200
211
|
cache: "no-store",
|
|
201
212
|
});
|
|
@@ -228,12 +239,15 @@ async function orderScopedCall<T>(
|
|
|
228
239
|
if (!base) return { ok: false, reason: "unconfigured" };
|
|
229
240
|
if (!/^[A-Za-z0-9_-]{1,256}$/.test(orderId)) return { ok: false, reason: "not-found" };
|
|
230
241
|
|
|
242
|
+
const scopedPath = orderPath(orderId, path);
|
|
243
|
+
if (scopedPath === null) return { ok: false, reason: "unconfigured" };
|
|
244
|
+
|
|
231
245
|
const headers: Record<string, string> = { Accept: "application/json", ...(init?.headers as Record<string, string>) };
|
|
232
246
|
const key = apiKey();
|
|
233
247
|
if (key) headers.Authorization = `Bearer ${key}`;
|
|
234
248
|
|
|
235
249
|
try {
|
|
236
|
-
const res = await fetch(`${base}
|
|
250
|
+
const res = await fetch(`${base}${scopedPath}`, {
|
|
237
251
|
...init,
|
|
238
252
|
headers,
|
|
239
253
|
cache: "no-store",
|