@carrierllc/mcp 0.2.1 → 0.2.3
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/index.d.ts +0 -13
- package/dist/index.js +9374 -77
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
- package/dist/apps/app-state.d.ts +0 -19
- package/dist/apps/app-state.d.ts.map +0 -1
- package/dist/apps/app-state.js +0 -28
- package/dist/apps/app-state.js.map +0 -1
- package/dist/apps/balance-topup.d.ts +0 -19
- package/dist/apps/balance-topup.d.ts.map +0 -1
- package/dist/apps/balance-topup.js +0 -121
- package/dist/apps/balance-topup.js.map +0 -1
- package/dist/apps/fleet-health-app.d.ts +0 -29
- package/dist/apps/fleet-health-app.d.ts.map +0 -1
- package/dist/apps/fleet-health-app.js +0 -152
- package/dist/apps/fleet-health-app.js.map +0 -1
- package/dist/apps/index.d.ts +0 -37
- package/dist/apps/index.d.ts.map +0 -1
- package/dist/apps/index.js +0 -42
- package/dist/apps/index.js.map +0 -1
- package/dist/apps/provisioning-wizard.d.ts +0 -11
- package/dist/apps/provisioning-wizard.d.ts.map +0 -1
- package/dist/apps/provisioning-wizard.js +0 -345
- package/dist/apps/provisioning-wizard.js.map +0 -1
- package/dist/audit.d.ts +0 -31
- package/dist/audit.d.ts.map +0 -1
- package/dist/audit.js +0 -42
- package/dist/audit.js.map +0 -1
- package/dist/billing-thresholds.d.ts +0 -71
- package/dist/billing-thresholds.d.ts.map +0 -1
- package/dist/billing-thresholds.js +0 -238
- package/dist/billing-thresholds.js.map +0 -1
- package/dist/billing.d.ts +0 -95
- package/dist/billing.d.ts.map +0 -1
- package/dist/billing.js +0 -239
- package/dist/billing.js.map +0 -1
- package/dist/clerk.d.ts +0 -125
- package/dist/clerk.d.ts.map +0 -1
- package/dist/clerk.js +0 -389
- package/dist/clerk.js.map +0 -1
- package/dist/client.d.ts +0 -35
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -85
- package/dist/client.js.map +0 -1
- package/dist/credits.d.ts +0 -165
- package/dist/credits.d.ts.map +0 -1
- package/dist/credits.js +0 -483
- package/dist/credits.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/intelligence.d.ts +0 -4
- package/dist/intelligence.d.ts.map +0 -1
- package/dist/intelligence.js +0 -977
- package/dist/intelligence.js.map +0 -1
- package/dist/list-recent-ocs-events.d.ts +0 -48
- package/dist/list-recent-ocs-events.d.ts.map +0 -1
- package/dist/list-recent-ocs-events.js +0 -160
- package/dist/list-recent-ocs-events.js.map +0 -1
- package/dist/manus-client.d.ts +0 -429
- package/dist/manus-client.d.ts.map +0 -1
- package/dist/manus-client.js +0 -401
- package/dist/manus-client.js.map +0 -1
- package/dist/manus-common.d.ts +0 -5
- package/dist/manus-common.d.ts.map +0 -1
- package/dist/manus-common.js +0 -5
- package/dist/manus-common.js.map +0 -1
- package/dist/manus-schedule.d.ts +0 -74
- package/dist/manus-schedule.d.ts.map +0 -1
- package/dist/manus-schedule.js +0 -213
- package/dist/manus-schedule.js.map +0 -1
- package/dist/manus-usage.d.ts +0 -32
- package/dist/manus-usage.d.ts.map +0 -1
- package/dist/manus-usage.js +0 -120
- package/dist/manus-usage.js.map +0 -1
- package/dist/manus-webhook.d.ts +0 -60
- package/dist/manus-webhook.d.ts.map +0 -1
- package/dist/manus-webhook.js +0 -361
- package/dist/manus-webhook.js.map +0 -1
- package/dist/pricing-tools.d.ts +0 -26
- package/dist/pricing-tools.d.ts.map +0 -1
- package/dist/pricing-tools.js +0 -428
- package/dist/pricing-tools.js.map +0 -1
- package/dist/projects-tools.d.ts +0 -39
- package/dist/projects-tools.d.ts.map +0 -1
- package/dist/projects-tools.js +0 -545
- package/dist/projects-tools.js.map +0 -1
- package/dist/prompts.d.ts +0 -7
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js +0 -151
- package/dist/prompts.js.map +0 -1
- package/dist/stripe-connect-tools.d.ts +0 -33
- package/dist/stripe-connect-tools.d.ts.map +0 -1
- package/dist/stripe-connect-tools.js +0 -504
- package/dist/stripe-connect-tools.js.map +0 -1
- package/dist/timing-safe-equal.d.ts +0 -5
- package/dist/timing-safe-equal.d.ts.map +0 -1
- package/dist/timing-safe-equal.js +0 -13
- package/dist/timing-safe-equal.js.map +0 -1
- package/dist/tools-backlog.d.ts +0 -37
- package/dist/tools-backlog.d.ts.map +0 -1
- package/dist/tools-backlog.js +0 -410
- package/dist/tools-backlog.js.map +0 -1
- package/dist/tools-carrier-ask.d.ts +0 -138
- package/dist/tools-carrier-ask.d.ts.map +0 -1
- package/dist/tools-carrier-ask.js +0 -913
- package/dist/tools-carrier-ask.js.map +0 -1
- package/dist/tools-pricing.d.ts +0 -18
- package/dist/tools-pricing.d.ts.map +0 -1
- package/dist/tools-pricing.js +0 -172
- package/dist/tools-pricing.js.map +0 -1
- package/dist/tools-ui-agent-ask.d.ts +0 -29
- package/dist/tools-ui-agent-ask.d.ts.map +0 -1
- package/dist/tools-ui-agent-ask.js +0 -272
- package/dist/tools-ui-agent-ask.js.map +0 -1
- package/dist/tools-ui-agent-schedule.d.ts +0 -29
- package/dist/tools-ui-agent-schedule.d.ts.map +0 -1
- package/dist/tools-ui-agent-schedule.js +0 -517
- package/dist/tools-ui-agent-schedule.js.map +0 -1
- package/dist/tools-ui-agent.d.ts +0 -40
- package/dist/tools-ui-agent.d.ts.map +0 -1
- package/dist/tools-ui-agent.js +0 -508
- package/dist/tools-ui-agent.js.map +0 -1
- package/dist/tools.d.ts +0 -60
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -1384
- package/dist/tools.js.map +0 -1
- package/dist/types.d.ts +0 -298
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
|
@@ -1,913 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Carrier MCP — carrier_ask + carrier_ask_describe (v2.0 live routing).
|
|
3
|
-
*
|
|
4
|
-
* IMPLEMENTATION STATUS: LIVE (Phase 2)
|
|
5
|
-
* carrier_ask now routes via Claude Haiku 4.5 on AWS Bedrock tool-use.
|
|
6
|
-
* The router receives the full TOOL_REGISTRY as its tool catalog and returns
|
|
7
|
-
* a single tool_use block identifying the best matching tool + extracted params.
|
|
8
|
-
*
|
|
9
|
-
* ACTIVATION:
|
|
10
|
-
* - Requires AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY in Doppler carrier/dev+stg+prd.
|
|
11
|
-
* - Set CARRIER_ASK_ENABLED=true in Doppler to activate per-env.
|
|
12
|
-
* - Without the credentials/flag, carrier_ask gracefully degrades to routing_pending.
|
|
13
|
-
*
|
|
14
|
-
* DESIGN CHOICES:
|
|
15
|
-
* - Router model: Claude Haiku 4.5 on Bedrock (us.anthropic.claude-haiku-4-5-20251001-v1:0).
|
|
16
|
-
* - Auth: SigV4 via aws4fetch — no AWS SDK bundle (CF Worker compatible).
|
|
17
|
-
* - tool_choice: { type: "any" } — forces a tool_use response.
|
|
18
|
-
* - Ambiguous/no-match: sentinel "carrier_clarify" tool for Haiku to signal.
|
|
19
|
-
* - HARD_BLOCK_TOOLS: always return confirm_token; never auto-execute.
|
|
20
|
-
* - Confirm token TTL: 120s. Single-use (deleted on consume).
|
|
21
|
-
* - Intent hashing: SHA-256, hex. Not stored verbatim (PII guard).
|
|
22
|
-
* - Audit log: Analytics Engine, fire-and-forget. Extended schema for router.
|
|
23
|
-
* - Rate-limit (429): returns friendly retry hint, never crashes.
|
|
24
|
-
*
|
|
25
|
-
* SAFETY FLOORS (non-negotiable):
|
|
26
|
-
* - Resolved tool name validated against TOOL_REGISTRY — no hallucination.
|
|
27
|
-
* - HARD_BLOCK_TOOLS always require confirm_token round-trip.
|
|
28
|
-
* - No-match / ambiguous: structured response, never fabricated tool call.
|
|
29
|
-
*/
|
|
30
|
-
import { AwsClient } from "aws4fetch";
|
|
31
|
-
import { z } from "zod";
|
|
32
|
-
import { sha256 } from "@noble/hashes/sha256";
|
|
33
|
-
import { bytesToHex } from "@noble/hashes/utils";
|
|
34
|
-
import { TOOL_SCOPES, DESTRUCTIVE_TOOLS, wrapHandler } from "./tools.js";
|
|
35
|
-
import { BACKLOG_TOOL_SCOPES } from "./tools-backlog.js";
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
// Tool registry — complete list of all MCP tool names known at build time.
|
|
38
|
-
// Resolved tool names are validated against this set before returning to caller.
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
const TOOL_REGISTRY = new Set([
|
|
41
|
-
// v1 OCS tools (43)
|
|
42
|
-
...Object.keys(TOOL_SCOPES),
|
|
43
|
-
// v1.1 backlog tools
|
|
44
|
-
...Object.keys(BACKLOG_TOOL_SCOPES),
|
|
45
|
-
// intelligence composites (8)
|
|
46
|
-
"diagnose_subscriber",
|
|
47
|
-
"fleet_health",
|
|
48
|
-
"detect_usage_anomalies",
|
|
49
|
-
"optimize_package",
|
|
50
|
-
"churn_risk",
|
|
51
|
-
"audit_network_coverage",
|
|
52
|
-
"marketing_intelligence",
|
|
53
|
-
"high_cost_subscribers",
|
|
54
|
-
// event ring buffer (1)
|
|
55
|
-
"list_recent_ocs_events",
|
|
56
|
-
// MCP App tools (3)
|
|
57
|
-
"fleet_health_app",
|
|
58
|
-
"provision_esim_wizard",
|
|
59
|
-
"balance_topup_form",
|
|
60
|
-
// router tools (self-reference)
|
|
61
|
-
"carrier_ask",
|
|
62
|
-
"carrier_ask_describe",
|
|
63
|
-
]);
|
|
64
|
-
// Tools that MUST NEVER auto-execute from a natural-language intent.
|
|
65
|
-
// Always require confirm_token round-trip regardless of confidence.
|
|
66
|
-
const HARD_BLOCK_TOOLS = new Set([
|
|
67
|
-
"clean_all_packages",
|
|
68
|
-
"delete_subscriber_package",
|
|
69
|
-
"modify_account_balance",
|
|
70
|
-
"modify_subscriber_status",
|
|
71
|
-
"change_sim_status",
|
|
72
|
-
"reset_subscriber_gz_counter",
|
|
73
|
-
]);
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// Confirm token helpers
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
const CONFIRM_TOKEN_TTL_SECONDS = 120;
|
|
78
|
-
const CONFIRM_TOKEN_PREFIX = "carrier_ask_confirm:";
|
|
79
|
-
function generateConfirmToken() {
|
|
80
|
-
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
81
|
-
return bytesToHex(bytes);
|
|
82
|
-
}
|
|
83
|
-
async function storeConfirmToken(kv, token, payload) {
|
|
84
|
-
await kv.put(`${CONFIRM_TOKEN_PREFIX}${token}`, JSON.stringify(payload), { expirationTtl: CONFIRM_TOKEN_TTL_SECONDS });
|
|
85
|
-
}
|
|
86
|
-
async function consumeConfirmToken(kv, token) {
|
|
87
|
-
const raw = await kv.get(`${CONFIRM_TOKEN_PREFIX}${token}`, "text");
|
|
88
|
-
if (!raw)
|
|
89
|
-
return null;
|
|
90
|
-
await kv.delete(`${CONFIRM_TOKEN_PREFIX}${token}`);
|
|
91
|
-
return JSON.parse(raw);
|
|
92
|
-
}
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
// Intent hashing — SHA-256, hex-encoded.
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
export function hashIntent(intent) {
|
|
97
|
-
return bytesToHex(sha256(new TextEncoder().encode(intent)));
|
|
98
|
-
}
|
|
99
|
-
// ---------------------------------------------------------------------------
|
|
100
|
-
// Router tool catalog for Bedrock.
|
|
101
|
-
// Sentinel "carrier_clarify" signals ambiguity without hallucinating a match.
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
const ROUTER_TOOLS = [
|
|
104
|
-
{
|
|
105
|
-
name: "carrier_clarify",
|
|
106
|
-
description: "Use ONLY when the intent is genuinely ambiguous and you cannot determine a single best-matching tool. " +
|
|
107
|
-
"Provide 1-3 candidate tools and a clarifying question.",
|
|
108
|
-
input_schema: {
|
|
109
|
-
type: "object",
|
|
110
|
-
properties: {
|
|
111
|
-
candidates: {
|
|
112
|
-
type: "array",
|
|
113
|
-
items: {
|
|
114
|
-
type: "object",
|
|
115
|
-
properties: {
|
|
116
|
-
tool: { type: "string" },
|
|
117
|
-
reason: { type: "string" },
|
|
118
|
-
},
|
|
119
|
-
required: ["tool", "reason"],
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
clarifying_question: { type: "string" },
|
|
123
|
-
},
|
|
124
|
-
required: ["candidates", "clarifying_question"],
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: "list_reseller_accounts",
|
|
129
|
-
description: "List all reseller accounts. Intent: 'show my accounts', 'list resellers', 'what accounts do I have'.",
|
|
130
|
-
input_schema: { type: "object", properties: {} },
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: "get_reseller_info",
|
|
134
|
-
description: "Get info about a specific reseller. Intent: 'reseller info', 'account details for reseller X'.",
|
|
135
|
-
input_schema: { type: "object", properties: { reseller_id: { type: "number" } } },
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: "esim_status_per_account",
|
|
139
|
-
description: "Show eSIM counts by status per account. Intent: 'esim overview', 'how many esims active per account'.",
|
|
140
|
-
input_schema: { type: "object", properties: {} },
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: "get_subscriber",
|
|
144
|
-
description: "Look up a single subscriber by ICCID or MSISDN. Intent: 'get subscriber', 'look up ICCID', 'find SIM 8931...'.",
|
|
145
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, msisdn: { type: "string" } } },
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
name: "list_subscribers",
|
|
149
|
-
description: "List subscribers in an account. Intent: 'list subscribers', 'show all SIMs', 'subscribers in account X'.",
|
|
150
|
-
input_schema: { type: "object", properties: { account_id: { type: "number" }, page: { type: "number" } } },
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
name: "subscriber_usage",
|
|
154
|
-
description: "Show data usage for a subscriber (max 7 days). Intent: 'how much data did X use', 'usage for subscriber', 'data consumption'.",
|
|
155
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, start: { type: "string" }, end: { type: "string" } } },
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: "subscriber_network_events",
|
|
159
|
-
description: "Show network events (attach/detach/roaming) for a subscriber. Intent: 'network events', 'connection history', 'roaming events'.",
|
|
160
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, start: { type: "string" }, end: { type: "string" } } },
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: "list_subscriber_packages",
|
|
164
|
-
description: "List active packages for a subscriber. Intent: 'what packages does subscriber have', 'show data packages', 'subscriber plan'.",
|
|
165
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
name: "list_package_templates",
|
|
169
|
-
description: "List available package templates. Intent: 'what packages can I assign', 'show product catalog', 'available data plans'.",
|
|
170
|
-
input_schema: { type: "object", properties: {} },
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: "assign_package",
|
|
174
|
-
description: "Assign a one-time data package to a subscriber. Intent: 'assign package', 'give subscriber X the Y plan', 'add data package'.",
|
|
175
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, packageTemplateId: { type: "number" } } },
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
name: "assign_recurring_package",
|
|
179
|
-
description: "Set up auto-renewing recurring package for a subscriber. Intent: 'monthly plan', 'recurring package', 'auto-renew data'.",
|
|
180
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, packageTemplateId: { type: "number" } } },
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
name: "modify_package_limits",
|
|
184
|
-
description: "Change data/voice/SMS limits on an existing package. Intent: 'change package limit', 'update data cap', 'modify plan limits'.",
|
|
185
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, packageId: { type: "number" } } },
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
name: "modify_package_expiry",
|
|
189
|
-
description: "Change the expiry date of a subscriber package. Intent: 'extend package', 'change expiry', 'push package end date'.",
|
|
190
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, packageId: { type: "number" }, expiryDate: { type: "string" } } },
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
name: "modify_package_status",
|
|
194
|
-
description: "Activate or pause a specific package. Intent: 'pause package', 'activate package', 'suspend data plan'.",
|
|
195
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, packageId: { type: "number" }, status: { type: "string" } } },
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
name: "stop_resume_recurring_package",
|
|
199
|
-
description: "Stop or resume a recurring package. Intent: 'cancel auto-renew', 'stop recurring', 'resume monthly plan'.",
|
|
200
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, packageId: { type: "number" }, action: { type: "string" } } },
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
name: "delete_subscriber_package",
|
|
204
|
-
description: "DESTRUCTIVE: Delete a package from a subscriber. Intent: 'delete package', 'remove plan from subscriber'. Requires confirm_token.",
|
|
205
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, packageId: { type: "number" } } },
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
name: "clean_all_packages",
|
|
209
|
-
description: "DESTRUCTIVE: Remove ALL packages from a subscriber. Irreversible. Intent: 'reset packages', 'clean all plans', 'wipe packages'. Requires confirm_token.",
|
|
210
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
name: "modify_subscriber_status",
|
|
214
|
-
description: "DESTRUCTIVE if terminating: Change subscriber status (ACTIVE/SUSPENDED/TERMINATED). Intent: 'suspend subscriber', 'pause SIM', 'terminate', 'reactivate'. Requires confirm_token.",
|
|
215
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, status: { type: "string" } } },
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
name: "modify_subscriber_balance",
|
|
219
|
-
description: "Add or set credit balance for a subscriber. Intent: 'top up subscriber', 'add credit', 'set subscriber balance'.",
|
|
220
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, amount: { type: "number" } } },
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
name: "modify_account_balance",
|
|
224
|
-
description: "DESTRUCTIVE: Modify account-level balance. Intent: 'adjust account balance', 'add account credit'. Requires confirm_token.",
|
|
225
|
-
input_schema: { type: "object", properties: { accountId: { type: "number" }, amount: { type: "number" } } },
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
name: "modify_subscriber_contact_info",
|
|
229
|
-
description: "Update subscriber contact details. Intent: 'update subscriber name', 'change email for SIM', 'fix contact info'.",
|
|
230
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, name: { type: "string" }, email: { type: "string" } } },
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: "set_subscriber_traffic_restrictions",
|
|
234
|
-
description: "Enable or disable voice/SMS/data restrictions. Intent: 'block data for subscriber', 'restrict voice', 'data-only SIM'.",
|
|
235
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
name: "modify_subscriber_steering_list",
|
|
239
|
-
description: "Set preferred network steering list for a subscriber. Intent: 'change network preference', 'steer to operator X', 'preferred network'.",
|
|
240
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, steeringListId: { type: "number" } } },
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
name: "push_steering_to_subscriber",
|
|
244
|
-
description: "Push network steering config to subscriber device. Intent: 'push steering', 'apply network config', 'force network update'.",
|
|
245
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
name: "move_subscriber_range_to_account",
|
|
249
|
-
description: "Move a range of subscribers to a different account. Intent: 'move subscribers', 'transfer SIMs to account'.",
|
|
250
|
-
input_schema: { type: "object", properties: { accountId: { type: "number" } } },
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
name: "hlr_get_bitrate",
|
|
254
|
-
description: "Get current HLR bitrate cap for a subscriber. Intent: 'what speed is subscriber throttled to', 'get bitrate', 'check speed cap'.",
|
|
255
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
name: "hlr_set_bitrate",
|
|
259
|
-
description: "Set or remove HLR speed cap (throttle). Intent: 'throttle subscriber', 'set speed limit', 'cap to 256kbps', 'remove throttle'.",
|
|
260
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, bitrate: { type: "number" } } },
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
name: "change_sim_status",
|
|
264
|
-
description: "DESTRUCTIVE: Change physical SIM card status. Intent: 'deactivate SIM', 'delete SIM card', 'suspend card'. Requires confirm_token.",
|
|
265
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, status: { type: "string" } } },
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
name: "send_sms",
|
|
269
|
-
description: "Send SMS to a subscriber. Intent: 'send text to subscriber', 'SMS ICCID X', 'message subscriber'.",
|
|
270
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, message: { type: "string" } } },
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
name: "get_sim_provider_status",
|
|
274
|
-
description: "Get SIM provider / eSIM profile status. Intent: 'SIM provider status', 'eSIM profile downloaded?', 'check profile status'.",
|
|
275
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
name: "get_subscriber_location",
|
|
279
|
-
description: "Get approximate subscriber location. Intent: 'where is subscriber', 'subscriber location', 'locate SIM'.",
|
|
280
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
name: "get_subscriber_location_by_cell_id",
|
|
284
|
-
description: "Get granular subscriber location by cell tower. Intent: 'cell-level location', 'cell tower for subscriber', 'exact location'.",
|
|
285
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
name: "list_steering_lists",
|
|
289
|
-
description: "List available network steering lists. Intent: 'show steering lists', 'available networks', 'what network profiles exist'.",
|
|
290
|
-
input_schema: { type: "object", properties: {} },
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
name: "list_network_profiles",
|
|
294
|
-
description: "List network profiles. Intent: 'list network profiles', 'show network configs'.",
|
|
295
|
-
input_schema: { type: "object", properties: {} },
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
name: "list_location_zones",
|
|
299
|
-
description: "List location zones. Intent: 'list zones', 'show coverage zones'.",
|
|
300
|
-
input_schema: { type: "object", properties: {} },
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
name: "list_detailed_location_zones",
|
|
304
|
-
description: "List detailed location zones with coordinates. Intent: 'detailed zones', 'zone coordinates', 'coverage map'.",
|
|
305
|
-
input_schema: { type: "object", properties: {} },
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
name: "list_destination_prefixes",
|
|
309
|
-
description: "List destination number prefixes. Intent: 'list prefixes', 'routing prefixes', 'number ranges'.",
|
|
310
|
-
input_schema: { type: "object", properties: {} },
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
name: "list_destination_lists",
|
|
314
|
-
description: "List destination lists for voice/SMS routing. Intent: 'destination lists', 'routing lists'.",
|
|
315
|
-
input_schema: { type: "object", properties: {} },
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
name: "list_sponsors",
|
|
319
|
-
description: "List sponsor accounts. Intent: 'list sponsors', 'show sponsor accounts'.",
|
|
320
|
-
input_schema: { type: "object", properties: {} },
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
name: "get_tariff",
|
|
324
|
-
description: "Get tariff/rate rules. Intent: 'what are my rates', 'tariff info', 'pricing rules'.",
|
|
325
|
-
input_schema: { type: "object", properties: {} },
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
name: "create_package_template",
|
|
329
|
-
description: "Create a new package template. Intent: 'create package', 'new product', 'add package template'.",
|
|
330
|
-
input_schema: { type: "object", properties: { name: { type: "string" } } },
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
name: "create_location_zone",
|
|
334
|
-
description: "Create a new location zone. Intent: 'create zone', 'add location zone', 'new coverage zone'.",
|
|
335
|
-
input_schema: { type: "object", properties: { name: { type: "string" } } },
|
|
336
|
-
},
|
|
337
|
-
{
|
|
338
|
-
name: "modify_template_core",
|
|
339
|
-
description: "Modify core settings of a package template. Intent: 'edit package template', 'change template name/settings'.",
|
|
340
|
-
input_schema: { type: "object", properties: { templateId: { type: "number" } } },
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
name: "modify_template_recurring",
|
|
344
|
-
description: "Modify recurring billing settings of a template. Intent: 'change template renewal', 'edit recurring settings'.",
|
|
345
|
-
input_schema: { type: "object", properties: { templateId: { type: "number" } } },
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
name: "modify_template_throttling",
|
|
349
|
-
description: "Modify throttling settings of a template. Intent: 'change template speed', 'edit throttle on template'.",
|
|
350
|
-
input_schema: { type: "object", properties: { templateId: { type: "number" } } },
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
name: "subscriber_active_period",
|
|
354
|
-
description: "Get or set the active period for a subscriber. Intent: 'active period', 'subscriber validity', 'SIM activation window'.",
|
|
355
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
name: "list_recent_ocs_events",
|
|
359
|
-
description: "List recent OCS events for a subscriber (last 50, 24h window). Intent: 'recent events', 'event history', 'OCS log'.",
|
|
360
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
name: "affect_subscriber_phone_number",
|
|
364
|
-
description: "Assign or unassign a phone number to a subscriber. Intent: 'assign phone number', 'give SIM a number', 'remove number'.",
|
|
365
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, msisdn: { type: "string" } } },
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
name: "modify_subscriber_mobile_plan",
|
|
369
|
-
description: "Change the mobile plan for a subscriber. Intent: 'change mobile plan', 'switch plan', 'update rate plan'.",
|
|
370
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, planId: { type: "number" } } },
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
name: "modify_subscriber_package_active_period",
|
|
374
|
-
description: "Modify the active period of a subscriber's package. Intent: 'extend package period', 'change package dates'.",
|
|
375
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" }, packageId: { type: "number" } } },
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
name: "modify_subscriber_voip_plan",
|
|
379
|
-
description: "Change VoIP plan for a subscriber. Intent: 'change voip plan', 'update voice plan'.",
|
|
380
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
name: "reset_subscriber_gz_counter",
|
|
384
|
-
description: "DESTRUCTIVE: Reset a subscriber's guaranteed zone counter. Irreversible. Intent: 'reset gz counter', 'clear gz usage'. Requires confirm_token.",
|
|
385
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
name: "diagnose_subscriber",
|
|
389
|
-
description: "Run composite diagnostic on a subscriber. Intent: 'why is subscriber offline', 'diagnose SIM', 'troubleshoot ICCID'.",
|
|
390
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
name: "fleet_health",
|
|
394
|
-
description: "Show overall fleet health (active/suspended/churned counts, alerts). Intent: 'fleet status', 'how is my fleet', 'health overview', 'how many esims active'.",
|
|
395
|
-
input_schema: { type: "object", properties: {} },
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
name: "detect_usage_anomalies",
|
|
399
|
-
description: "Detect unusual data usage patterns across the fleet. Intent: 'usage anomalies', 'abnormal data use', 'spike detection'.",
|
|
400
|
-
input_schema: { type: "object", properties: {} },
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
name: "optimize_package",
|
|
404
|
-
description: "Suggest package optimizations for a subscriber based on usage. Intent: 'optimize package', 'right-size plan', 'package recommendation'.",
|
|
405
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
name: "churn_risk",
|
|
409
|
-
description: "Identify subscribers at risk of churning. Intent: 'churn risk', 'at-risk subscribers', 'who might leave'.",
|
|
410
|
-
input_schema: { type: "object", properties: {} },
|
|
411
|
-
},
|
|
412
|
-
{
|
|
413
|
-
name: "audit_network_coverage",
|
|
414
|
-
description: "Audit network coverage for subscriber or fleet. Intent: 'coverage audit', 'network coverage check', 'coverage gaps'.",
|
|
415
|
-
input_schema: { type: "object", properties: { iccid: { type: "string" } } },
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
name: "marketing_intelligence",
|
|
419
|
-
description: "Get marketing intelligence: usage trends, popular packages, growth metrics. Intent: 'marketing data', 'growth report', 'popular plans'.",
|
|
420
|
-
input_schema: { type: "object", properties: {} },
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
name: "high_cost_subscribers",
|
|
424
|
-
description: "List subscribers generating highest costs. Intent: 'high cost subscribers', 'most expensive SIMs', 'cost outliers'.",
|
|
425
|
-
input_schema: { type: "object", properties: {} },
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
name: "fleet_health_app",
|
|
429
|
-
description: "Open the fleet health MCP App UI. Intent: 'open fleet dashboard app', 'fleet health UI'.",
|
|
430
|
-
input_schema: { type: "object", properties: {} },
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name: "provision_esim_wizard",
|
|
434
|
-
description: "Open the eSIM provisioning wizard app. Intent: 'provision esim', 'new esim wizard', 'onboard esim'.",
|
|
435
|
-
input_schema: { type: "object", properties: {} },
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
name: "balance_topup_form",
|
|
439
|
-
description: "Open the balance top-up form app. Intent: 'top up balance', 'add credit form', 'balance topup'.",
|
|
440
|
-
input_schema: { type: "object", properties: {} },
|
|
441
|
-
},
|
|
442
|
-
{
|
|
443
|
-
name: "carrier_ask_describe",
|
|
444
|
-
description: "Get documentation for a specific carrier tool. Intent: 'describe tool X', 'how does assign_package work', 'tool documentation'.",
|
|
445
|
-
input_schema: { type: "object", properties: { tool_name: { type: "string" } } },
|
|
446
|
-
},
|
|
447
|
-
];
|
|
448
|
-
// System prompt for the Haiku router
|
|
449
|
-
const ROUTER_SYSTEM_PROMPT = `You are a carrier fleet operations router. Your job is to map a user's natural-language intent to exactly ONE tool from the Carrier MCP tool registry.
|
|
450
|
-
|
|
451
|
-
Rules:
|
|
452
|
-
1. Pick the single best-matching tool. Never pick carrier_ask (the router itself).
|
|
453
|
-
2. Extract any parameters mentioned in the intent (ICCID, account IDs, amounts, etc.) as the tool's input.
|
|
454
|
-
3. Use carrier_clarify ONLY when genuinely ambiguous — when multiple tools are equally likely and you need more information.
|
|
455
|
-
4. DESTRUCTIVE tools are flagged in their descriptions — still pick them if they match; the safety layer handles the confirm flow.
|
|
456
|
-
5. Only include params explicitly mentioned in the intent.
|
|
457
|
-
6. Context fields (iccid, account_id, reseller_id) from the routing context take precedence.`;
|
|
458
|
-
// ---------------------------------------------------------------------------
|
|
459
|
-
// Default Bedrock config
|
|
460
|
-
// ---------------------------------------------------------------------------
|
|
461
|
-
const DEFAULT_REGION = "us-east-1";
|
|
462
|
-
const DEFAULT_MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
463
|
-
// ---------------------------------------------------------------------------
|
|
464
|
-
// callBedrock — SigV4-signed InvokeModel via aws4fetch.
|
|
465
|
-
// Exported for testing (mock globalThis.fetch or AwsClient in tests).
|
|
466
|
-
// ---------------------------------------------------------------------------
|
|
467
|
-
export async function callBedrock(env, payload) {
|
|
468
|
-
const region = env.AWS_REGION ?? DEFAULT_REGION;
|
|
469
|
-
const modelId = env.BEDROCK_MODEL_ID ?? DEFAULT_MODEL_ID;
|
|
470
|
-
const aws = new AwsClient({
|
|
471
|
-
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
472
|
-
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
|
473
|
-
region,
|
|
474
|
-
service: "bedrock",
|
|
475
|
-
});
|
|
476
|
-
const url = `https://bedrock-runtime.${region}.amazonaws.com/model/${encodeURIComponent(modelId)}/invoke`;
|
|
477
|
-
const resp = await aws.fetch(url, {
|
|
478
|
-
method: "POST",
|
|
479
|
-
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
480
|
-
body: JSON.stringify(payload),
|
|
481
|
-
});
|
|
482
|
-
if (!resp.ok) {
|
|
483
|
-
const body = await resp.text();
|
|
484
|
-
// 429 → surface as a structured rate-limit signal
|
|
485
|
-
if (resp.status === 429) {
|
|
486
|
-
const retryAfter = resp.headers.get("retry-after");
|
|
487
|
-
const err = new Error(`Bedrock rate limit: ${body}`);
|
|
488
|
-
err.isRateLimit = true;
|
|
489
|
-
err.retryAfter = retryAfter;
|
|
490
|
-
throw err;
|
|
491
|
-
}
|
|
492
|
-
throw new Error(`Bedrock invoke failed: ${resp.status} ${body}`);
|
|
493
|
-
}
|
|
494
|
-
return (await resp.json());
|
|
495
|
-
}
|
|
496
|
-
// ---------------------------------------------------------------------------
|
|
497
|
-
// _routeIntent — core routing logic via Claude Haiku 4.5 on Bedrock.
|
|
498
|
-
// Exported for testing (mock callBedrock or globalThis.fetch in tests).
|
|
499
|
-
// ---------------------------------------------------------------------------
|
|
500
|
-
export async function _routeIntent(intent, context, env) {
|
|
501
|
-
// Feature flag gate
|
|
502
|
-
if (env.CARRIER_ASK_ENABLED !== "true" || !env.AWS_ACCESS_KEY_ID || !env.AWS_SECRET_ACCESS_KEY) {
|
|
503
|
-
return {
|
|
504
|
-
match: "routing_pending",
|
|
505
|
-
intent_received: intent,
|
|
506
|
-
note: "carrier_ask routing engine is not yet activated. " +
|
|
507
|
-
"Add AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY to Doppler carrier/dev+stg+prd and set CARRIER_ASK_ENABLED=true.",
|
|
508
|
-
scaffold_version: "v2.0-bedrock",
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
// Merge context into user message (omit keys with undefined — JSON.stringify drops them)
|
|
512
|
-
const definedContext = Object.fromEntries(Object.entries(context).filter(([, value]) => value !== undefined));
|
|
513
|
-
const contextNote = Object.keys(definedContext).length > 0
|
|
514
|
-
? `\n\nPre-resolved context: ${JSON.stringify(definedContext)}`
|
|
515
|
-
: "";
|
|
516
|
-
const userMessage = `${intent}${contextNote}`;
|
|
517
|
-
const payload = {
|
|
518
|
-
anthropic_version: "bedrock-2023-05-31",
|
|
519
|
-
max_tokens: 512,
|
|
520
|
-
system: ROUTER_SYSTEM_PROMPT,
|
|
521
|
-
messages: [{ role: "user", content: userMessage }],
|
|
522
|
-
tools: ROUTER_TOOLS,
|
|
523
|
-
tool_choice: { type: "any" },
|
|
524
|
-
};
|
|
525
|
-
let response;
|
|
526
|
-
try {
|
|
527
|
-
response = await callBedrock({
|
|
528
|
-
AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID,
|
|
529
|
-
AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY,
|
|
530
|
-
AWS_REGION: env.AWS_REGION,
|
|
531
|
-
BEDROCK_MODEL_ID: env.BEDROCK_MODEL_ID,
|
|
532
|
-
}, payload);
|
|
533
|
-
}
|
|
534
|
-
catch (err) {
|
|
535
|
-
if (err instanceof Error && err.isRateLimit) {
|
|
536
|
-
const retryAfter = err.retryAfter;
|
|
537
|
-
const parsedSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
|
|
538
|
-
return {
|
|
539
|
-
match: "rate_limited",
|
|
540
|
-
retry_after_seconds: Number.isFinite(parsedSeconds) ? parsedSeconds : 60,
|
|
541
|
-
suggestion: "Bedrock rate limit reached. Please retry after the indicated delay.",
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
throw err;
|
|
545
|
-
}
|
|
546
|
-
// Extract tool_use block
|
|
547
|
-
const toolUseBlock = response.content.find((block) => block.type === "tool_use");
|
|
548
|
-
if (!toolUseBlock) {
|
|
549
|
-
return {
|
|
550
|
-
match: "none",
|
|
551
|
-
closest: [],
|
|
552
|
-
suggestion: "Could not determine the right tool for this intent. Try rephrasing or use carrier_ask_describe to explore available tools.",
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
const { name: resolvedTool, input } = toolUseBlock;
|
|
556
|
-
const resolvedParams = (input ?? {});
|
|
557
|
-
// Clarification sentinel
|
|
558
|
-
if (resolvedTool === "carrier_clarify") {
|
|
559
|
-
const candidates = (resolvedParams.candidates ?? []);
|
|
560
|
-
const clarifyingQuestion = typeof resolvedParams.clarifying_question === "string"
|
|
561
|
-
? resolvedParams.clarifying_question
|
|
562
|
-
: "Could you clarify your intent?";
|
|
563
|
-
return {
|
|
564
|
-
match: "ambiguous",
|
|
565
|
-
candidates,
|
|
566
|
-
clarifying_question: clarifyingQuestion,
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
// Hallucination guard
|
|
570
|
-
if (!TOOL_REGISTRY.has(resolvedTool)) {
|
|
571
|
-
return {
|
|
572
|
-
match: "none",
|
|
573
|
-
closest: [],
|
|
574
|
-
suggestion: `Router returned unknown tool '${resolvedTool}'. Please try rephrasing your intent.`,
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
// HARD_BLOCK: require confirm_token
|
|
578
|
-
if (HARD_BLOCK_TOOLS.has(resolvedTool)) {
|
|
579
|
-
return {
|
|
580
|
-
match: "pending_confirm",
|
|
581
|
-
resolved_tool: resolvedTool,
|
|
582
|
-
resolved_params: resolvedParams,
|
|
583
|
-
confidence: 0.95,
|
|
584
|
-
confirm_required: true,
|
|
585
|
-
confirm_token: "", // filled by caller after storeConfirmToken
|
|
586
|
-
confirm_expires_in_seconds: CONFIRM_TOKEN_TTL_SECONDS,
|
|
587
|
-
safety_note: `'${resolvedTool}' is a protected destructive operation. ` +
|
|
588
|
-
"Re-call carrier_ask with the returned confirm_token to execute.",
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
// Safe read/write tool
|
|
592
|
-
return {
|
|
593
|
-
match: "confirmed",
|
|
594
|
-
resolved_tool: resolvedTool,
|
|
595
|
-
resolved_params: resolvedParams,
|
|
596
|
-
confidence: 0.95,
|
|
597
|
-
confirm_required: false,
|
|
598
|
-
execution_note: `Call '${resolvedTool}' directly with resolved_params to execute. ` +
|
|
599
|
-
"carrier_ask does not auto-execute — the caller makes the explicit tool call.",
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
// ---------------------------------------------------------------------------
|
|
603
|
-
// Audit log write for carrier_ask routing events.
|
|
604
|
-
// ---------------------------------------------------------------------------
|
|
605
|
-
function writeCarrierAskAudit(env, row) {
|
|
606
|
-
try {
|
|
607
|
-
env.AUDIT_LOG.writeDataPoint({
|
|
608
|
-
blobs: [
|
|
609
|
-
"carrier_ask", // blob[0] tool_name
|
|
610
|
-
row.resolved_tool, // blob[1] resolved tool (or "none")
|
|
611
|
-
row.status, // blob[2] ok | error
|
|
612
|
-
row.match, // blob[3] match type
|
|
613
|
-
row.intent_hash, // blob[4] SHA-256 of intent
|
|
614
|
-
row.confirm_token_state, // blob[5] confirm token state
|
|
615
|
-
row.sub, // blob[6] user subject
|
|
616
|
-
],
|
|
617
|
-
doubles: [row.latency_ms],
|
|
618
|
-
indexes: [String(row.reseller_id)],
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
catch {
|
|
622
|
-
// Never let audit failure propagate
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
// ---------------------------------------------------------------------------
|
|
626
|
-
// registerAllCarrierAskTools — registers carrier_ask + carrier_ask_describe
|
|
627
|
-
// ---------------------------------------------------------------------------
|
|
628
|
-
export function registerAllCarrierAskTools(server, ctx) {
|
|
629
|
-
// =========================================================================
|
|
630
|
-
// carrier_ask — natural-language router
|
|
631
|
-
// =========================================================================
|
|
632
|
-
server.registerTool("carrier_ask", {
|
|
633
|
-
title: "Natural Language Carrier Tool Router",
|
|
634
|
-
description: "Use this when you want to perform a carrier operation but don't know which specific tool to call. " +
|
|
635
|
-
"Describe your intent in plain language and carrier_ask will identify the correct tool(s) and " +
|
|
636
|
-
"suggest the required parameters. For simple read intents, it executes directly and returns results. " +
|
|
637
|
-
"For destructive operations it returns a confirm_token that you must pass in a second call. " +
|
|
638
|
-
"Params: `intent` (string — natural language description of what you want to do), " +
|
|
639
|
-
"`context` (optional: iccid, account_id, reseller_id if already known). " +
|
|
640
|
-
"Returns: RouteResult — confirmed | pending_confirm | ambiguous | none. " +
|
|
641
|
-
"Do NOT use this when you know the right tool — direct calls are faster and cheaper.",
|
|
642
|
-
inputSchema: {
|
|
643
|
-
intent: z
|
|
644
|
-
.string()
|
|
645
|
-
.min(3)
|
|
646
|
-
.describe("Natural-language description of the operation to perform " +
|
|
647
|
-
"(e.g. 'suspend ICCID 89316 until next month', 'show fleet health', " +
|
|
648
|
-
"'assign Europe 5GB package to subscriber 89316...')"),
|
|
649
|
-
context: z
|
|
650
|
-
.object({
|
|
651
|
-
iccid: z.string().optional().describe("Subscriber ICCID if already known"),
|
|
652
|
-
account_id: z.number().optional().describe("Account ID if already known"),
|
|
653
|
-
reseller_id: z.number().optional().describe("Reseller ID if already known"),
|
|
654
|
-
})
|
|
655
|
-
.optional()
|
|
656
|
-
.describe("Optional pre-resolved context to improve routing accuracy"),
|
|
657
|
-
confirm_token: z
|
|
658
|
-
.string()
|
|
659
|
-
.optional()
|
|
660
|
-
.describe("One-time token from a previous carrier_ask call with confirm_required=true. " +
|
|
661
|
-
"Providing this executes the previously staged destructive operation."),
|
|
662
|
-
},
|
|
663
|
-
}, wrapHandler("carrier_ask", "[carrier_ask]", "read", ctx, async (args, _token) => {
|
|
664
|
-
const { intent, context = {}, confirm_token } = args;
|
|
665
|
-
const intentHash = hashIntent(intent);
|
|
666
|
-
const start = Date.now();
|
|
667
|
-
// ------------------------------------------------------------------
|
|
668
|
-
// Confirm token redemption path
|
|
669
|
-
// ------------------------------------------------------------------
|
|
670
|
-
if (confirm_token) {
|
|
671
|
-
const staged = await consumeConfirmToken(ctx.env.OAUTH_KV, confirm_token);
|
|
672
|
-
if (!staged) {
|
|
673
|
-
writeCarrierAskAudit(ctx.env, {
|
|
674
|
-
intent_hash: intentHash,
|
|
675
|
-
match: "confirm_expired",
|
|
676
|
-
resolved_tool: "none",
|
|
677
|
-
confirm_token_state: "expired",
|
|
678
|
-
status: "error",
|
|
679
|
-
latency_ms: Date.now() - start,
|
|
680
|
-
sub: ctx.props.sub,
|
|
681
|
-
reseller_id: ctx.props.reseller_id,
|
|
682
|
-
});
|
|
683
|
-
return {
|
|
684
|
-
content: [
|
|
685
|
-
{
|
|
686
|
-
type: "text",
|
|
687
|
-
text: JSON.stringify({
|
|
688
|
-
error: "confirm_token_expired",
|
|
689
|
-
message: `Confirm token '${confirm_token}' has expired or was already used. ` +
|
|
690
|
-
`Tokens expire after ${CONFIRM_TOKEN_TTL_SECONDS}s. Re-issue the original intent to get a new token.`,
|
|
691
|
-
}),
|
|
692
|
-
},
|
|
693
|
-
],
|
|
694
|
-
isError: true,
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
const resolvedTool = staged.resolved_tool;
|
|
698
|
-
if (!TOOL_REGISTRY.has(resolvedTool)) {
|
|
699
|
-
return {
|
|
700
|
-
content: [
|
|
701
|
-
{
|
|
702
|
-
type: "text",
|
|
703
|
-
text: JSON.stringify({
|
|
704
|
-
error: "invalid_tool",
|
|
705
|
-
message: `Staged tool '${resolvedTool}' is not in the tool registry.`,
|
|
706
|
-
}),
|
|
707
|
-
},
|
|
708
|
-
],
|
|
709
|
-
isError: true,
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
writeCarrierAskAudit(ctx.env, {
|
|
713
|
-
intent_hash: typeof staged.intent_hash === "string" ? staged.intent_hash : intentHash,
|
|
714
|
-
match: "confirm_executed",
|
|
715
|
-
resolved_tool: resolvedTool,
|
|
716
|
-
confirm_token_state: "redeemed",
|
|
717
|
-
status: "ok",
|
|
718
|
-
latency_ms: Date.now() - start,
|
|
719
|
-
sub: ctx.props.sub,
|
|
720
|
-
reseller_id: ctx.props.reseller_id,
|
|
721
|
-
});
|
|
722
|
-
return {
|
|
723
|
-
content: [
|
|
724
|
-
{
|
|
725
|
-
type: "text",
|
|
726
|
-
text: JSON.stringify({
|
|
727
|
-
match: "confirm_executed",
|
|
728
|
-
resolved_tool: resolvedTool,
|
|
729
|
-
resolved_params: staged.resolved_params,
|
|
730
|
-
note: "Confirm token accepted. Call the resolved_tool directly with resolved_params " +
|
|
731
|
-
"to execute the operation. carrier_ask does not auto-execute — the caller must " +
|
|
732
|
-
"make the explicit tool call.",
|
|
733
|
-
}),
|
|
734
|
-
},
|
|
735
|
-
],
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
// ------------------------------------------------------------------
|
|
739
|
-
// Main routing path
|
|
740
|
-
// ------------------------------------------------------------------
|
|
741
|
-
let route;
|
|
742
|
-
try {
|
|
743
|
-
route = await _routeIntent(intent, context, ctx.env);
|
|
744
|
-
}
|
|
745
|
-
catch (err) {
|
|
746
|
-
writeCarrierAskAudit(ctx.env, {
|
|
747
|
-
intent_hash: intentHash,
|
|
748
|
-
match: "error",
|
|
749
|
-
resolved_tool: "none",
|
|
750
|
-
confirm_token_state: "none",
|
|
751
|
-
status: "error",
|
|
752
|
-
latency_ms: Date.now() - start,
|
|
753
|
-
sub: ctx.props.sub,
|
|
754
|
-
reseller_id: ctx.props.reseller_id,
|
|
755
|
-
});
|
|
756
|
-
return {
|
|
757
|
-
content: [
|
|
758
|
-
{
|
|
759
|
-
type: "text",
|
|
760
|
-
text: JSON.stringify({
|
|
761
|
-
error: "routing_error",
|
|
762
|
-
message: err instanceof Error
|
|
763
|
-
? err.message
|
|
764
|
-
: "An unexpected error occurred during routing.",
|
|
765
|
-
}),
|
|
766
|
-
},
|
|
767
|
-
],
|
|
768
|
-
isError: true,
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
// Issue confirm token for HARD_BLOCK results
|
|
772
|
-
let finalRoute = route;
|
|
773
|
-
let confirmTokenState = "none";
|
|
774
|
-
if (route.match === "pending_confirm") {
|
|
775
|
-
const token = generateConfirmToken();
|
|
776
|
-
await storeConfirmToken(ctx.env.OAUTH_KV, token, {
|
|
777
|
-
resolved_tool: route.resolved_tool,
|
|
778
|
-
resolved_params: route.resolved_params,
|
|
779
|
-
intent_hash: intentHash,
|
|
780
|
-
issued_at: new Date().toISOString(),
|
|
781
|
-
});
|
|
782
|
-
finalRoute = { ...route, confirm_token: token };
|
|
783
|
-
confirmTokenState = "issued";
|
|
784
|
-
}
|
|
785
|
-
const resolvedToolForAudit = (route.match === "confirmed" || route.match === "pending_confirm")
|
|
786
|
-
? route.resolved_tool
|
|
787
|
-
: "none";
|
|
788
|
-
writeCarrierAskAudit(ctx.env, {
|
|
789
|
-
intent_hash: intentHash,
|
|
790
|
-
match: route.match,
|
|
791
|
-
resolved_tool: resolvedToolForAudit,
|
|
792
|
-
confirm_token_state: confirmTokenState,
|
|
793
|
-
status: "ok",
|
|
794
|
-
latency_ms: Date.now() - start,
|
|
795
|
-
sub: ctx.props.sub,
|
|
796
|
-
reseller_id: ctx.props.reseller_id,
|
|
797
|
-
});
|
|
798
|
-
return {
|
|
799
|
-
content: [
|
|
800
|
-
{
|
|
801
|
-
type: "text",
|
|
802
|
-
text: JSON.stringify(finalRoute, null, 2),
|
|
803
|
-
},
|
|
804
|
-
],
|
|
805
|
-
};
|
|
806
|
-
}));
|
|
807
|
-
// =========================================================================
|
|
808
|
-
// carrier_ask_describe — drill-down companion
|
|
809
|
-
// =========================================================================
|
|
810
|
-
server.registerTool("carrier_ask_describe", {
|
|
811
|
-
title: "Describe a Carrier Tool",
|
|
812
|
-
description: "Get full documentation for any registered Carrier MCP tool: description, " +
|
|
813
|
-
"parameters, 2-3 example invocations, required scope, destructive flag, and guidance. " +
|
|
814
|
-
"Params: `tool_name` (exact MCP tool name, e.g. 'assign_package'). " +
|
|
815
|
-
"Returns: structured tool metadata. Does NOT execute the tool.",
|
|
816
|
-
inputSchema: {
|
|
817
|
-
tool_name: z
|
|
818
|
-
.string()
|
|
819
|
-
.describe("The exact MCP tool name to describe (e.g. 'assign_package', 'hlr_set_bitrate')"),
|
|
820
|
-
},
|
|
821
|
-
}, wrapHandler("carrier_ask_describe", "[carrier_ask_describe]", "read", ctx, async ({ tool_name }, _token) => {
|
|
822
|
-
if (!TOOL_REGISTRY.has(tool_name)) {
|
|
823
|
-
const closest = [...TOOL_REGISTRY]
|
|
824
|
-
.filter((name) => name.includes(tool_name.split("_")[0] ?? "") || tool_name.includes(name.split("_")[0] ?? ""))
|
|
825
|
-
.slice(0, 5);
|
|
826
|
-
return {
|
|
827
|
-
content: [
|
|
828
|
-
{
|
|
829
|
-
type: "text",
|
|
830
|
-
text: JSON.stringify({
|
|
831
|
-
error: "tool_not_found",
|
|
832
|
-
tool_name,
|
|
833
|
-
message: `'${tool_name}' is not a registered Carrier MCP tool.`,
|
|
834
|
-
did_you_mean: closest.length > 0 ? closest : undefined,
|
|
835
|
-
total_registered: TOOL_REGISTRY.size,
|
|
836
|
-
}),
|
|
837
|
-
},
|
|
838
|
-
],
|
|
839
|
-
isError: true,
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
const allScopes = { ...TOOL_SCOPES, ...BACKLOG_TOOL_SCOPES };
|
|
843
|
-
const scope = allScopes[tool_name] ?? "read";
|
|
844
|
-
const isDestructive = DESTRUCTIVE_TOOLS.has(tool_name);
|
|
845
|
-
const isHardBlock = HARD_BLOCK_TOOLS.has(tool_name);
|
|
846
|
-
const doc = {
|
|
847
|
-
tool_name,
|
|
848
|
-
scope,
|
|
849
|
-
destructive: isDestructive,
|
|
850
|
-
hard_block: isHardBlock,
|
|
851
|
-
hard_block_note: isHardBlock
|
|
852
|
-
? "This tool is in the HARD_BLOCK list: carrier_ask will never auto-execute it. " +
|
|
853
|
-
"It always requires an explicit confirm_token redemption."
|
|
854
|
-
: undefined,
|
|
855
|
-
dry_run_supported: isDestructive,
|
|
856
|
-
examples: buildExamples(tool_name),
|
|
857
|
-
note: "Full parameter descriptions are available in the tool's inputSchema. " +
|
|
858
|
-
"Call the tool with no arguments to trigger the MCP schema introspection response.",
|
|
859
|
-
};
|
|
860
|
-
return {
|
|
861
|
-
content: [
|
|
862
|
-
{
|
|
863
|
-
type: "text",
|
|
864
|
-
text: JSON.stringify(doc, null, 2),
|
|
865
|
-
},
|
|
866
|
-
],
|
|
867
|
-
};
|
|
868
|
-
}));
|
|
869
|
-
}
|
|
870
|
-
// ---------------------------------------------------------------------------
|
|
871
|
-
// buildExamples — curated invocation examples for high-traffic tools.
|
|
872
|
-
// ---------------------------------------------------------------------------
|
|
873
|
-
function buildExamples(toolName) {
|
|
874
|
-
const examples = {
|
|
875
|
-
assign_package: [
|
|
876
|
-
{ intent: "Give subscriber 89316... the Europe 5GB package", params: { iccid: "89316...", packageTemplateId: 42 } },
|
|
877
|
-
{ intent: "Assign a one-time data package to this eSIM", params: { iccid: "89316...", packageTemplateId: 42 } },
|
|
878
|
-
],
|
|
879
|
-
assign_recurring_package: [
|
|
880
|
-
{ intent: "Set up monthly auto-renewal for subscriber 89316...", params: { iccid: "89316...", packageTemplateId: 55 } },
|
|
881
|
-
],
|
|
882
|
-
modify_subscriber_status: [
|
|
883
|
-
{ intent: "Pause Maya's subscription for 2 months", params: { iccid: "89316...", status: "SUSPENDED" } },
|
|
884
|
-
{ intent: "Reactivate subscriber 89316...", params: { iccid: "89316...", status: "ACTIVE" } },
|
|
885
|
-
],
|
|
886
|
-
hlr_set_bitrate: [
|
|
887
|
-
{ intent: "Throttle ICCID 89316... to 256kbps", params: { iccid: "89316...", bitrate: 256000 } },
|
|
888
|
-
{ intent: "Remove speed cap from subscriber 89316...", params: { iccid: "89316...", bitrate: 0 } },
|
|
889
|
-
],
|
|
890
|
-
modify_subscriber_steering_list: [
|
|
891
|
-
{ intent: "Connect this eSIM to the best network in Italy", params: { iccid: "89316...", steeringListId: 7 } },
|
|
892
|
-
],
|
|
893
|
-
push_steering_to_subscriber: [
|
|
894
|
-
{ intent: "Push the new steering config to the device", params: { iccid: "89316..." } },
|
|
895
|
-
],
|
|
896
|
-
clean_all_packages: [
|
|
897
|
-
{ intent: "Reset all packages for subscriber 89316... before reprovisioning", params: { iccid: "89316...", dry_run: true } },
|
|
898
|
-
],
|
|
899
|
-
fleet_health: [
|
|
900
|
-
{ intent: "Show me the fleet status overview", params: {} },
|
|
901
|
-
{ intent: "How many eSIMs are active right now", params: {} },
|
|
902
|
-
],
|
|
903
|
-
diagnose_subscriber: [
|
|
904
|
-
{ intent: "Why is subscriber 89316... offline?", params: { iccid: "89316..." } },
|
|
905
|
-
],
|
|
906
|
-
};
|
|
907
|
-
return examples[toolName] ?? [
|
|
908
|
-
{ intent: `Call ${toolName} for subscriber 89316...`, params: { iccid: "89316..." } },
|
|
909
|
-
];
|
|
910
|
-
}
|
|
911
|
-
// Export for TOOL_INVENTORY generation and reconcile script
|
|
912
|
-
export { TOOL_REGISTRY, HARD_BLOCK_TOOLS };
|
|
913
|
-
//# sourceMappingURL=tools-carrier-ask.js.map
|