@apify/mcpc 0.3.0 → 0.3.1-beta.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +171 -48
  3. package/dist/bridge/index.js +32 -40
  4. package/dist/bridge/index.js.map +1 -1
  5. package/dist/cli/commands/clean.d.ts.map +1 -1
  6. package/dist/cli/commands/clean.js +3 -2
  7. package/dist/cli/commands/clean.js.map +1 -1
  8. package/dist/cli/commands/logs.d.ts +8 -0
  9. package/dist/cli/commands/logs.d.ts.map +1 -0
  10. package/dist/cli/commands/logs.js +143 -0
  11. package/dist/cli/commands/logs.js.map +1 -0
  12. package/dist/cli/commands/sessions.d.ts +6 -4
  13. package/dist/cli/commands/sessions.d.ts.map +1 -1
  14. package/dist/cli/commands/sessions.js +141 -85
  15. package/dist/cli/commands/sessions.js.map +1 -1
  16. package/dist/cli/commands/skills.d.ts +20 -0
  17. package/dist/cli/commands/skills.d.ts.map +1 -0
  18. package/dist/cli/commands/skills.js +160 -0
  19. package/dist/cli/commands/skills.js.map +1 -0
  20. package/dist/cli/commands/x402.d.ts.map +1 -1
  21. package/dist/cli/commands/x402.js +14 -4
  22. package/dist/cli/commands/x402.js.map +1 -1
  23. package/dist/cli/index.js +113 -24
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/cli/output.d.ts +13 -0
  26. package/dist/cli/output.d.ts.map +1 -1
  27. package/dist/cli/output.js +114 -20
  28. package/dist/cli/output.js.map +1 -1
  29. package/dist/cli/parser.d.ts +1 -1
  30. package/dist/cli/parser.d.ts.map +1 -1
  31. package/dist/cli/parser.js +25 -2
  32. package/dist/cli/parser.js.map +1 -1
  33. package/dist/cli/shell.js.map +1 -1
  34. package/dist/core/capabilities.d.ts +3 -0
  35. package/dist/core/capabilities.d.ts.map +1 -0
  36. package/dist/core/capabilities.js +14 -0
  37. package/dist/core/capabilities.js.map +1 -0
  38. package/dist/core/index.d.ts +1 -0
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +1 -0
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/mcp-client.d.ts +4 -0
  43. package/dist/core/mcp-client.d.ts.map +1 -1
  44. package/dist/core/mcp-client.js +34 -12
  45. package/dist/core/mcp-client.js.map +1 -1
  46. package/dist/lib/bridge-client.d.ts +4 -1
  47. package/dist/lib/bridge-client.d.ts.map +1 -1
  48. package/dist/lib/bridge-client.js +29 -3
  49. package/dist/lib/bridge-client.js.map +1 -1
  50. package/dist/lib/bridge-manager.d.ts +2 -2
  51. package/dist/lib/bridge-manager.d.ts.map +1 -1
  52. package/dist/lib/bridge-manager.js +31 -15
  53. package/dist/lib/bridge-manager.js.map +1 -1
  54. package/dist/lib/cleanup.d.ts.map +1 -1
  55. package/dist/lib/cleanup.js +20 -19
  56. package/dist/lib/cleanup.js.map +1 -1
  57. package/dist/lib/config.d.ts +14 -0
  58. package/dist/lib/config.d.ts.map +1 -1
  59. package/dist/lib/config.js +41 -27
  60. package/dist/lib/config.js.map +1 -1
  61. package/dist/lib/errors.d.ts +0 -1
  62. package/dist/lib/errors.d.ts.map +1 -1
  63. package/dist/lib/errors.js +2 -4
  64. package/dist/lib/errors.js.map +1 -1
  65. package/dist/lib/index.d.ts +1 -0
  66. package/dist/lib/index.d.ts.map +1 -1
  67. package/dist/lib/index.js +1 -0
  68. package/dist/lib/index.js.map +1 -1
  69. package/dist/lib/log-reader.d.ts +28 -0
  70. package/dist/lib/log-reader.d.ts.map +1 -0
  71. package/dist/lib/log-reader.js +278 -0
  72. package/dist/lib/log-reader.js.map +1 -0
  73. package/dist/lib/session-client.d.ts.map +1 -1
  74. package/dist/lib/session-client.js +3 -5
  75. package/dist/lib/session-client.js.map +1 -1
  76. package/dist/lib/stderr-tail.d.ts +10 -0
  77. package/dist/lib/stderr-tail.d.ts.map +1 -0
  78. package/dist/lib/stderr-tail.js +28 -0
  79. package/dist/lib/stderr-tail.js.map +1 -0
  80. package/dist/lib/types.d.ts +6 -1
  81. package/dist/lib/types.d.ts.map +1 -1
  82. package/dist/lib/types.js +1 -0
  83. package/dist/lib/types.js.map +1 -1
  84. package/dist/lib/utils.d.ts +1 -0
  85. package/dist/lib/utils.d.ts.map +1 -1
  86. package/dist/lib/utils.js +12 -2
  87. package/dist/lib/utils.js.map +1 -1
  88. package/dist/lib/x402/fetch-middleware.d.ts +3 -2
  89. package/dist/lib/x402/fetch-middleware.d.ts.map +1 -1
  90. package/dist/lib/x402/fetch-middleware.js +37 -24
  91. package/dist/lib/x402/fetch-middleware.js.map +1 -1
  92. package/dist/lib/x402/signer.d.ts +7 -1
  93. package/dist/lib/x402/signer.d.ts.map +1 -1
  94. package/dist/lib/x402/signer.js +249 -5
  95. package/dist/lib/x402/signer.js.map +1 -1
  96. package/docs/README.md +1 -0
  97. package/docs/claude-skill/SKILL.md +151 -117
  98. package/docs/examples/company-lookup.sh +6 -6
  99. package/docs/vhs/README.md +79 -0
  100. package/docs/vhs/grep.tape +56 -0
  101. package/docs/vhs/mcpc-demo.tape +179 -0
  102. package/docs/vhs/proxy.tape +69 -0
  103. package/docs/vhs/quickstart.tape +59 -0
  104. package/docs/vhs/scripting.tape +69 -0
  105. package/docs/vhs/tools.tape +66 -0
  106. package/package.json +20 -20
