@carrierllc/mcp 0.13.29 → 0.13.31

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 CHANGED
@@ -21,12 +21,183 @@ carrier status # plugin + MCP + next steps
21
21
  carrier plugin install # zero-cred MCP URL; OAuth on first use in Claude
22
22
  carrier open signup # create account
23
23
  carrier examples # paste-ready “talk to fleet” prompts
24
- carrier ask "fleet health" # natural-language fallback; needs CARRIER_API_KEY=ak_…
24
+ carrier login # browser once; native OAuth refreshes silently
25
+ carrier ask "fleet health" # uses CLI OAuth / compatible Claude Code OAuth / explicit key
26
+ carrier exec -- bash # real child terminal with CARRIER_API_KEY in its environment
25
27
  ```
26
28
 
27
29
  **Entry A:** register `https://mcp.carrier.llc/mcp` with no token. The first tool
28
30
  call in Claude/Cursor opens Clerk OAuth (Google / GitHub / email).
29
31
 
32
+ ### Natural-language answers
33
+
34
+ `carrier ask "how many subscribers are there?"` and the interactive home's
35
+ **Ask the fleet** action present executed tool results as readable answers.
36
+ Known OCS list summaries use the server's authoritative `summary.total`, not a
37
+ recount of the displayed rows. That number describes the result/page, not
38
+ necessarily the entire fleet; paging, missing rows and shortened display are
39
+ reported explicitly. Nested subscriber status and SIM inventory status remain
40
+ separate. A server facet that could not read nested status is not a claim that
41
+ subscriber status is missing.
42
+
43
+ This is deterministic presentation, not another AI/model call. The original
44
+ question distinguishes counts from requested lists/status/details. Unsupported
45
+ predictions and judgements receive an explicit limitation rather than unrelated
46
+ records. Current subscriber status uses the shared OCS open-period normalizer,
47
+ with its maintained closed-history fallback; SIM inventory remains separate.
48
+
49
+ Default JSON presentation uses typed scalar allowlists and explicit shallow
50
+ account/result wrappers. Unknown fields, nested shapes, and wrong field types
51
+ are omitted with a safe limitation; they are never recursively dumped. Safe
52
+ ordinary scalar facts remain readable. Empty or unsupported summary facets are
53
+ omitted. Plain-text tool answers are passed through, shortened only at whole
54
+ sentence/row boundaries; this is not a general redaction guarantee for arbitrary
55
+ prose. Oversized indivisible rows are omitted entirely with the raw-output hint.
56
+
57
+ ```bash
58
+ carrier ask "how many subscribers are there?" --json
59
+ ```
60
+
61
+ `--json` is explicit raw access: complete JSON stdout, no banner/spinner; plain
62
+ text is encoded as a JSON string. Raw output may contain sensitive data. Failures
63
+ remain nonzero with errors on stderr. The MCP machine contract and domain
64
+ commands are unchanged. Ambiguous intents and confirmation-required mutations
65
+ still do not execute a resolved tool.
66
+
67
+ ### OAuth and terminal handoff
68
+
69
+ Carrier commands, status and Ask the Fleet share credential resolution. Explicit
70
+ `CARRIER_API_KEY` / `CARRIER_ORG_API_KEY` always win. Native `carrier login` stores
71
+ a private refreshable session with one cross-process owner lock and atomic writes.
72
+ Login, refresh and logout serialize; a completed logout cannot be resurrected by
73
+ an earlier refresh. A crashed CLI owner leaves a fail-closed `credentials.lock`
74
+ directory: verify that no CLI login/refresh is running before manually removing
75
+ that directory. Changing the callback port triggers fresh client registration.
76
+ Native login and refresh request the exact production MCP resource. A present
77
+ wrong/malformed token-response resource is rejected before credential storage;
78
+ legacy absence is distinct and refresh retains a previously known binding.
79
+
80
+ ### Existing bash/zsh terminal (explicit setup)
81
+
82
+ ```bash
83
+ source "$(carrier shell-path bash)" # use zsh instead of bash in zsh
84
+ carrier_auth_export
85
+ # CARRIER_API_KEY is now exported in THIS shell and its subsequent children.
86
+ # Before an arbitrary headless job, refresh this snapshot again:
87
+ carrier_auth_export && your-headless-job
88
+ carrier logout
89
+ carrier_auth_clear # erase this shell's snapshot too
90
+ ```
91
+
92
+ Only a shipped static file is sourced. No secret is printed, evaluated as code,
93
+ put in command arguments/history, or written to a startup file. The function
94
+ reads an anonymous private pipe via an inherited descriptor. It refuses shell
95
+ traps and turns off xtrace/verbose **without restoring them**, because the next
96
+ caller command could expand the newly exported environment. Bash opt-in loading
97
+ enables errtrace so caller ERR traps are visible for refusal. Trusted commands,
98
+ same-UID process environment readers and crash dumps can still expose a bearer;
99
+ this is not protection from a compromised shell/OS. Do not re-enable tracing
100
+ around credentials.
101
+
102
+ Shell acceptance requires **both bash and zsh**: `pnpm test:oauth-shell`.
103
+ `CARRIER_TEST_ZSH` may point to an isolated zsh binary and
104
+ `CARRIER_TEST_ZSH_MODULE_PATH` to its matching modules. The general repair suite
105
+ reports a missing zsh interpreter as an explicit SKIP, never a zsh pass; the
106
+ acceptance command fails when either interpreter is unavailable.
107
+
108
+ OAuth exports carry `CARRIER_OAUTH_ORIGIN`, `CARRIER_OAUTH_EXPIRES_AT` (epoch ms)
109
+ and a `CARRIER_OAUTH_HANDOFF` fingerprint plus non-secret `CARRIER_OAUTH_BINDING`.
110
+ Carrier invocations reread only the selected owner, source directory/file, server,
111
+ registration and opaque subject/grant. Same-owner token rotation is allowed;
112
+ expiry, logout, owner/config/account/org changes fail closed, never switch to
113
+ another valid client session. Old exports without binding must be exported again.
114
+ These metadata equality checks are not cryptographic account/org proof.
115
+ Arbitrary programs retain it until expiry: re-run `carrier_auth_export` before
116
+ consumption. Replacing `CARRIER_API_KEY` intentionally overrides the fingerprint
117
+ and remains authoritative. `carrier_auth_clear` removes this shell's exported
118
+ key and metadata, not the other client's login.
119
+
120
+ `carrier exec -- <command> [args...]` remains available for a trusted child only;
121
+ no setup required, no parent-shell mutation, and child exit/signal is preserved.
122
+ OAuth is a short-lived bearer, **not** an `ak_` key.
123
+
124
+ ### Supported read-only MCP owners
125
+
126
+ Without a native session, a current compatible MCP access token may be reused;
127
+ no refresh grant is copied, rotated or written by Carrier. If the owner expires,
128
+ refresh it in that client or use native `carrier login`. No owner reauth command
129
+ is invoked. Multiple current owners require `CARRIER_MCP_OAUTH_OWNER=hermes` or
130
+ `claude`; the CLI never guesses which account to use.
131
+
132
+ - **Claude Code 2.1.277 and 2.1.296:** exact inspected config and storage adapters.
133
+ The owner's `.claude.json` lives under `CLAUDE_CONFIG_DIR || ~`; an existing
134
+ legacy `.config.json` takes precedence. For the consuming CLI's cwd, local
135
+ `projects[real git root || real cwd].mcpServers` wins over approved ancestor
136
+ `.mcp.json` project config, which wins over user config. Other projects are not
137
+ candidates. Project import supports explicit trusted user settings approval
138
+ only; pending/rejected or ambiguous policy/settings fail closed.
139
+ Hashes the actual `{type,url,headers:headers||{}}` in original insertion order:
140
+ HTTP, SSE and absent `type` remain distinct. No hardcoded empty-header hash.
141
+ Managed selection remains unsupported and fails closed; no inferred
142
+ plugin/proxy/CLI override resolution. Authorization/API-key/cookie headers and
143
+ unresolved environment substitutions are not imported as OAuth.
144
+ `CLAUDE_SECURESTORAGE_CONFIG_DIR` takes precedence by presence for credential
145
+ storage; present-empty selects `~/.claude`, while nonempty and `CLAUDE_CONFIG_DIR`
146
+ retain the exact NFC strings (no trim or resolve before the Keychain hash).
147
+ `USER || os.userInfo().username` selects the exact account, with Claude's
148
+ invalid-account fallback. An approved custom OAuth environment selects only
149
+ its own suffix; unrelated suffixes/accounts are never enumerated.
150
+ Linux reads the private `.credentials.json`. `CARRIER_MCP_OAUTH_FILE` explicitly
151
+ selects that file backend on either platform, without consulting Keychain.
152
+ CamelCase expiry is epoch milliseconds.
153
+ - **Hermes:** selected `HERMES_HOME` (default `~/.hermes`), exact configured
154
+ `mcp_servers.carrier.url`, with private `mcp-tokens/carrier.json`,
155
+ `.client.json` and `.meta.json`. Real snake_case access-token fields,
156
+ `hermes_issuer`, Bearer type and absolute `expires_at` in seconds are required.
157
+ Issuer/discovery endpoints, client registration and resource configuration are
158
+ checked. Legacy relative-only expiry is refused. Config JSON and simple scalar
159
+ block YAML are supported; nested/alias/flow constructs fail closed.
160
+
161
+ `CARRIER_MCP_OAUTH_SERVER` selects an alternative explicit name. Private mode
162
+ and current-user file ownership, exact issuer/resource, `read` scope and finite
163
+ future expiry are required. `CARRIER_OAUTH_ACCOUNT` optionally selects the
164
+ Carrier opaque token's user prefix; it is **not** cryptographic account/org
165
+ proof. Only the server validates actual token audience, organization and grant
166
+ permissions. Invalid native state never falls through to another owner.
167
+
168
+ On macOS the packaged `native/claude-keychain.js` uses the built-in JXA
169
+ Objective-C bridge and Apple Security Framework: one generic-password query,
170
+ exact service and account, `kSecMatchLimitOne`, `kSecReturnData`,
171
+ `kSecUseAuthenticationUIFail`, plus process-local legacy UI inhibition.
172
+ There is no compiler/download/install-at-login dependency, no `security -w`,
173
+ no broad dump, and no owner lock/refresh/login. Only non-secret selectors reach
174
+ stdin. Only the selected access-token fields reach an inherited private FD;
175
+ stdout/stderr are discarded. Owner refresh grants, client secrets and unrelated
176
+ credentials never leave the helper. Apple may report an ACL prompt requirement
177
+ or locked store identically (`errSecInteractionNotAllowed`): `locked` denotes
178
+ that noninteractive refusal, not a diagnosis of the physical Keychain state.
179
+ `errSecAuthFailed`/user-cancel are `denied`, parse errors are `malformed`, and
180
+ other failures are `unavailable`. None falls back to plaintext or Hermes.
181
+ Only `errSecItemNotFound` for the exact item permits Claude's documented private
182
+ file fallback. An existing item without the exact MCP entry does not fall back.
183
+ Keychain reuse requires `claude --version` to be exactly 2.1.277 or 2.1.296;
184
+ other versions fail closed. Compatibility is an explicit inspected allowlist,
185
+ not a version range. The source contracts are tested on Linux; a real Darwin
186
+ absent-item probe also executes the packaged helper byte-for-byte without
187
+ stdout/stderr. Successful existing-item access and installed end-to-end Mac
188
+ OAuth remain unproved. The inspected Mac 2.1.296 query returned `denied`
189
+ (OSStatus -25293), not absence; this adapter never broadens Keychain access or
190
+ falls back after that refusal.
191
+ Claude Desktop, Cursor, Windsurf and mcp-remote remain unsupported owners.
192
+ Issuer-less entries are intentionally rejected. `carrier logout` disables MCP
193
+ reuse until a successful native login and never edits the owner's store or
194
+ another shell's explicit keys.
195
+
196
+ Native API references: [Apple SecItemCopyMatching](https://developer.apple.com/documentation/security/secitemcopymatching(_:_:)),
197
+ [Apple SecItem.h](https://github.com/apple-oss-distributions/Security/blob/main/keychain/headers/SecItem.h),
198
+ [Apple JXA release notes](https://developer.apple.com/library/archive/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/),
199
+ [Claude authentication storage](https://code.claude.com/docs/en/authentication).
200
+
30
201
  ### Domain commands
31
202
 
32
203
  With a headless key in the environment, each capability has its own command,
@@ -1855,16 +1855,22 @@ function storefrontScreen(input) {
1855
1855
  // package.json
1856
1856
  var package_default = {
1857
1857
  name: "@carrierllc/mcp",
1858
- version: "0.13.29",
1858
+ version: "0.13.31",
1859
1859
  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).",
1860
1860
  license: "MIT",
1861
1861
  author: "Carrier (Lifecycle Innovations Limited)",
1862
1862
  homepage: "https://mcp.carrier.llc",
1863
1863
  type: "module",
1864
+ carrierCompatibility: {
1865
+ claudeOAuthOwnerVersion: "2.1.277",
1866
+ claudeOAuthOwnerVersions: ["2.1.277", "2.1.296"]
1867
+ },
1864
1868
  main: "./dist/index.js",
1865
1869
  files: [
1866
1870
  "dist",
1867
1871
  "plugin",
1872
+ "shell",
1873
+ "native",
1868
1874
  "README.md",
1869
1875
  "templates",
1870
1876
  "!templates/**/node_modules",
@@ -1882,7 +1888,9 @@ var package_default = {
1882
1888
  lint: "eslint src",
1883
1889
  prepublishOnly: "pnpm run build",
1884
1890
  "build:test": "tsup --config tsup.test.config.ts",
1885
- test: "pnpm run build && pnpm run build:test && node --test test/*.test.js",
1891
+ test: "pnpm run build && pnpm run build:test && node --test-concurrency=1 --test test/*.test.js && pnpm run test:oauth-repair",
1892
+ "test:oauth-repair": "node --experimental-transform-types --test-concurrency=1 --test test/oauth-repair.test.mjs test/claude-owner.test.mjs test/current-owner.test.mjs",
1893
+ "test:oauth-shell": "node scripts/check-oauth-shell.mjs",
1886
1894
  "check:pack": "node scripts/check-pack-size.mjs",
1887
1895
  "generate:domains": "node scripts/generate-domains.mjs"
1888
1896
  },
@@ -4344,6 +4352,32 @@ function ocsArray(data, keys) {
4344
4352
  }
4345
4353
  return [];
4346
4354
  }
4355
+ function subscriberIccid(row) {
4356
+ if (row === null || typeof row !== "object") return "";
4357
+ const r = row;
4358
+ const imsiList = r.imsiList;
4359
+ if (Array.isArray(imsiList)) {
4360
+ for (const entry of imsiList) {
4361
+ if (entry && typeof entry === "object") {
4362
+ const v = entry.iccid;
4363
+ if (typeof v === "string" && v.length > 0) return v;
4364
+ if (typeof v === "number") return String(v);
4365
+ }
4366
+ }
4367
+ }
4368
+ for (const key of ["iccid", "ICCID"]) {
4369
+ const v = r[key];
4370
+ if (typeof v === "string" && v.length > 0) return v;
4371
+ if (typeof v === "number") return String(v);
4372
+ }
4373
+ const sim = r.sim;
4374
+ if (sim && typeof sim === "object") {
4375
+ const v = sim.iccid;
4376
+ if (typeof v === "string" && v.length > 0) return v;
4377
+ if (typeof v === "number") return String(v);
4378
+ }
4379
+ return "";
4380
+ }
4347
4381
  var FACET_TALLY_MAX = 25;
4348
4382
  var OCS_LIST_FACETS = {
4349
4383
  templates: [
@@ -5418,6 +5452,8 @@ export {
5418
5452
  recordToolDescription,
5419
5453
  buildRouterCatalog,
5420
5454
  ROUTER_RULES,
5455
+ OCS_LIST_KEYS,
5456
+ subscriberIccid,
5421
5457
  withOcsListSummary,
5422
5458
  extractDailyUsage,
5423
5459
  daysUntil,
@@ -5480,6 +5516,7 @@ export {
5480
5516
  walletScreen,
5481
5517
  greenzoneScreen,
5482
5518
  storefrontScreen,
5519
+ package_default,
5483
5520
  CARRIER_VERSION
5484
5521
  };
5485
- //# sourceMappingURL=chunk-LJPYVILK.js.map
5522
+ //# sourceMappingURL=chunk-EHJEAI4V.js.map