@carrierllc/sdk 0.1.0 → 0.2.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/README.md +22 -2
- package/dist/adapters.d.ts +22 -0
- package/dist/adapters.js +83 -0
- package/dist/catalog.d.ts +44 -0
- package/dist/catalog.js +45 -0
- package/dist/cli.d.ts +30 -0
- package/dist/cli.js +70 -0
- package/dist/client.d.ts +8 -2
- package/dist/client.js +35 -12
- package/dist/data/cli.json +2807 -0
- package/dist/data/operations.json +2534 -0
- package/dist/idempotency.d.ts +2 -0
- package/dist/idempotency.js +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/mcp.d.ts +41 -0
- package/dist/mcp.js +173 -0
- package/dist/operation-ids.d.ts +5 -0
- package/dist/operation-ids.js +1 -0
- package/dist/operations.d.ts +22 -0
- package/dist/operations.js +177 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +3 -0
- package/package.json +3 -3
package/dist/idempotency.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
/** Same limits as the API, so a bad key fails before a request is sent. */
|
|
2
2
|
export declare function requireIdempotencyKey(key: string | undefined): string;
|
|
3
|
+
/** Reject a path segment that could escape the resource URL. */
|
|
4
|
+
export declare function requirePathId(value: string, label: string): string;
|
package/dist/idempotency.js
CHANGED
|
@@ -15,3 +15,10 @@ export function requireIdempotencyKey(key) {
|
|
|
15
15
|
}
|
|
16
16
|
return key;
|
|
17
17
|
}
|
|
18
|
+
/** Reject a path segment that could escape the resource URL. */
|
|
19
|
+
export function requirePathId(value, label) {
|
|
20
|
+
if (value.trim() === "" || /[/\\?#]/.test(value) || value.includes("..")) {
|
|
21
|
+
throw new CarrierError(400, "invalid_id", `${label} is not a valid id.`);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export { cliTools, mcpTools, restTools, type CarrierTool } from "./adapters.js";
|
|
2
|
+
export { cliCommands, operations, type CliCommand, type Operation } from "./catalog.js";
|
|
3
|
+
export { Cli, type CliOptions, type CliResult, type CliRunner } from "./cli.js";
|
|
1
4
|
export { Carrier, type CarrierOptions } from "./client.js";
|
|
2
5
|
export { CarrierError } from "./error.js";
|
|
6
|
+
export { DEFAULT_MCP_URL, Mcp, PROTOCOL_VERSION, type McpOptions } from "./mcp.js";
|
|
7
|
+
export type { OperationId } from "./operation-ids.js";
|
|
8
|
+
export type { CallParams } from "./operations.js";
|
|
9
|
+
export { SDK_VERSION } from "./version.js";
|
|
3
10
|
export type { ConnectivityEsim, ConnectivityIntent, ConnectivityPlan, IdempotentRequest, IntentCreateParams, IntentStatus, PlanPrice, ProductCreateParams, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
export { cliTools, mcpTools, restTools } from "./adapters.js";
|
|
2
|
+
export { cliCommands, operations } from "./catalog.js";
|
|
3
|
+
export { Cli } from "./cli.js";
|
|
1
4
|
export { Carrier } from "./client.js";
|
|
2
5
|
export { CarrierError } from "./error.js";
|
|
6
|
+
export { DEFAULT_MCP_URL, Mcp, PROTOCOL_VERSION } from "./mcp.js";
|
|
7
|
+
export { SDK_VERSION } from "./version.js";
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP client for the hosted Carrier MCP server.
|
|
3
|
+
*
|
|
4
|
+
* `auth: "token"` sends an eSIMVault reseller token as `?TOKEN=`.
|
|
5
|
+
* `auth: "bearer"` sends an OAuth access token or a Clerk organization API key.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_MCP_URL = "https://mcp.carrier.llc/mcp";
|
|
8
|
+
export declare const PROTOCOL_VERSION = "2025-11-25";
|
|
9
|
+
export interface McpOptions {
|
|
10
|
+
/** Defaults to https://mcp.carrier.llc/mcp. */
|
|
11
|
+
url?: string;
|
|
12
|
+
/** `token` uses ?TOKEN=. `bearer` uses Authorization. Defaults to token. */
|
|
13
|
+
auth?: "token" | "bearer";
|
|
14
|
+
/** Override fetch in tests. */
|
|
15
|
+
fetch?: typeof fetch;
|
|
16
|
+
}
|
|
17
|
+
export declare class Mcp {
|
|
18
|
+
private readonly token;
|
|
19
|
+
private readonly url;
|
|
20
|
+
private readonly auth;
|
|
21
|
+
private readonly fetchImpl;
|
|
22
|
+
private sessionId;
|
|
23
|
+
private protocol;
|
|
24
|
+
private connected;
|
|
25
|
+
private nextId;
|
|
26
|
+
constructor(token: string, options?: McpOptions);
|
|
27
|
+
/** Initialize a session and send notifications/initialized. */
|
|
28
|
+
connect(): Promise<Record<string, unknown>>;
|
|
29
|
+
/** Return the tools advertised for this session. */
|
|
30
|
+
listTools(): Promise<unknown[]>;
|
|
31
|
+
/** Call one MCP tool and return the tool result object. */
|
|
32
|
+
callTool(name: string, args?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
33
|
+
/** Call any MCP method. The session is opened on the first call. */
|
|
34
|
+
request(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
35
|
+
private ensureConnected;
|
|
36
|
+
private notify;
|
|
37
|
+
private rpc;
|
|
38
|
+
private send;
|
|
39
|
+
private endpoint;
|
|
40
|
+
private headers;
|
|
41
|
+
}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP client for the hosted Carrier MCP server.
|
|
3
|
+
*
|
|
4
|
+
* `auth: "token"` sends an eSIMVault reseller token as `?TOKEN=`.
|
|
5
|
+
* `auth: "bearer"` sends an OAuth access token or a Clerk organization API key.
|
|
6
|
+
*/
|
|
7
|
+
import { CarrierError } from "./error.js";
|
|
8
|
+
import { SDK_VERSION, USER_AGENT } from "./version.js";
|
|
9
|
+
export const DEFAULT_MCP_URL = "https://mcp.carrier.llc/mcp";
|
|
10
|
+
export const PROTOCOL_VERSION = "2025-11-25";
|
|
11
|
+
const ACCEPTED_PROTOCOL_VERSIONS = new Set(["2025-11-25", "2025-06-18", "2025-03-26"]);
|
|
12
|
+
export class Mcp {
|
|
13
|
+
token;
|
|
14
|
+
url;
|
|
15
|
+
auth;
|
|
16
|
+
fetchImpl;
|
|
17
|
+
sessionId;
|
|
18
|
+
protocol;
|
|
19
|
+
connected = false;
|
|
20
|
+
nextId = 1;
|
|
21
|
+
constructor(token, options = {}) {
|
|
22
|
+
if (token.trim() === "")
|
|
23
|
+
throw new CarrierError(400, "invalid_token", "An MCP token is required.");
|
|
24
|
+
const auth = options.auth ?? "token";
|
|
25
|
+
if (auth !== "token" && auth !== "bearer") {
|
|
26
|
+
throw new CarrierError(400, "invalid_auth", "MCP auth must be token or bearer.");
|
|
27
|
+
}
|
|
28
|
+
this.token = token;
|
|
29
|
+
this.url = options.url ?? DEFAULT_MCP_URL;
|
|
30
|
+
this.auth = auth;
|
|
31
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
32
|
+
}
|
|
33
|
+
/** Initialize a session and send notifications/initialized. */
|
|
34
|
+
async connect() {
|
|
35
|
+
const result = await this.rpc("initialize", {
|
|
36
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
37
|
+
capabilities: { tools: {} },
|
|
38
|
+
clientInfo: { name: "@carrierllc/sdk", version: SDK_VERSION },
|
|
39
|
+
}, true);
|
|
40
|
+
if (!isRecord(result))
|
|
41
|
+
throw new CarrierError(200, "invalid_response", "MCP initialize did not return an object.");
|
|
42
|
+
const version = result.protocolVersion;
|
|
43
|
+
if (typeof version !== "string" || !ACCEPTED_PROTOCOL_VERSIONS.has(version)) {
|
|
44
|
+
throw new CarrierError(400, "unsupported_protocol", `MCP server negotiated ${String(version)}.`);
|
|
45
|
+
}
|
|
46
|
+
this.protocol = version;
|
|
47
|
+
await this.notify("notifications/initialized");
|
|
48
|
+
this.connected = true;
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
/** Return the tools advertised for this session. */
|
|
52
|
+
async listTools() {
|
|
53
|
+
await this.ensureConnected();
|
|
54
|
+
const result = await this.rpc("tools/list", {});
|
|
55
|
+
if (!isRecord(result) || !Array.isArray(result.tools)) {
|
|
56
|
+
throw new CarrierError(200, "invalid_response", "MCP tools/list did not return tools.");
|
|
57
|
+
}
|
|
58
|
+
return result.tools;
|
|
59
|
+
}
|
|
60
|
+
/** Call one MCP tool and return the tool result object. */
|
|
61
|
+
async callTool(name, args = {}) {
|
|
62
|
+
await this.ensureConnected();
|
|
63
|
+
if (name.trim() === "")
|
|
64
|
+
throw new CarrierError(400, "invalid_tool", "An MCP tool name is required.");
|
|
65
|
+
const result = await this.rpc("tools/call", { name, arguments: args });
|
|
66
|
+
if (!isRecord(result))
|
|
67
|
+
throw new CarrierError(200, "invalid_response", "MCP tools/call did not return an object.");
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/** Call any MCP method. The session is opened on the first call. */
|
|
71
|
+
async request(method, params = {}) {
|
|
72
|
+
if (method !== "initialize")
|
|
73
|
+
await this.ensureConnected();
|
|
74
|
+
return this.rpc(method, params);
|
|
75
|
+
}
|
|
76
|
+
async ensureConnected() {
|
|
77
|
+
if (!this.connected)
|
|
78
|
+
await this.connect();
|
|
79
|
+
}
|
|
80
|
+
async notify(method) {
|
|
81
|
+
await this.send({ jsonrpc: "2.0", method }, true);
|
|
82
|
+
}
|
|
83
|
+
async rpc(method, params, handshake = false) {
|
|
84
|
+
const id = this.nextId;
|
|
85
|
+
this.nextId += 1;
|
|
86
|
+
return this.send({ jsonrpc: "2.0", id, method, params }, false, handshake);
|
|
87
|
+
}
|
|
88
|
+
async send(message, notify = false, handshake = false) {
|
|
89
|
+
let response;
|
|
90
|
+
try {
|
|
91
|
+
response = await this.fetchImpl(this.endpoint(), {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: this.headers(),
|
|
94
|
+
body: JSON.stringify(message),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const text = error instanceof Error ? error.message : "Network request failed.";
|
|
99
|
+
throw new CarrierError(0, "network_error", text, "api_error");
|
|
100
|
+
}
|
|
101
|
+
if (handshake)
|
|
102
|
+
this.sessionId = response.headers.get("mcp-session-id") ?? this.sessionId;
|
|
103
|
+
const raw = await response.text();
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
throw new CarrierError(response.status, "mcp_error", raw.trim() === "" ? "MCP request failed." : raw.slice(0, 300), "api_error");
|
|
106
|
+
}
|
|
107
|
+
if (notify && raw.trim() === "")
|
|
108
|
+
return undefined;
|
|
109
|
+
return decodeRpc(raw, response.headers.get("content-type") ?? "");
|
|
110
|
+
}
|
|
111
|
+
endpoint() {
|
|
112
|
+
if (this.auth !== "token")
|
|
113
|
+
return this.url;
|
|
114
|
+
const url = new URL(this.url);
|
|
115
|
+
url.searchParams.set("TOKEN", this.token);
|
|
116
|
+
return url.toString();
|
|
117
|
+
}
|
|
118
|
+
headers() {
|
|
119
|
+
const headers = {
|
|
120
|
+
"Content-Type": "application/json",
|
|
121
|
+
Accept: "application/json, text/event-stream",
|
|
122
|
+
"User-Agent": USER_AGENT,
|
|
123
|
+
};
|
|
124
|
+
if (this.auth === "bearer")
|
|
125
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
126
|
+
if (this.sessionId)
|
|
127
|
+
headers["Mcp-Session-Id"] = this.sessionId;
|
|
128
|
+
if (this.protocol)
|
|
129
|
+
headers["MCP-Protocol-Version"] = this.protocol;
|
|
130
|
+
return headers;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function decodeRpc(raw, contentType) {
|
|
134
|
+
if (raw.trim() === "")
|
|
135
|
+
return undefined;
|
|
136
|
+
if (contentType.includes("text/event-stream"))
|
|
137
|
+
return decodeSse(raw);
|
|
138
|
+
let parsed;
|
|
139
|
+
try {
|
|
140
|
+
parsed = JSON.parse(raw);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new CarrierError(200, "invalid_response", "MCP returned a body that is not JSON.");
|
|
144
|
+
}
|
|
145
|
+
if (!isRecord(parsed))
|
|
146
|
+
throw new CarrierError(200, "invalid_response", "MCP returned a body that is not an object.");
|
|
147
|
+
return rpcPayload(parsed);
|
|
148
|
+
}
|
|
149
|
+
function decodeSse(raw) {
|
|
150
|
+
for (const block of raw.split("\n\n")) {
|
|
151
|
+
for (const line of block.split("\n")) {
|
|
152
|
+
if (!line.startsWith("data:"))
|
|
153
|
+
continue;
|
|
154
|
+
const data = line.slice(5).trim();
|
|
155
|
+
if (data === "" || data === "[DONE]")
|
|
156
|
+
continue;
|
|
157
|
+
const parsed = JSON.parse(data);
|
|
158
|
+
if (isRecord(parsed) && ("result" in parsed || "error" in parsed))
|
|
159
|
+
return rpcPayload(parsed);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw new CarrierError(200, "invalid_response", "MCP event stream did not include a result.");
|
|
163
|
+
}
|
|
164
|
+
function rpcPayload(message) {
|
|
165
|
+
if (isRecord(message.error)) {
|
|
166
|
+
const text = message.error.message;
|
|
167
|
+
throw new CarrierError(200, "mcp_error", typeof text === "string" && text !== "" ? text : "MCP request failed.", "api_error");
|
|
168
|
+
}
|
|
169
|
+
return message.result;
|
|
170
|
+
}
|
|
171
|
+
function isRecord(value) {
|
|
172
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
173
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Operation ids from the public OpenAPI document.
|
|
2
|
+
*
|
|
3
|
+
* Generated by sdks/generate.py. Do not edit by hand.
|
|
4
|
+
*/
|
|
5
|
+
export type OperationId = "list_reseller_accounts" | "get_reseller_info" | "esim_status_per_account" | "list_sponsors" | "list_steering_lists" | "get_subscriber" | "list_subscribers" | "create_subscriber" | "get_sim_provider_status" | "change_sim_status" | "get_subscriber_location" | "hlr_get_bitrate" | "hlr_set_bitrate" | "list_subscriber_packages" | "assign_package" | "clean_all_packages" | "list_package_templates" | "create_package_template" | "list_location_zones" | "create_location_zone" | "list_detailed_location_zones" | "list_destination_prefixes" | "subscriber_usage" | "subscriber_network_events" | "subscriber_active_period" | "get_tariff" | "list_network_profiles" | "modify_subscriber_balance" | "modify_subscriber_status" | "modify_subscriber_contact_info" | "set_subscriber_traffic_restrictions" | "modify_subscriber_steering_list" | "move_subscriber_range_to_account" | "assign_recurring_package" | "modify_package_limits" | "modify_package_expiry" | "modify_package_status" | "stop_resume_recurring_package" | "modify_account_balance" | "delete_subscriber_package" | "modify_template_core" | "modify_template_recurring" | "modify_template_throttling" | "send_sms" | "fleet_health" | "churn_risk" | "audit_network_coverage" | "diagnose_subscriber" | "detect_usage_anomalies" | "optimize_package" | "marketing_intelligence" | "high_cost_subscribers" | "high_cost_subscribers_method_guard" | "detect_country_entry" | "list_intelligence_schedules" | "create_intelligence_schedule" | "delete_intelligence_schedule" | "run_intelligence_schedule" | "get_intelligence_schedule_report" | "get_intelligence_share" | "operator_billing_connect_begin" | "operator_billing_connect_oauth_begin" | "operator_billing_connect_oauth_complete" | "operator_billing_connect_status" | "operator_billing_connect_dashboard_link" | "operator_billing_list_payouts" | "operator_billing_create_manual_payout" | "operator_billing_get_payout_schedule" | "operator_billing_update_payout_schedule" | "operator_billing_list_fees" | "operator_billing_list_refunds" | "operator_billing_create_refund" | "operator_billing_list_disputes" | "operator_billing_submit_dispute_evidence" | "operator_billing_list_fraud_reviews" | "operator_billing_approve_fraud_review" | "operator_billing_decline_fraud_review" | "operator_billing_list_value_lists" | "operator_billing_add_value_list_item" | "operator_identity_status" | "operator_identity_create_session" | "operator_identity_update_session" | "operator_list_promotions" | "operator_create_promotion" | "signup_start" | "signup_verify" | "create_api_key" | "get_onboarding_state" | "onboarding_save_org" | "onboarding_connect_ocs_token" | "onboarding_start_managed_account" | "onboarding_save_plan" | "onboarding_skip_plan" | "onboarding_test_esim" | "onboarding_complete" | "operator_billing_stripe_webhook" | "receive_bridge4ip_webhook" | "affect_subscriber_phone_number" | "modify_subscriber_mobile_plan" | "modify_subscriber_voip_plan" | "push_steering_to_subscriber" | "modify_subscriber_package_active_period" | "get_subscriber_location_by_cell_id" | "reset_subscriber_gz_counter" | "list_destination_lists" | "list_subscriber_voip_tariff" | "list_voip_tariff_rule" | "change_network_profile_of_location_zone" | "carrier_webhook_config" | "ai_chat" | "ai_insights" | "ai_quota" | "list_invoice_periods" | "get_billing_config" | "update_billing_config" | "stripe_connect_begin" | "stripe_connect_status" | "list_invoices" | "create_invoice" | "get_invoice" | "update_invoice" | "void_invoice" | "get_invoice_document" | "list_connectivity_plans" | "list_connectivity_products" | "create_connectivity_product" | "get_connectivity_product" | "list_connectivity_intents" | "create_connectivity_intent" | "get_connectivity_intent" | "confirm_connectivity_intent" | "create_connectivity_webhook_endpoint" | "list_connectivity_webhook_endpoints" | "delete_connectivity_webhook_endpoint" | "list_connectivity_events" | "get_connectivity_event" | "create_connectivity_usage_record" | "list_connectivity_usage_records" | "retry_connectivity_event" | "issue_esim_from_inventory" | "rollback_esim_issuance" | "subscriber_country_history" | "get_subscriber_notes" | "add_subscriber_note" | "put_subscriber_notes" | "observe_connectivity_event" | "ingest_connectivity_usage" | "ingest_cdr_events" | "aggregate_cdr" | "list_storefront_templates" | "get_storefront_order" | "health_check";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic caller for every operation id in the public OpenAPI document.
|
|
3
|
+
*/
|
|
4
|
+
import { type Operation } from "./catalog.js";
|
|
5
|
+
export interface CallParams {
|
|
6
|
+
path?: Record<string, string>;
|
|
7
|
+
query?: Record<string, string | number | boolean>;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
body?: unknown;
|
|
10
|
+
idempotencyKey?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PreparedCall {
|
|
13
|
+
method: string;
|
|
14
|
+
path: string;
|
|
15
|
+
query?: Record<string, string>;
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
body?: unknown;
|
|
18
|
+
idempotencyKey?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function prepareCall(operationId: string, params?: CallParams): PreparedCall;
|
|
21
|
+
export declare function inputSchema(spec: Operation): Record<string, unknown>;
|
|
22
|
+
export declare function argumentsToCall(spec: Operation, args: Record<string, unknown>): CallParams;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic caller for every operation id in the public OpenAPI document.
|
|
3
|
+
*/
|
|
4
|
+
import { CarrierError } from "./error.js";
|
|
5
|
+
import { requireIdempotencyKey, requirePathId } from "./idempotency.js";
|
|
6
|
+
import { HEADER_FIELDS, operation } from "./catalog.js";
|
|
7
|
+
const IDEMPOTENCY_HEADERS = new Set(["Idempotency-Key", "X-Idempotency-Key"]);
|
|
8
|
+
export function prepareCall(operationId, params = {}) {
|
|
9
|
+
const spec = operation(operationId);
|
|
10
|
+
const pathValues = params.path ?? {};
|
|
11
|
+
const queryValues = params.query ?? {};
|
|
12
|
+
const headerValues = { ...(params.headers ?? {}) };
|
|
13
|
+
rejectUnknown("path", pathValues, spec.pathParams);
|
|
14
|
+
rejectUnknown("query", queryValues, spec.queryParams);
|
|
15
|
+
rejectUnknown("header", headerValues, spec.headerParams);
|
|
16
|
+
requirePresent("Path", pathValues, spec.pathParams);
|
|
17
|
+
applyIdempotency(spec, headerValues, params.idempotencyKey);
|
|
18
|
+
requireHeaders(headerValues, spec);
|
|
19
|
+
for (const name of Object.keys(headerValues)) {
|
|
20
|
+
if (IDEMPOTENCY_HEADERS.has(name)) {
|
|
21
|
+
headerValues[name] = requireIdempotencyKey(headerValues[name]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (spec.bodyRequired && params.body === undefined) {
|
|
25
|
+
throw new CarrierError(400, "missing_body", "This operation requires a JSON body.");
|
|
26
|
+
}
|
|
27
|
+
if (params.body !== undefined && !spec.body) {
|
|
28
|
+
throw new CarrierError(400, "invalid_body", "This operation does not take a JSON body.");
|
|
29
|
+
}
|
|
30
|
+
const idempotencyKey = headerValues["Idempotency-Key"];
|
|
31
|
+
delete headerValues["Idempotency-Key"];
|
|
32
|
+
const query = {};
|
|
33
|
+
for (const [key, value] of Object.entries(queryValues)) {
|
|
34
|
+
query[key] = queryValue(value);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
method: spec.method,
|
|
38
|
+
path: fillPath(spec.path, pathValues),
|
|
39
|
+
query: Object.keys(query).length > 0 ? query : undefined,
|
|
40
|
+
headers: Object.keys(headerValues).length > 0 ? headerValues : undefined,
|
|
41
|
+
body: spec.body ? params.body : undefined,
|
|
42
|
+
idempotencyKey,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function inputSchema(spec) {
|
|
46
|
+
const properties = {};
|
|
47
|
+
const required = [];
|
|
48
|
+
for (const param of spec.pathParams) {
|
|
49
|
+
properties[param.name] = { type: "string", description: `Path ${param.name}` };
|
|
50
|
+
if (param.required)
|
|
51
|
+
required.push(param.name);
|
|
52
|
+
}
|
|
53
|
+
for (const param of spec.queryParams) {
|
|
54
|
+
properties[param.name] = { type: "string", description: `Query ${param.name}` };
|
|
55
|
+
if (param.required)
|
|
56
|
+
required.push(param.name);
|
|
57
|
+
}
|
|
58
|
+
for (const param of spec.headerParams) {
|
|
59
|
+
const field = HEADER_FIELDS[param.name] ?? param.name;
|
|
60
|
+
properties[field] = { type: "string", description: param.name };
|
|
61
|
+
if (param.required)
|
|
62
|
+
required.push(field);
|
|
63
|
+
}
|
|
64
|
+
if (spec.body) {
|
|
65
|
+
properties.body = { type: "object", additionalProperties: true, description: "JSON request body" };
|
|
66
|
+
if (spec.bodyRequired)
|
|
67
|
+
required.push("body");
|
|
68
|
+
}
|
|
69
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
70
|
+
}
|
|
71
|
+
export function argumentsToCall(spec, args) {
|
|
72
|
+
const known = new Set();
|
|
73
|
+
for (const param of spec.pathParams)
|
|
74
|
+
known.add(param.name);
|
|
75
|
+
for (const param of spec.queryParams)
|
|
76
|
+
known.add(param.name);
|
|
77
|
+
const headerByField = new Map();
|
|
78
|
+
for (const param of spec.headerParams) {
|
|
79
|
+
const field = HEADER_FIELDS[param.name] ?? param.name;
|
|
80
|
+
headerByField.set(field, param.name);
|
|
81
|
+
known.add(field);
|
|
82
|
+
}
|
|
83
|
+
if (spec.body)
|
|
84
|
+
known.add("body");
|
|
85
|
+
for (const key of Object.keys(args)) {
|
|
86
|
+
if (!known.has(key))
|
|
87
|
+
throw new CarrierError(400, "unknown_parameter", `Unknown argument ${key}.`);
|
|
88
|
+
}
|
|
89
|
+
const path = {};
|
|
90
|
+
const query = {};
|
|
91
|
+
const headers = {};
|
|
92
|
+
for (const param of spec.pathParams) {
|
|
93
|
+
if (param.name in args)
|
|
94
|
+
path[param.name] = stringArg(args[param.name], param.name);
|
|
95
|
+
}
|
|
96
|
+
for (const param of spec.queryParams) {
|
|
97
|
+
if (param.name in args)
|
|
98
|
+
query[param.name] = stringArg(args[param.name], param.name);
|
|
99
|
+
}
|
|
100
|
+
let idempotencyKey;
|
|
101
|
+
for (const [field, header] of headerByField) {
|
|
102
|
+
if (!(field in args))
|
|
103
|
+
continue;
|
|
104
|
+
const value = stringArg(args[field], field);
|
|
105
|
+
if (field === "idempotency_key")
|
|
106
|
+
idempotencyKey = value;
|
|
107
|
+
else
|
|
108
|
+
headers[header] = value;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
path,
|
|
112
|
+
query,
|
|
113
|
+
headers,
|
|
114
|
+
body: "body" in args ? args.body : undefined,
|
|
115
|
+
idempotencyKey,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function rejectUnknown(label, values, params) {
|
|
119
|
+
const allowed = new Set(params.map((param) => param.name));
|
|
120
|
+
for (const name of Object.keys(values)) {
|
|
121
|
+
if (!allowed.has(name)) {
|
|
122
|
+
throw new CarrierError(400, "unknown_parameter", `Unknown ${label} parameter ${name}.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function requirePresent(label, values, params) {
|
|
127
|
+
for (const param of params) {
|
|
128
|
+
if (param.required && !(param.name in values)) {
|
|
129
|
+
throw new CarrierError(400, "missing_parameter", `${label} parameter ${param.name} is required.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function applyIdempotency(spec, headers, idempotencyKey) {
|
|
134
|
+
if (idempotencyKey === undefined)
|
|
135
|
+
return;
|
|
136
|
+
const names = new Set(spec.headerParams.map((param) => param.name));
|
|
137
|
+
if (names.has("Idempotency-Key")) {
|
|
138
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (names.has("X-Idempotency-Key")) {
|
|
142
|
+
headers["X-Idempotency-Key"] = idempotencyKey;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
throw new CarrierError(400, "unknown_parameter", "This operation does not take an idempotency key.");
|
|
146
|
+
}
|
|
147
|
+
function requireHeaders(headers, spec) {
|
|
148
|
+
for (const param of spec.headerParams) {
|
|
149
|
+
if (!param.required || param.name in headers)
|
|
150
|
+
continue;
|
|
151
|
+
const field = HEADER_FIELDS[param.name] ?? param.name;
|
|
152
|
+
throw new CarrierError(400, "missing_parameter", `${field} is required.`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function fillPath(template, values) {
|
|
156
|
+
const filled = template.replace(/\{([^{}]+)\}/g, (_match, name) => {
|
|
157
|
+
if (!(name in values)) {
|
|
158
|
+
throw new CarrierError(400, "missing_parameter", `Path parameter ${name} is required.`);
|
|
159
|
+
}
|
|
160
|
+
return encodeURIComponent(requirePathId(values[name] ?? "", name));
|
|
161
|
+
});
|
|
162
|
+
if (filled.includes("{") || filled.includes("}")) {
|
|
163
|
+
throw new CarrierError(400, "invalid_path", "Operation path could not be filled.");
|
|
164
|
+
}
|
|
165
|
+
return filled;
|
|
166
|
+
}
|
|
167
|
+
function queryValue(value) {
|
|
168
|
+
if (typeof value === "boolean")
|
|
169
|
+
return value ? "true" : "false";
|
|
170
|
+
return String(value);
|
|
171
|
+
}
|
|
172
|
+
function stringArg(value, label) {
|
|
173
|
+
if (typeof value !== "string") {
|
|
174
|
+
throw new CarrierError(400, "invalid_parameter", `${label} must be a string.`);
|
|
175
|
+
}
|
|
176
|
+
return value;
|
|
177
|
+
}
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carrierllc/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TypeScript client for the Carrier API, MCP server, and CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"node": ">=22"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"build": "tsc -p tsconfig.build.json",
|
|
34
|
+
"build": "tsc -p tsconfig.build.json && rm -rf dist/data && cp -r src/data dist/data",
|
|
35
35
|
"type-check": "tsc --noEmit",
|
|
36
36
|
"lint": "eslint src tests",
|
|
37
37
|
"test": "vitest run"
|