package/CHANGELOG.md CHANGED
@@ -7,22 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - `mcpc @<session>` now reports the negotiated MCP protocol version and whether the connection is stateful (a stdio process, or an HTTP server that assigned a session id) or stateless (an HTTP server that assigned none — the upcoming `2026-07-28` model). The same `statefulness` value is exposed under `_mcpc` in the `--json` output of `mcpc @<session>` and `mcpc connect`, and on each session in the session list. Stateless sessions are never marked `expired` on a transient `404` (they hold no session to lose).
13
+ - `mcpc @<session> logs` command to show or follow the bridge log file. Supports `-n/--tail <n>` (default 50), `--follow` to stream new lines, and `--since <duration|iso>` (e.g. `1h`, `30m`, `2026-04-28T12:00:00Z`). Transparently spans rotated files (`.log.1` … `.log.5`) when more lines are needed. With `--json`, returns parsed `{ time, level, context?, msg }` records (continuation lines such as stack traces fold into the preceding entry's `msg`; lines that aren't standard log entries become `{ raw }`); combined with `--follow`, output is JSONL. `mcpc @<session> --json` now also exposes `_mcpc.logPath` and `_mcpc.logSize`. Error messages that previously pointed users to a raw log file path now suggest `mcpc @<session> logs` instead (#205).
14
+
15
+ ### Deprecated
16
+
17
+ - The `shell` command (`mcpc shell @<session>` and `mcpc @<session> shell`) is deprecated and will be removed in a future release. It is now hidden from `--help` output and prints a deprecation warning when invoked
18
+
19
+ ### Fixed
20
+
21
+ - `mcpc connect` (with no arguments) no longer silently ignores config files it can't use. Files with an empty servers object are listed as `0 servers`, and files that fail to load — invalid JSON, a project config missing a `mcpServers`/`servers` property, or an unreadable file — are listed as `(invalid)` with the reason, instead of being dropped or misreported as `No MCP config files found`.
22
+ - `mcpc connect` now prints config file paths so they can be copy-pasted directly: paths containing spaces (e.g. macOS `Library/Application Support/...`) are quoted instead of being split when pasted into a shell.
23
+ - `mcpc connect` no longer fails with `socket file not created within timeout` when the bridge is slow to start (CPU-constrained machines, many parallel connects). The startup window is more generous, and a bridge that crashes on startup now reports its exit code immediately instead of stalling until the timeout
24
+ - Sessions no longer fail to start with `Bridge process exited during startup` when the mcpc home directory (or `MCPC_HOME_DIR`) resolves to a deep path — notably macOS temp dirs like `/var/folders/...` — or when a session name is very long. The bridge's Unix socket path was exceeding the operating system limit (104 bytes on macOS, 108 on Linux); such paths now fall back to a short location under the system temp directory
25
+ - `mcpc @<session> restart` (and other session startups) no longer intermittently fail with `Failed to connect to bridge: connect ECONNREFUSED` during rapid restarts. The CLI now briefly retries the first connection to a freshly started bridge while it is still (re)creating its socket
26
+
10
27
  ## [0.3.0] - 2026-05-20
11
28
 
12
29
  ### Added
13
30
 
31
+ - New **[EXPERIMENTAL]** `skills-list` and `skills-get` commands implementing the draft MCP skills extension (`io.modelcontextprotocol/skills`, [SEP-2640](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640)). Marked experimental because the spec is still in draft and the index shape, recognized types, and capability key may change. Skills are discovered via the well-known `skill://index.json` resource, falling back to scanning `skill://*/SKILL.md` URIs. Supports all three SEP-2640 index entry types: `skill-md`, `archive` (`.tar.gz`/`.zip` bundles), and `mcp-resource-template`. Entries with unrecognized `type` values are skipped per spec. `skills-get <name>` reads a skill's `SKILL.md`; pass `--raw` to print just the markdown for piping to LLMs or files. The session overview lists `skills` under capabilities when a server advertises the extension under either `capabilities.extensions` (per spec) or `capabilities.experimental` (the SDK-preserved escape hatch). JSON shape: `[{ name, description, type, url }]` for `skills-list`, full `ReadResourceResult` for `skills-get`.
14
32
  - `mcpc connect` (with no arguments) now auto-discovers standard MCP config files (`.mcp.json`, `mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `~/.claude.json`, Claude Desktop, Windsurf, Kiro, etc.) in the current directory and home directory, and connects every server defined across them. Entries with duplicate session names are deduplicated (project-scoped files win over global ones). VS Code's `"servers"` key is also supported.
15
33
  - `mcpc connect` auto-connects to `mcp.apify.com` as `@apify` when the `APIFY_API_TOKEN` environment variable is set, using it as a Bearer token. Existing live sessions are reused without restart.
34
+ - `mcpc x402 sign` supports the x402 `upto` scheme alongside `exact`. Use `--scheme <auto|upto|exact>` to pick a preference (default `auto` prefers `upto`, falls back to `exact`). The signer auto-grants a one-time `USDC.approve(PERMIT2, MAX_UINT256)` allowance on first upto sign; pass `--no-approve` to skip.
35
+ - `mcpc connect --x402 [auto|upto|exact]` enables x402 auto-payment with an optional scheme preference. Bare `--x402` defaults to `auto` (prefer upto, fall back to exact). The choice is persisted to `sessions.json` and reused on `mcpc restart`. Flag position is unrestricted — `mcpc connect --x402 mcp.apify.com @s` and `mcpc connect mcp.apify.com @s --x402` both work.
36
+ - Sessions using x402 auto-payment now show a yellow `[x402]` marker in session listings, alongside the existing OAuth and proxy markers.
16
37
 
17
38
  ### Changed
18
39
 
19
40
  - Stdio (command-based) config entries are now skipped by default when connecting from a config file (`mcpc connect <file>`). Pass `--stdio` to include them. Single-entry connects (`mcpc connect file:entry @session`) are not affected.
41
+ - x402 debug logs now announce the selected scheme (`scheme=upto` / `scheme=exact`) up front and print USD amounts with 6-decimal precision (USDC atomic unit). Enable with `--verbose` or `MCPC_VERBOSE=1`.
20
42
  - **Breaking:** `mcpc connect --json` now always returns an array of `InitializeResult` objects (extended with `toolNames` and `_mcpc` metadata), regardless of whether you connect a single server, a config file, or auto-discover all configs. Skipped/failed entries carry `_mcpc.status` (`created` | `active` | `failed` | `skipped`) and `_mcpc.skipReason` / `_mcpc.error`. The previous wrapper-object shapes (`{configFile, results, skipped}` and `{discovered, results, skipped}`) have been removed.
21
43
  - `tools-call --task` now prints the task ID and recovery commands when interrupted with Ctrl+C, so you can fetch or cancel the server-side task later
44
+ - Human-mode `tools-call` / `tasks-result` output no longer prints the **Structured content** section when **Content** already has at least one visible block. The JSON dump was redundant verbose output (especially for LLMs) whenever a server returned both. The section is still shown when `content` is empty or contains only a JSON-dump duplicate of `structuredContent`; use `--json` to always get the full payload
22
45
 
23
46
  ### Fixed
24
47
 
25
- - `mcpc connect` and `mcpc restart` no longer fail with `ENOENT` when the macOS Keychain prompts for a password. Credentials are now read from the keychain *before* the bridge process is spawned, so the bridge's IPC startup timer no longer races a foreground password dialog (#55)
48
+ - `mcpc connect` and `mcpc restart` no longer fail with `ENOENT` when the macOS Keychain prompts for a password. Credentials are now read from the keychain _before_ the bridge process is spawned, so the bridge's IPC startup timer no longer races a foreground password dialog (#55)
26
49
  - Background auto-reconnect now correctly marks sessions as `unauthorized` when the server returns 401/403, instead of leaving them stuck in `connecting` after the bridge crashed on an unhandled rejection
27
50
  - Sessions using a static bearer token (via `--header "Authorization: ..."`) no longer flip between `unauthorized` and `connecting` on every `mcpc` invocation — they stay `unauthorized` until you `mcpc login` or reconnect. OAuth-profile sessions still auto-retry because tokens may have been refreshed by another session
28
51
  - Stdio servers no longer fail silently: the bridge now captures the child's stderr, writes it to `~/.mcpc/logs/bridge-<session>.log`, and appends a tail of the most recent lines to `mcpc connect` errors. This makes it obvious when a stdio server crashes on startup due to e.g. missing TLS trust (`NODE_EXTRA_CA_CERTS`), missing proxy vars, or missing credentials (#195)
package/README.md CHANGED
@@ -63,12 +63,12 @@ wiring up dozens of MCP functions. Just one `Bash()` tool, and `mcpc` handles th
63
63
 
64
64
  ```
65
65
 
66
- ┌──────────┐ Bash() ┌──────────┐ MCP ┌────────────┐
67
- │ AI agent │ ────────────────────► mcpc ────────────────────► │ MCP server │
68
- └──────────┘ └──────────┘ Sessions, OAuth, └────────────┘
69
- Tools, Resources,
70
- Prompts, Tasks,
71
- x402, ...
66
+ ┌──────────┐ Bash() ┌────────┐ MCP ┌────────────┐
67
+ │ AI agent │ ─────────► mcpc ────────► │ MCP server │
68
+ └──────────┘ └────────┘ └────────────┘
69
+ Sessions, OAuth, Tools,
70
+ Resources, Prompts,
71
+ Tasks, x402, ...
72
72
  ```
73
73
 
74
74
  CLI is the perfect _local_ interface between agents and MCP, while MCP remains the
@@ -80,6 +80,8 @@ many AI agents on the same machine. Authenticate once, reuse everywhere.
80
80
 
81
81
  ## Install
82
82
 
83
+ Requires a JavaScript runtime — install the latest [Node.js](https://nodejs.org/en/download) or [Bun](https://bun.sh) if you don't have one yet.
84
+
83
85
  ```bash
84
86
  npm install -g @apify/mcpc
85
87
 
@@ -127,6 +129,56 @@ mcpc @fs tools-list
127
129
  <!-- AUTO-GENERATED: mcpc --help -->
128
130
 
129
131
  ```
132
+ Usage: mcpc [<@session>] [<command>] [options]
133
+
134
+ Universal command-line client for the Model Context Protocol (MCP).
135
+
136
+ Commands:
137
+ connect [<server>] [@session] Connect to an MCP server and start a new named @session
138
+ close <@session> Close a session
139
+ restart <@session> Restart a session (losing all state)
140
+ login <server> Interactively login to a server using OAuth and save profile
141
+ logout <server> Delete an OAuth profile for a server
142
+ clean [resources...] Clean up mcpc data (sessions, profiles, logs, all)
143
+ grep <pattern> Search tools and instructions across all active sessions
144
+ x402 [subcommand] [args...] Configure an x402 payment wallet (EXPERIMENTAL)
145
+ help [command] [subcommand] Show help for a specific command
146
+
147
+ Options:
148
+ --json Output in JSON format for scripting
149
+ --verbose Enable debug logging
150
+ --profile <name> OAuth profile for the server ("default" if not provided)
151
+ --timeout <seconds> Request timeout in seconds (default: 300)
152
+ --max-chars <n> Truncate output to n characters (ignored in --json mode)
153
+ --insecure Skip TLS certificate verification (for self-signed certs)
154
+ -v, --version Output the version number
155
+ -h, --help Display help
156
+
157
+ MCP session commands (after connecting):
158
+ <@session> Show MCP server info, capabilities, and tools overview
159
+ <@session> grep <pattern> Search tools and instructions
160
+ <@session> tools-list List all server tools
161
+ <@session> tools-get <name> Get tool details and schema
162
+ <@session> tools-call <name> [arg:=val ... | <json> | <stdin]
163
+ <@session> prompts-list
164
+ <@session> prompts-get <name> [arg:=val ... | <json> | <stdin]
165
+ <@session> resources-list
166
+ <@session> resources-read <uri>
167
+ <@session> resources-subscribe <uri>
168
+ <@session> resources-unsubscribe <uri>
169
+ <@session> resources-templates-list
170
+ <@session> skills-list
171
+ <@session> skills-get <name> [--raw]
172
+ <@session> tasks-list
173
+ <@session> tasks-get <taskId>
174
+ <@session> tasks-result <taskId>
175
+ <@session> tasks-cancel <taskId>
176
+ <@session> logging-set-level <level>
177
+ <@session> ping
178
+ <@session> logs [-n N] [--follow] [--since 1h]
179
+
180
+ Run "mcpc" without arguments to show active sessions and OAuth profiles.
181
+ Run "mcpc --json" to get the same data as `{ sessions: [...], profiles: [...] }`.
130
182
  ```
131
183
 
132
184
  ### General actions
@@ -153,6 +205,26 @@ The `connect`, `login`, and `logout` commands accept a `<server>` argument in th
153
205
  - **Remote URL** (e.g. `mcp.apify.com` or `https://mcp.apify.com`) — scheme defaults to `https://`
154
206
  - **Config file entry** (e.g. `~/.vscode/mcp.json:filesystem`) — `file:entry-name` syntax
155
207
 
208
+ `connect` additionally supports two **bulk** forms that connect many servers at once:
209
+
210
+ - **Config file** without an entry (e.g. `~/.vscode/mcp.json`) — connect every server in the file
211
+ - **No argument** (`mcpc connect`) — auto-discover MCP config files in the current directory and
212
+ your home dir (`.mcp.json`, `mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `~/.claude.json`,
213
+ Claude Desktop, Windsurf, Kiro, …) and connect everything found (run `mcpc connect --help` for the
214
+ full list). Set `APIFY_API_TOKEN` to also connect `mcp.apify.com` as `@apify`.
215
+
216
+ ```bash
217
+ mcpc connect # discover standard config files and connect all servers
218
+ mcpc connect ~/.vscode/mcp.json # connect every server in one file
219
+ ```
220
+
221
+ Bulk connects auto-generate session names (so they don't take an `@session`) and **skip local
222
+ stdio servers by default** — pass `--stdio` to include them. Each discovered config file is listed
223
+ with its servers and their status (`● live`, `● connecting`); files that can't be used are shown as
224
+ `(0 servers)` or `(invalid)` with the reason, rather than silently ignored. Without `--json` the
225
+ command returns right away without waiting for every connection to finish (still-connecting sessions
226
+ show `● connecting`); `--json` waits and reports each server's details.
227
+
156
228
  ### MCP commands
157
229
 
158
230
  All MCP commands go through a named session created with `connect`:
@@ -302,7 +374,7 @@ and auto-reconnects on network failures or its own crashes (10s cooldown on fail
302
374
  **Session states:**
303
375
 
304
376
  | State | Meaning |
305
- |------------------| ----------------------------------------------------------------------------------------------- |
377
+ | ---------------- | ----------------------------------------------------------------------------------------------- |
306
378
  | 🟢`live` | Bridge process running and server responding |
307
379
  | 🟡`connecting` | Initial bridge startup in progress (`mcpc connect`) |
308
380
  | 🟡`reconnecting` | Bridge crashed or lost auth; auto-reconnecting in the background |
@@ -437,13 +509,13 @@ always win over stored profiles, and credentials are never silently downgraded.
437
509
  is missing, expired, or invalid, `mcpc` fails with an error that includes the right
438
510
  `mcpc login` command to recover.
439
511
 
440
- | Flag | Behavior |
441
- | ------------------------------- | ------------------------------------------------------------------------------------------- |
442
- | `--header "Authorization: ..."` | Use explicit header; skip OAuth auto-detection. Cannot combine with `--profile`. |
443
- | `--profile <name>` | Require the named profile to exist. |
444
- | `--no-profile` | Connect anonymously even if a `default` profile exists. |
445
- | `--x402` | Skip OAuth auto-detection; use x402 payments instead. Combine with `--profile` to use both. |
446
- | _(none)_ | Use `default` profile if it exists; otherwise connect anonymously. |
512
+ | Flag | Behavior |
513
+ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
514
+ | `--header "Authorization: ..."` | Use explicit header; skip OAuth auto-detection. Cannot combine with `--profile`. |
515
+ | `--profile <name>` | Require the named profile to exist. |
516
+ | `--no-profile` | Connect anonymously even if a `default` profile exists. |
517
+ | `--x402 [scheme]` | Skip OAuth auto-detection; use x402 payments instead. Optional scheme: `auto` (default), `upto`, `exact`. Combine with `--profile` to use both. |
518
+ | _(none)_ | Use `default` profile if it exists; otherwise connect anonymously. |
447
519
 
448
520
  Config file headers (from `--config`) apply to servers loaded from that file.
449
521
 
@@ -620,6 +692,9 @@ To help Claude Code use `mcpc`, you can install this [Claude skill](./docs/claud
620
692
 
621
693
  <!-- TODO: Add also AGENTS.md, GitHub skills etc. -->
622
694
 
695
+ `mcpc` also acts as a **client for skills served by MCP servers** (experimental, SEP-2640) — see
696
+ [Skills](#skills) for the `skills-list` / `skills-get` commands.
697
+
623
698
  ## Agentic payments (x402)
624
699
 
625
700
  > ⚠️ **Experimental.** This feature is under active development and may change.
@@ -633,13 +708,12 @@ This is entirely **opt-in**: existing functionality is unaffected unless you exp
633
708
 
634
709
  ### How it works
635
710
 
636
- 1. **Server returns HTTP 402** with a `PAYMENT-REQUIRED` header describing the price and payment details.
637
- 2. `mcpc` parses the header, signs an [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) `TransferWithAuthorization` using your local wallet.
638
- 3. `mcpc` retries the request with a `PAYMENT-SIGNATURE` header containing the signed payment.
639
- 4. The server verifies the signature and fulfills the request.
711
+ Two schemes are supported, both signed by your local wallet:
712
+
713
+ - **`exact`** EIP-3009 `TransferWithAuthorization`. Settles on-chain at call-time.
714
+ - **`upto`** Permit2 `PermitWitnessTransferFrom`. You sign a max cap; the facilitator settles accumulated usage later. First use auto-grants a one-time `USDC.approve(PERMIT2, MAX_UINT256)` (needs a tiny native ETH float for gas).
640
715
 
641
- For tools that advertise pricing in their `_meta.x402` metadata, `mcpc` can **proactively sign** payments
642
- on the first request, avoiding the 402 round-trip entirely.
716
+ Flow: server returns HTTP 402 with a `PAYMENT-REQUIRED` header → `mcpc` picks the best scheme per your preference, signs, and retries with `PAYMENT-SIGNATURE` → server verifies and fulfills. Tools that advertise pricing in `_meta.x402` are signed proactively, skipping the 402 round-trip.
643
717
 
644
718
  ### Wallet setup
645
719
 
@@ -683,31 +757,38 @@ mcpc x402 sign <base64-payment-required> --amount 1.00 --expiry 3600 --json
683
757
 
684
758
  **Options:**
685
759
 
686
- | Option | Description |
687
- | -------------------- | ------------------------------------------------------------- |
688
- | `--amount <usd>` | Override the payment amount in USD (e.g. `0.50` for $0.50) |
689
- | `--expiry <seconds>` | Override the payment expiry in seconds from now (e.g. `3600`) |
760
+ | Option | Description |
761
+ | -------------------- | ----------------------------------------------------------------------- |
762
+ | `--amount <usd>` | Override the payment amount in USD (e.g. `0.50` for $0.50) |
763
+ | `--expiry <seconds>` | Override the payment expiry in seconds from now (e.g. `3600`) |
764
+ | `--scheme <val>` | Scheme preference: `auto` (default, upto > exact), `upto`, or `exact` |
765
+ | `--no-approve` | For `upto`, skip checking and auto-approving on-chain Permit2 allowance |
690
766
 
691
767
  The command outputs the signed `PAYMENT-SIGNATURE` header value and an MCP config snippet
692
768
  that can be used directly with other MCP clients.
693
769
 
694
770
  ### Using x402 with MCP servers
695
771
 
696
- Pass the `--x402` flag when connecting to a session or running direct commands:
772
+ Pass the `--x402` flag when connecting to a session. It accepts an optional scheme preference
773
+ (`auto`, `upto`, or `exact`); bare `--x402` defaults to `auto`.
697
774
 
698
775
  ```bash
699
- # Create a session with x402 payment support
776
+ # Create a session with x402 payment support (auto picks the best advertised scheme)
700
777
  mcpc connect mcp.apify.com @apify --x402
701
778
 
702
- # The session now automatically handles 402 responses
779
+ # Pin a specific scheme position doesn't matter, before or after positional args
780
+ mcpc connect --x402 upto mcp.apify.com @apify
781
+ mcpc connect mcp.apify.com @apify --x402 exact
782
+
783
+ # The session now automatically handles 402 responses using your preference
703
784
  mcpc @apify tools-call expensive-tool query:="hello"
704
785
 
705
- # Restart a session with x402 enabled
706
- mcpc @apify restart --x402
786
+ # Restart re-uses the saved scheme from sessions.json — no need to repeat the flag
787
+ mcpc @apify restart
707
788
  ```
708
789
 
709
790
  When `--x402` is active, a fetch middleware wraps all HTTP requests to the MCP server.
710
- If any request returns HTTP 402, the middleware transparently signs and retries.
791
+ If any request returns HTTP 402, the middleware transparently signs and retries. Your scheme preference is persisted in `sessions.json` and reused on every reconnect or restart.
711
792
 
712
793
  ### Supported networks
713
794
 
@@ -747,21 +828,22 @@ The bridge process manages the full MCP session lifecycle:
747
828
 
748
829
  ### MCP feature support
749
830
 
750
- | **Feature** | **Status** |
751
- | :------------------------------------------------- | :-------------------------------- |
752
- | 📖 [**Instructions**](#server-instructions) | ✅ Supported |
753
- | 🔧 [**Tools**](#tools) | ✅ Supported |
754
- | 💬 [**Prompts**](#prompts) | ✅ Supported |
755
- | 📦 [**Resources**](#resources) | ✅ Supported |
756
- | 📝 [**Logging**](#server-logs) | Supported |
757
- | 🔔 [**Notifications**](#list-change-notifications) | ✅ Supported |
758
- | 📄 [**Pagination**](#pagination) | ✅ Supported |
759
- | 🏓 [**Ping**](#ping) | ✅ Supported |
760
- | [**Async tasks**](#async-tasks) | ✅ Supported |
761
- | 📁 **Roots** | 🚧 Planned |
762
- | **Elicitation** | 🚧 Planned |
763
- | 🔤 **Completion** | 🚧 Planned |
764
- | 🤖 **Sampling** | Not applicable (no LLM access) |
831
+ | **Feature** | **Status** |
832
+ | :------------------------------------------------- | :--------------------------------- |
833
+ | 📖 [**Instructions**](#server-instructions) | ✅ Supported |
834
+ | 🔧 [**Tools**](#tools) | ✅ Supported |
835
+ | 💬 [**Prompts**](#prompts) | ✅ Supported |
836
+ | 📦 [**Resources**](#resources) | ✅ Supported |
837
+ | 🧠 [**Skills**](#skills) | 🧪 Experimental (SEP-2640) |
838
+ | 📝 [**Logging**](#server-logs) | ✅ Supported (deprecated by MCP) |
839
+ | 🔔 [**Notifications**](#list-change-notifications) | ✅ Supported |
840
+ | 📄 [**Pagination**](#pagination) | ✅ Supported |
841
+ | 🏓 [**Ping**](#ping) | ✅ Supported |
842
+ | [**Async tasks**](#async-tasks) | Supported |
843
+ | 📁 **Roots** | Not planned (deprecated by MCP) |
844
+ | **Elicitation** | 🚧 Planned |
845
+ | 🔤 **Completion** | 🚧 Planned |
846
+ | 🤖 **Sampling** | ❌ Not applicable (no LLM access) |
765
847
 
766
848
  #### Server instructions
767
849
 
@@ -863,6 +945,41 @@ mcpc @apify resources-subscribe "https://api.example.com/data"
863
945
  mcpc @apify resources-templates-list
864
946
  ```
865
947
 
948
+ #### Skills
949
+
950
+ > 🧪 **Experimental.** Implements the draft [MCP skills extension (SEP-2640)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640).
951
+ > The spec is in active iteration; the index shape, recognized entry types, and capability key may change.
952
+
953
+ [Agent Skills](https://agentskills.io/) are reusable markdown workflow instructions (`SKILL.md` with YAML frontmatter)
954
+ that AI agents load on demand. `mcpc` lets you discover and pull skills served by any MCP server that exposes
955
+ them under the `skill://` URI convention — no SDK changes required, since skills are just resources:
956
+
957
+ ```bash
958
+ # List skills exposed by the server (tries skill://index.json, falls back to scanning skill://*/SKILL.md)
959
+ mcpc @apify skills-list
960
+
961
+ # Read a skill's SKILL.md by bare name, nested path, or full URI
962
+ mcpc @apify skills-get git-workflow
963
+ mcpc @apify skills-get acme/billing/refunds
964
+ mcpc @apify skills-get skill://git-workflow/SKILL.md
965
+
966
+ # Print just the markdown (no header/fences) — pipe straight to an LLM or a file
967
+ mcpc @apify skills-get git-workflow --raw > /tmp/skill.md
968
+
969
+ # JSON for scripts: [{ name, description, type, url }]
970
+ mcpc --json @apify skills-list | jq '.[].name'
971
+ ```
972
+
973
+ Recognized index entry types (per SEP-2640): `skill-md` (concrete skill), `mcp-resource-template`
974
+ (parameterized namespace), and `archive` (`.tar.gz`/`.zip` bundle — fetch the URL via `resources-read`).
975
+ Entries with an unrecognized `type` are silently skipped.
976
+
977
+ Skills appear under capabilities in `mcpc @session` output when a server advertises the extension
978
+ under either `capabilities.extensions["io.modelcontextprotocol/skills"]` (per spec) or
979
+ `capabilities.experimental["io.modelcontextprotocol/skills"]` (the SDK-preserved escape hatch some
980
+ SDKs still use). Skill content is treated as untrusted input — `mcpc` only reads and prints it; it
981
+ never executes hooks, scripts, or other frontmatter-declared behavior.
982
+
866
983
  #### List change notifications
867
984
 
868
985
  When connected via a [session](#sessions), `mcpc` automatically handles `list_changed`
@@ -965,6 +1082,10 @@ mcpc connect .vscode/mcp.json:apify @my-apify
965
1082
  mcpc @my-apify tools-list
966
1083
  ```
967
1084
 
1085
+ `mcpc` also finds these files for you: run `mcpc connect` with no arguments to auto-discover config
1086
+ files in standard locations and connect every server, or pass a file without an entry to connect all
1087
+ of its servers. See [Server formats](#server-formats).
1088
+
968
1089
  **Example MCP config JSON file:**
969
1090
 
970
1091
  ```json
@@ -1128,9 +1249,11 @@ This causes `mcpc` to print detailed debug messages to stderr.
1128
1249
 
1129
1250
  ### Logs
1130
1251
 
1131
- The background bridge processes log to `~/.mcpc/logs/bridge-@<session>.log`.
1132
- The main `mcpc` process doesn't save log files, but supports [verbose mode](#verbose-mode).
1133
- `mcpc` automatically rotates log files: keep last 10MB per session, max 5 files.
1252
+ View the bridge log for a session with `mcpc @<session> logs` (run with
1253
+ `--help` for `--follow`, `-n`, and `--since` options). The underlying file
1254
+ lives at `~/.mcpc/logs/bridge-@<session>.log` and is rotated automatically
1255
+ (10MB per file, max 5 files). The main `mcpc` process doesn't save log
1256
+ files, but supports [verbose mode](#verbose-mode).
1134
1257
 
1135
1258
  ### Troubleshooting
1136
1259
 
@@ -2,10 +2,11 @@
2
2
  import { initProxy, proxyFetch } from '../lib/proxy.js';
3
3
  import { createServer } from 'net';
4
4
  import { unlink } from 'fs/promises';
5
- import { createMcpClient } from '../core/index.js';
6
- import { KEEPALIVE_INTERVAL_MS } from '../lib/types.js';
5
+ import { dirname } from 'path';
6
+ import { createMcpClient, buildClientCapabilities } from '../core/index.js';
7
+ import { KEEPALIVE_INTERVAL_MS, X402_SCHEME_PREFERENCES } from '../lib/types.js';
7
8
  import { createLogger, setVerbose, initFileLogger, closeFileLogger } from '../lib/index.js';
8
- import { fileExists, getBridgesDir, getSocketPath, ensureDir, cleanupOrphanedLogFiles, isSessionExpiredError, } from '../lib/index.js';
9
+ import { fileExists, getSocketPath, ensureDir, cleanupOrphanedLogFiles, isSessionExpiredError, StderrTail, } from '../lib/index.js';
9
10
  import { ClientError, ServerError, NetworkError, AuthError, isAuthenticationError, } from '../lib/index.js';
10
11
  import { getSession, loadSessions, updateSession } from '../lib/sessions.js';
11
12
  import { OAuthTokenManager } from '../lib/auth/oauth-token-manager.js';
@@ -43,10 +44,7 @@ class BridgeProcess {
43
44
  mcpClientReady;
44
45
  mcpClientReadyResolver;
45
46
  mcpClientReadyRejecter;
46
- stderrTail = [];
47
- stderrTailChars = 0;
48
- static STDERR_TAIL_MAX_LINES = 50;
49
- static STDERR_TAIL_MAX_CHARS = 8_000;
47
+ stderrTail = new StderrTail();
50
48
  constructor(options) {
51
49
  this.options = options;
52
50
  this.socketPath = getSocketPath(options.sessionName, process.pid);
@@ -252,9 +250,8 @@ class BridgeProcess {
252
250
  if (isAuthenticationError(errorMsg) && !(error instanceof AuthError)) {
253
251
  classifiedError = new AuthError(errorMsg);
254
252
  }
255
- if (this.stderrTail.length > 0) {
256
- const tail = this.stderrTail.map((l) => ` ${l}`).join('\n');
257
- classifiedError.message = `${classifiedError.message}\n\nRecent server stderr:\n${tail}`;
253
+ if (this.stderrTail.count > 0) {
254
+ classifiedError.message = `${classifiedError.message}\n\nRecent server stderr:\n${this.stderrTail.format()}`;
258
255
  }
259
256
  this.mcpClientReadyRejecter(classifiedError);
260
257
  if (isSessionExpiredError(errorMsg, {
@@ -295,18 +292,9 @@ class BridgeProcess {
295
292
  }
296
293
  }
297
294
  recordServerStderr(line) {
298
- const trimmed = line.length > BridgeProcess.STDERR_TAIL_MAX_CHARS
299
- ? line.slice(0, BridgeProcess.STDERR_TAIL_MAX_CHARS) + '…'
300
- : line;
301
- logger.info(`[server stderr] ${trimmed}`);
302
- this.stderrTail.push(trimmed);
303
- this.stderrTailChars += trimmed.length + 1;
304
- while (this.stderrTail.length > BridgeProcess.STDERR_TAIL_MAX_LINES ||
305
- (this.stderrTail.length > 1 && this.stderrTailChars > BridgeProcess.STDERR_TAIL_MAX_CHARS)) {
306
- const removed = this.stderrTail.shift();
307
- if (removed === undefined)
308
- break;
309
- this.stderrTailChars -= removed.length + 1;
295
+ const stored = this.stderrTail.add(line);
296
+ if (stored !== null) {
297
+ logger.info(`[server stderr] ${stored}`);
310
298
  }
311
299
  }
312
300
  broadcastNotification(method, params) {
@@ -370,22 +358,13 @@ class BridgeProcess {
370
358
  wallet,
371
359
  getToolByName,
372
360
  paymentCache: this.x402PaymentCache,
361
+ ...(this.options.x402 && { schemePreference: this.options.x402 }),
373
362
  });
374
363
  }
375
364
  const clientConfig = {
376
365
  clientInfo: { name: 'mcpc', version: mcpcVersion },
377
366
  serverConfig,
378
- capabilities: {
379
- roots: { listChanged: true },
380
- sampling: {},
381
- tasks: {
382
- list: {},
383
- cancel: {},
384
- requests: {
385
- sampling: { createMessage: {} },
386
- },
387
- },
388
- },
367
+ capabilities: buildClientCapabilities(),
389
368
  ...(this.authProvider && { authProvider: this.authProvider }),
390
369
  ...(this.options.mcpSessionId && { mcpSessionId: this.options.mcpSessionId }),
391
370
  ...(customFetch && { customFetch }),
@@ -462,6 +441,9 @@ class BridgeProcess {
462
441
  if (serverDetails.protocolVersion) {
463
442
  sessionUpdate.protocolVersion = serverDetails.protocolVersion;
464
443
  }
444
+ if (serverDetails.statefulness) {
445
+ sessionUpdate.statefulness = serverDetails.statefulness;
446
+ }
465
447
  if (serverDetails.serverInfo) {
466
448
  sessionUpdate.serverInfo = serverDetails.serverInfo;
467
449
  }
@@ -551,8 +533,9 @@ class BridgeProcess {
551
533
  return;
552
534
  }
553
535
  }
536
+ const hadActiveSession = !!(this.options.mcpSessionId || this.client?.getMcpSessionId());
554
537
  let status = null;
555
- if (isSessionExpiredError(error.message, { hadActiveSession: true })) {
538
+ if (isSessionExpiredError(error.message, { hadActiveSession })) {
556
539
  logger.warn('Session appears to be expired, marking as expired and shutting down');
557
540
  status = 'expired';
558
541
  }
@@ -599,7 +582,7 @@ class BridgeProcess {
599
582
  }
600
583
  async createSocketServer() {
601
584
  if (process.platform !== 'win32') {
602
- await ensureDir(getBridgesDir());
585
+ await ensureDir(dirname(this.socketPath));
603
586
  if (await fileExists(this.socketPath)) {
604
587
  logger.debug(`Removing existing socket: ${this.socketPath}`);
605
588
  await unlink(this.socketPath);
@@ -712,7 +695,7 @@ class BridgeProcess {
712
695
  const paymentRequired = extractPaymentRequiredFromResult(toolResult);
713
696
  if (!paymentRequired)
714
697
  return { handled: false };
715
- const parsed = extractAcceptFromPaymentRequired(paymentRequired);
698
+ const parsed = extractAcceptFromPaymentRequired(paymentRequired, this.options.x402);
716
699
  if (!parsed) {
717
700
  logger.warn('Payment-required tool result but could not extract supported payment terms');
718
701
  return { handled: false };
@@ -726,7 +709,7 @@ class BridgeProcess {
726
709
  resource: parsed.resource,
727
710
  });
728
711
  this.x402PaymentCache.signature = signed.paymentSignatureBase64;
729
- logger.debug(`Fresh payment signed for retry: $${signed.amountUsd.toFixed(4)} to ${signed.to} on ${signed.networkLabel}`);
712
+ logger.debug(`Fresh payment signed for retry: $${signed.amountUsd.toFixed(6)} to ${signed.to} on ${signed.networkLabel}`);
730
713
  }
731
714
  catch (signError) {
732
715
  logger.warn('Failed to sign fresh payment for 402 retry:', signError);
@@ -1084,7 +1067,7 @@ class BridgeProcess {
1084
1067
  async function main() {
1085
1068
  const args = process.argv.slice(2);
1086
1069
  if (args.length < 2) {
1087
- console.error('Usage: mcpc-bridge <sessionName> <transportConfigJson> [--verbose] [--profile <name>] [--proxy-host <host>] [--proxy-port <port>] [--mcp-session-id <id>] [--x402] [--insecure]');
1070
+ console.error('Usage: mcpc-bridge <sessionName> <transportConfigJson> [--verbose] [--profile <name>] [--proxy-host <host>] [--proxy-port <port>] [--mcp-session-id <id>] [--x402 <auto|upto|exact>] [--insecure]');
1088
1071
  process.exit(1);
1089
1072
  }
1090
1073
  const sessionName = args[0];
@@ -1110,7 +1093,16 @@ async function main() {
1110
1093
  if (mcpSessionIdIndex !== -1 && args[mcpSessionIdIndex + 1]) {
1111
1094
  mcpSessionId = args[mcpSessionIdIndex + 1];
1112
1095
  }
1113
- const x402 = args.includes('--x402');
1096
+ let x402;
1097
+ const x402Index = args.indexOf('--x402');
1098
+ if (x402Index !== -1) {
1099
+ const value = args[x402Index + 1];
1100
+ if (value === undefined || !X402_SCHEME_PREFERENCES.includes(value)) {
1101
+ console.error(`--x402 requires a scheme: ${X402_SCHEME_PREFERENCES.join('|')} (got ${value ?? '<missing>'})`);
1102
+ process.exit(1);
1103
+ }
1104
+ x402 = value;
1105
+ }
1114
1106
  const insecure = args.includes('--insecure');
1115
1107
  initProxy({ insecure });
1116
1108
  try {
@@ -1129,7 +1121,7 @@ async function main() {
1129
1121
  bridgeOptions.mcpSessionId = mcpSessionId;
1130
1122
  }
1131
1123
  if (x402) {
1132
- bridgeOptions.x402 = true;
1124
+ bridgeOptions.x402 = x402;
1133
1125
  }
1134
1126
  if (insecure) {
1135
1127
  bridgeOptions.insecure = true;