@apify/mcpc 0.1.11-beta.4 → 0.1.11-beta.5

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 (63) hide show
  1. package/CHANGELOG.md +43 -35
  2. package/README.md +62 -26
  3. package/dist/bridge/index.js +61 -41
  4. package/dist/bridge/index.js.map +1 -1
  5. package/dist/cli/commands/sessions.d.ts.map +1 -1
  6. package/dist/cli/commands/sessions.js +157 -158
  7. package/dist/cli/commands/sessions.js.map +1 -1
  8. package/dist/cli/commands/tools.d.ts +1 -1
  9. package/dist/cli/commands/tools.d.ts.map +1 -1
  10. package/dist/cli/commands/tools.js +77 -17
  11. package/dist/cli/commands/tools.js.map +1 -1
  12. package/dist/cli/commands/x402.d.ts.map +1 -1
  13. package/dist/cli/commands/x402.js +20 -6
  14. package/dist/cli/commands/x402.js.map +1 -1
  15. package/dist/cli/helpers.d.ts +1 -1
  16. package/dist/cli/helpers.d.ts.map +1 -1
  17. package/dist/cli/helpers.js +4 -18
  18. package/dist/cli/helpers.js.map +1 -1
  19. package/dist/cli/index.js +45 -38
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/cli/output.d.ts +4 -2
  22. package/dist/cli/output.d.ts.map +1 -1
  23. package/dist/cli/output.js +74 -23
  24. package/dist/cli/output.js.map +1 -1
  25. package/dist/cli/parser.d.ts.map +1 -1
  26. package/dist/cli/parser.js +0 -2
  27. package/dist/cli/parser.js.map +1 -1
  28. package/dist/cli/shell.js +4 -4
  29. package/dist/cli/shell.js.map +1 -1
  30. package/dist/core/mcp-client.d.ts +10 -4
  31. package/dist/core/mcp-client.d.ts.map +1 -1
  32. package/dist/core/mcp-client.js +75 -7
  33. package/dist/core/mcp-client.js.map +1 -1
  34. package/dist/core/transports.d.ts.map +1 -1
  35. package/dist/core/transports.js +3 -0
  36. package/dist/core/transports.js.map +1 -1
  37. package/dist/lib/auth/keychain.d.ts.map +1 -1
  38. package/dist/lib/auth/keychain.js +40 -15
  39. package/dist/lib/auth/keychain.js.map +1 -1
  40. package/dist/lib/auth/oauth-flow.d.ts.map +1 -1
  41. package/dist/lib/auth/oauth-flow.js +87 -10
  42. package/dist/lib/auth/oauth-flow.js.map +1 -1
  43. package/dist/lib/auth/oauth-utils.d.ts.map +1 -1
  44. package/dist/lib/auth/oauth-utils.js +3 -2
  45. package/dist/lib/auth/oauth-utils.js.map +1 -1
  46. package/dist/lib/bridge-client.js +1 -1
  47. package/dist/lib/bridge-manager.js +1 -1
  48. package/dist/lib/bridge-manager.js.map +1 -1
  49. package/dist/lib/proxy.d.ts +6 -0
  50. package/dist/lib/proxy.d.ts.map +1 -0
  51. package/dist/lib/proxy.js +13 -0
  52. package/dist/lib/proxy.js.map +1 -0
  53. package/dist/lib/session-client.d.ts +6 -3
  54. package/dist/lib/session-client.d.ts.map +1 -1
  55. package/dist/lib/session-client.js +13 -6
  56. package/dist/lib/session-client.js.map +1 -1
  57. package/dist/lib/types.d.ts +9 -3
  58. package/dist/lib/types.d.ts.map +1 -1
  59. package/dist/lib/x402/fetch-middleware.d.ts.map +1 -1
  60. package/dist/lib/x402/fetch-middleware.js +41 -8
  61. package/dist/lib/x402/fetch-middleware.js.map +1 -1
  62. package/docs/TODOs.md +0 -2
  63. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,42 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ### Changed
11
- - Revised session states: auth failures (401/403) now show as `unauthorized` (separate from `expired` which is for session ID expiry), with actionable login guidance; new `disconnected` display state surfaces when bridge is alive but server has been unreachable for >2 minutes
12
- - `DISCONNECTED_THRESHOLD_MS` is now derived from `KEEPALIVE_INTERVAL_MS` (2× ping interval + 5s buffer) via shared constants, eliminating duplicate magic numbers
13
-
14
- ### Added
15
-
16
- - `--async` flag for `tools-call` to opt-in to async task execution (experimental) with a progress spinner showing elapsed time and server status messages in human mode
17
- - `--detach` flag for `tools-call` to start an async task and return the task ID immediately without waiting for completion (implies `--async`)
18
- - New `tasks-list`, `tasks-get`, `tasks-cancel` commands for managing async tasks on the server
19
- - Task capability and `execution.taskSupport` displayed in `tools-get` and server info
20
- - E2E test server now includes a `slow-task` tool that supports async task execution
21
- - E2E tests for async task execution, detached mode, task management (list/get/cancel), and synchronous fallback
22
- - `[async]` indicator in `tools-list` output for tools that support async task execution
23
- - `--insecure` global option to skip TLS certificate verification, for MCP servers with self-signed certificates
24
- - E2E test for `--insecure` flag using a self-signed HTTPS test server wrapper
25
- - `--client-id` and `--client-secret` options for `mcpc login` command, for servers that don't support dynamic client registration
26
- - `mcpc close @session`, `mcpc restart @session`, and `mcpc shell @session` command-first syntax as alternatives to `mcpc @session close/restart/shell`
27
- - E2E tests now run under the Bun runtime (in addition to Node.js); use `./test/e2e/run.sh --runtime bun` or `npm run test:e2e:bun`
28
- - `--no-profile` option for `connect` command to skip OAuth profile auto-detection and connect anonymously
29
-
30
10
  ### Fixed
31
- - `--async` and `--detach` tool calls now correctly send task creation parameters to the server, fixing "task stream ended without creating a task" errors
32
- - File lock retries now use randomized exponential backoff to reduce contention when multiple processes compete for the same lock
33
- - Explicit `--header "Authorization: Bearer ..."` is no longer silently ignored when a default OAuth profile exists for the same server; explicit CLI headers now take precedence over auto-detected profiles
34
- - Combining `--profile` with `--header "Authorization: ..."` now returns a clear error instead of silently stripping the header
35
- - `logTarget` no longer prints a misleading `[→ @name (HTTP)]` prefix when a session doesn't exist; only the error message is shown
36
- - `logging-set-level` JSON output no longer includes a `success` field; output is now `{"level":"<level>"}` consistent with the project's convention of indicating errors via exit codes
37
- - `--header` / `-H` option is now specific to the `connect` command instead of being shown as a global option in `mcpc --help`
38
- - Bridge now forwards `logging/message` notifications from the MCP server to connected clients, so `logging-set-level` actually takes effect in interactive shell sessions
39
- - IPC buffer between CLI and bridge process is now capped at 10 MB; sockets are destroyed if the limit is exceeded, preventing unbounded memory growth
40
- - `validateOptions()` no longer includes subcommand-specific options (`--full`, `--x402`, `--proxy`, etc.) in global known-options list; misplaced flags now produce clear "Unknown option" errors instead of confusing Commander rejections
41
- - Sessions requiring authentication now correctly show as `expired` instead of `live` when the server rejects unauthenticated connections
42
- - Auth errors wrapped in `NetworkError` by bridge IPC are now detected on first health check, avoiding unnecessary bridge restart
43
- - Fixed flaky E2E invariant check that failed when `lastSeenAt` changed between `--json` and `--json --verbose` calls
44
- - `--timeout` flag now correctly propagates to MCP requests via session bridge
45
- - `parseServerArg()` now handles well Windows drive-letter config paths as well as other ambiguous cases
11
+
12
+ - Fixed auth loss when reconnecting an unauthorized session via `mcpc connect` — the `unauthorized` status was not cleared, causing all subsequent operations to fail with "Authentication required by server" even after successful reconnection
13
+ - HTTP proxy support (`HTTP_PROXY`/`HTTPS_PROXY` env vars) now works for MCP server connections, OAuth token refresh, and x402 payment signing — previously the MCP SDK transport and OAuth calls bypassed the global proxy dispatcher
46
14
 
47
15
  ### Changed
48
16
 
@@ -59,7 +27,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
59
27
 
60
28
  Direct one-shot URL access (e.g. `mcpc mcp.apify.com tools-list`) is removed; create a session first with `mcpc connect`.
61
29
 
30
+ - Renamed `--async` flag to `--task` in `tools-call` for consistency with MCP specification; `--detach` now implies `--task`
31
+ - Revised session states: auth failures (401/403) now show as `unauthorized` (separate from `expired` which is for session ID expiry), with actionable login guidance; new `disconnected` state surfaces when bridge is alive but server has been unreachable for >2 minutes
32
+ - When `--profile` is not specified, only the `default` profile is used; non-default profiles require an explicit `--profile` flag
62
33
  - `@napi-rs/keyring` native addon is now loaded lazily: `mcpc` starts and works normally even when `libsecret` (Linux) or the addon itself is missing; a one-time warning is emitted and credentials fall back to `~/.mcpc/credentials.json` (mode 0600)
34
+ - `x402 sign` now takes the PAYMENT-REQUIRED header as a positional argument instead of `-r` flag (e.g. `mcpc x402 sign <base64>`)
35
+ - `tools-list` and `tools-get` now show `[task:optional]`, `[task:required]`, or `[task:forbidden]` instead of `[async]`
36
+ - Tools cache now fetches all pages on startup and on `tools/list_changed` notifications; `tools-get` uses cached list first and only re-fetches if the tool is not found
37
+ - `--header` / `-H` option is now specific to the `connect` command instead of being shown as a global option
38
+
39
+ ### Added
40
+
41
+ - New `tasks-list`, `tasks-get`, `tasks-cancel` commands for managing async tasks on the server
42
+ - `--task` flag for `tools-call` to opt-in to task execution with a progress spinner showing elapsed time, server status messages, and progress notifications in human mode
43
+ - `--detach` flag for `tools-call` to start a task and return the task ID immediately without waiting for completion (implies `--task`)
44
+ - Press ESC during `--task` execution to detach on the fly — the task continues in the background and the task ID is printed
45
+ - `--insecure` global option to skip TLS certificate verification, for MCP servers with self-signed certificates
46
+ - `--client-id` and `--client-secret` options for `mcpc login` command, for servers that don't support dynamic client registration
47
+ - `--no-profile` option for `connect` command to skip OAuth profile auto-detection and connect anonymously
48
+ - `mcpc close @session`, `mcpc restart @session`, and `mcpc shell @session` command-first syntax as alternatives to `mcpc @session close/restart/shell`
49
+ - `mcpc login` now falls back to accepting a pasted callback URL when the browser cannot be opened (e.g. headless servers, containers)
50
+ - `tools-list` now shows inline parameter signatures (e.g. `read_file(path: string, +4 optional)`) for quick scanning without `--full`
51
+ - `mcpc @session` now shows available tools list from bridge cache (no extra server call)
52
+ - Task capability and `execution.taskSupport` displayed in `tools-get` and server info
53
+ - x402 payments are now also sent via the MCP `_meta["x402/payment"]` field on `tools/call` requests, in addition to the existing HTTP header
54
+
55
+ ### Fixed
56
+
57
+ - Explicit `--header "Authorization: Bearer ..."` is no longer silently ignored when a default OAuth profile exists for the same server; explicit CLI headers now take precedence over auto-detected profiles
58
+ - Combining `--profile` with `--header "Authorization: ..."` now returns a clear error instead of silently stripping the header
59
+ - Session restart now auto-detects the `default` OAuth profile created after the session was established, fixing the `login` then `restart` flow for unauthorized sessions
60
+ - Sessions requiring authentication now correctly show as `expired` instead of `live` when the server rejects unauthenticated connections
61
+ - Auth errors wrapped in `NetworkError` by bridge IPC are now detected on first health check, avoiding unnecessary bridge restart
62
+ - `--timeout` flag now correctly propagates to MCP requests via session bridge
63
+ - `--task` and `--detach` tool calls now correctly send task creation parameters to the server
64
+ - Bridge now forwards `logging/message` notifications from the MCP server to connected clients, so `logging-set-level` actually takes effect in interactive shell sessions
65
+ - IPC buffer between CLI and bridge process is now capped at 10 MB, preventing unbounded memory growth
66
+ - `parseServerArg()` now handles Windows drive-letter config paths and other ambiguous cases
67
+ - Misplaced subcommand-specific flags (e.g. `--full`, `--proxy`) now produce clear "Unknown option" errors instead of confusing rejections
68
+ - `logging-set-level` JSON output no longer includes a redundant `success` field
69
+ - `logTarget` no longer prints a misleading prefix when a session doesn't exist
70
+ - File lock retries now use randomized exponential backoff to reduce contention
63
71
 
64
72
  ## [0.1.10] - 2026-03-01
65
73
 
package/README.md CHANGED
@@ -130,7 +130,7 @@ Commands:
130
130
  logout <server> Delete an authentication profile for a server
131
131
  clean [resources...] Clean up mcpc data (sessions, profiles, logs, all)
132
132
  x402 [subcommand] [args...] Configure an x402 payment wallet (EXPERIMENTAL)
133
- help [command] Show help for a specific command
133
+ help [command] [subcommand] Show help for a specific command
134
134
 
135
135
  MCP session commands (after connecting):
136
136
  <@session> Show MCP server info and capabilities
@@ -316,29 +316,34 @@ Still, sessions can fail due to network disconnects, bridge process crash, or se
316
316
 
317
317
  **Session states:**
318
318
 
319
- | State | Meaning |
320
- | ---------------- | --------------------------------------------------------------------------------------------- |
321
- | 🟢 **`live`** | Bridge process is running; server might or might not be operational |
322
- | 🟡 **`crashed`** | Bridge process crashed or was killed; will auto-restart on next use |
323
- | 🔴 **`expired`** | Server rejected the session (auth failed, session ID invalid); requires `close` and reconnect |
319
+ | State | Meaning |
320
+ | --------------------- | ------------------------------------------------------------------------------------------------- |
321
+ | 🟢 **`live`** | Bridge process running and server responding |
322
+ | 🟡 **`disconnected`** | Bridge process running but server unreachable; auto-recovers when server responds |
323
+ | 🟡 **`crashed`** | Bridge process crashed or was killed; auto-restarts on next use |
324
+ | 🔴 **`unauthorized`** | Server rejected authentication (401/403) or token refresh failed; requires `login` then `restart` |
325
+ | 🔴 **`expired`** | Server rejected session ID (404); requires `restart` |
324
326
 
325
327
  Here's how `mcpc` handles various bridge process and server connection states:
326
328
 
327
329
  - While the **bridge process is running**:
328
330
  - If **server positively responds** to pings, the session is marked 🟢 **`live`**, and everything is fine.
329
- - If **server stops responding**, the bridge will keep trying to reconnect in the background.
330
- - If **server negatively responds** to indicate `MCP-Session-Id` is no longer valid
331
- or authentication permanently failed (HTTP 401 or 403),
332
- the bridge process will flag the session as 🔴 **`expired`** and **terminate** to avoid wasting resources.
333
- Any future attempt to use the session (`mcpc @my-session ...`) will fail.
331
+ - If **server stops responding**, the session is marked 🟡 **`disconnected`**.
332
+ The bridge will keep trying to reconnect in the background and will return to 🟢 **`live`** once the server responds again.
333
+ - If **server rejects authentication** (HTTP 401 or 403) or token refresh fails,
334
+ the session is marked 🔴 **`unauthorized`**.
335
+ You need to re-authenticate with `mcpc login <server>` and then `mcpc @my-session restart`.
336
+ - If **server rejects the session ID** (HTTP 404), indicating the MCP session is no longer valid,
337
+ the session is marked 🔴 **`expired`**.
338
+ You need to restart the session with `mcpc @my-session restart` to establish a new connection.
334
339
  - If the **bridge process crashes**, `mcpc` will mark the session as 🟡 **`crashed`** on first use.
335
340
  Next time you run `mcpc @my-session ...`, it will attempt to restart the bridge process.
336
341
  - If bridge **restart succeeds**, everything starts again (see above).
337
342
  - If bridge **restart fails**, `mcpc @my-session ...` returns error, and session remains marked 🟡 **`crashed`**.
338
343
 
339
344
  Note that `mcpc` never automatically removes sessions from the list.
340
- Instead, it keeps them flagged as 🟡 **`crashed`** or 🔴 **`expired`**,
341
- and any future attempts to use them will fail.
345
+ Instead, it keeps them flagged as 🟡 **`crashed`**, 🔴 **`unauthorized`**, or 🔴 **`expired`**,
346
+ and any future attempts to use them will show the appropriate error with recovery instructions.
342
347
 
343
348
  To **remove the session from the list**, you need to explicitly close it:
344
349
 
@@ -705,6 +710,35 @@ mcpc x402 remove
705
710
 
706
711
  After creating a wallet, **fund it with USDC on Base** (mainnet or Sepolia testnet) to enable payments.
707
712
 
713
+ ### Manual payment signing
714
+
715
+ You can manually sign a payment from a server's `PAYMENT-REQUIRED` header using `x402 sign`.
716
+ This is useful for pre-signing payments or integrating with tools outside of `mcpc`.
717
+
718
+ ```bash
719
+ # Sign a payment using the base64-encoded PAYMENT-REQUIRED header
720
+ mcpc x402 sign <base64-payment-required>
721
+
722
+ # Override the amount (in USD, e.g. 2.50 = $2.50)
723
+ mcpc x402 sign <base64-payment-required> --amount 2.50
724
+
725
+ # Override the expiry (in seconds from now)
726
+ mcpc x402 sign <base64-payment-required> --expiry 7200
727
+
728
+ # Combine overrides and use JSON output
729
+ mcpc x402 sign <base64-payment-required> --amount 1.00 --expiry 3600 --json
730
+ ```
731
+
732
+ **Options:**
733
+
734
+ | Option | Description |
735
+ | ------------------- | ---------------------------------------------------------------- |
736
+ | `--amount <usd>` | Override the payment amount in USD (e.g. `0.50` for $0.50) |
737
+ | `--expiry <seconds>`| Override the payment expiry in seconds from now (e.g. `3600`) |
738
+
739
+ The command outputs the signed `PAYMENT-SIGNATURE` header value and an MCP config snippet
740
+ that can be used directly with other MCP clients.
741
+
708
742
  ### Using x402 with MCP servers
709
743
 
710
744
  Pass the `--x402` flag when connecting to a session or running direct commands:
@@ -1147,23 +1181,25 @@ See [CONTRIBUTING](./CONTRIBUTING.md) for development setup, architecture overvi
1147
1181
 
1148
1182
  <!-- Stars and activity as of March 2026. -->
1149
1183
 
1150
- | Tool | Lang | Stars | Active | Tools | Resources | Prompts | Code mode | Sessions | OAuth | Stdio | HTTP | Tool search | LLM |
1151
- |---|---|--:|---|---|---|---|---|---|---|---|---|---|---|
1152
- | **[apify/mcpc](https://github.com/apify/mcpc)** | TS | ~350 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
1153
- | [steipete/mcporter](https://github.com/steipete/mcporter) | TS | ~2.6k | ✅ | ✅ | — | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
1154
- | [IBM/mcp-cli](https://github.com/IBM/mcp-cli) | Python | ~1.9k | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ |
1155
- | [f/mcptools](https://github.com/f/mcptools) | Go | ~1.5k | ⚠️ | ✅ | ✅ | ✅ | ✅ | — | — | ✅ | ✅ | — | — |
1156
- | [philschmid/mcp-cli](https://github.com/philschmid/mcp-cli) | TS | ~950 | ✅ | ✅ | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — |
1157
- | [adhikasp/mcp-client-cli](https://github.com/adhikasp/mcp-client-cli) | Python | ~670 | ⚠️ | ✅ | ✅ | ✅ | — | — | — | ✅ | — | — | ✅ |
1158
- | [thellimist/clihub](https://github.com/thellimist/clihub) | Go | ~590 | ✅ | ✅ | — | — | — | — | ✅ | ✅ | ✅ | ✅ | — |
1159
- | [wong2/mcp-cli](https://github.com/wong2/mcp-cli) | JS | ~420 | ⚠️ | ✅ | ✅ | ✅ | — | — | ✅ | — | ✅ | — | — |
1160
- | [knowsuchagency/mcp2cli](https://github.com/knowsuchagency/mcp2cli) | Python | ~170 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — |
1161
- | [mcpshim/mcpshim](https://github.com/mcpshim/mcpshim) | Go | ~46 | ✅ | ✅ | — | — | ✅ | ✅ | ✅ | — | ✅ | ✅ | — |
1162
- | [EstebanForge/mcp-cli-ent](https://github.com/EstebanForge/mcp-cli-ent) | Go | ~13 | ✅ | ✅ | | — | | ✅ | — | ✅ | ✅ | ✅ | — |
1184
+ | Tool | Lang | Stars | Active | Tools | Resources | Prompts | Code mode | Sessions | OAuth | Stdio | HTTP | Tool search | LLM |
1185
+ | ----------------------------------------------------------------------- | ------ | ----: | ------ | ----- | --------- | ------- | --------- | -------- | ----- | ----- | ---- | ----------- | --- |
1186
+ | **[apify/mcpc](https://github.com/apify/mcpc)** | TS | ~350 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
1187
+ | [steipete/mcporter](https://github.com/steipete/mcporter) | TS | ~2.6k | ✅ | ✅ | — | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
1188
+ | [IBM/mcp-cli](https://github.com/IBM/mcp-cli) | Python | ~1.9k | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ |
1189
+ | [f/mcptools](https://github.com/f/mcptools) | Go | ~1.5k | ⚠️ | ✅ | ✅ | ✅ | ✅ | — | — | ✅ | ✅ | — | — |
1190
+ | [philschmid/mcp-cli](https://github.com/philschmid/mcp-cli) | TS | ~950 | ✅ | ✅ | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — |
1191
+ | [adhikasp/mcp-client-cli](https://github.com/adhikasp/mcp-client-cli) | Python | ~670 | ⚠️ | ✅ | ✅ | ✅ | — | — | — | ✅ | — | — | ✅ |
1192
+ | [thellimist/clihub](https://github.com/thellimist/clihub) | Go | ~590 | ✅ | ✅ | — | — | — | — | ✅ | ✅ | ✅ | ✅ | — |
1193
+ | [wong2/mcp-cli](https://github.com/wong2/mcp-cli) | JS | ~420 | ⚠️ | ✅ | ✅ | ✅ | — | — | ✅ | — | ✅ | — | — |
1194
+ | [knowsuchagency/mcp2cli](https://github.com/knowsuchagency/mcp2cli) | Python | ~170 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — |
1195
+ | [mcpshim/mcpshim](https://github.com/mcpshim/mcpshim) | Go | ~46 | ✅ | ✅ | — | — | ✅ | ✅ | ✅ | — | ✅ | ✅ | — |
1196
+ | [evantahler/mcpx](https://github.com/evantahler/mcpx) | TS | ~26 | ✅ | ✅ | | ✅ | ✅ | — | | ✅ | ✅ | ✅ | — |
1197
+ | [EstebanForge/mcp-cli-ent](https://github.com/EstebanForge/mcp-cli-ent) | Go | ~13 | ✅ | ✅ | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — |
1163
1198
 
1164
1199
  **Legend:** ✅ = supported, ⚠️ = stale (no commits in 3+ months), **LLM** = requires/uses an LLM.
1165
1200
 
1166
1201
  **Notes:**
1202
+
1167
1203
  - [thellimist/clihub](https://github.com/thellimist/clihub) is a code generator that compiles MCP tools into standalone CLI binaries, rather than a runtime client ([HN discussion](https://news.ycombinator.com/item?id=47157398)).
1168
1204
  - [knowsuchagency/mcp2cli](https://github.com/knowsuchagency/mcp2cli) also supports OpenAPI specs directly and uses a custom TOON encoding for token-efficient tool schemas.
1169
1205
  - [IBM/mcp-cli](https://github.com/IBM/mcp-cli) and [mcp-client-cli](https://github.com/adhikasp/mcp-client-cli) integrate an LLM (Ollama, OpenAI, etc.) for chat-style interaction, while the other tools are pure CLI clients.
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';
2
+ import { initProxy, proxyFetch } from '../lib/proxy.js';
3
3
  import { createServer } from 'net';
4
4
  import { unlink } from 'fs/promises';
5
5
  import { createMcpClient } from '../core/index.js';
6
6
  import { KEEPALIVE_INTERVAL_MS } from '../lib/types.js';
7
7
  import { createLogger, setVerbose, initFileLogger, closeFileLogger } from '../lib/index.js';
8
8
  import { fileExists, getBridgesDir, getSocketPath, ensureDir, cleanupOrphanedLogFiles, isSessionExpiredError, } from '../lib/index.js';
9
- import { ClientError, NetworkError, isAuthenticationError } from '../lib/index.js';
9
+ import { ClientError, NetworkError, AuthError, isAuthenticationError } from '../lib/index.js';
10
10
  import { getSession, loadSessions, updateSession } from '../lib/sessions.js';
11
11
  import { OAuthTokenManager } from '../lib/auth/oauth-token-manager.js';
12
12
  import { OAuthProvider } from '../lib/auth/oauth-provider.js';
@@ -33,7 +33,6 @@ class BridgeProcess {
33
33
  authProvider = null;
34
34
  headers = null;
35
35
  x402Wallet = null;
36
- cachedTools = null;
37
36
  activeTasks = new Map();
38
37
  authCredentialsReceived = null;
39
38
  authCredentialsResolver = null;
@@ -232,8 +231,8 @@ class BridgeProcess {
232
231
  logger.error('Bridge startup failed, will stay alive briefly for CLI to receive error:', error);
233
232
  this.mcpClientReadyRejecter(error);
234
233
  const errorMsg = error.message || '';
235
- if (isSessionExpiredError(errorMsg) || isAuthenticationError(errorMsg)) {
236
- logger.warn('Session rejected by server (expired or auth failure), marking as expired');
234
+ if (isSessionExpiredError(errorMsg)) {
235
+ logger.warn('Session rejected by server (expired session ID), marking as expired');
237
236
  try {
238
237
  await updateSession(this.options.sessionName, { status: 'expired' });
239
238
  }
@@ -241,6 +240,15 @@ class BridgeProcess {
241
240
  logger.error('Failed to mark session as expired:', updateError);
242
241
  }
243
242
  }
243
+ else if (isAuthenticationError(errorMsg)) {
244
+ logger.warn('Server requires authentication, marking as unauthorized');
245
+ try {
246
+ await updateSession(this.options.sessionName, { status: 'unauthorized' });
247
+ }
248
+ catch (updateError) {
249
+ logger.error('Failed to mark session as unauthorized:', updateError);
250
+ }
251
+ }
244
252
  this.setupSignalHandlers();
245
253
  setTimeout(() => {
246
254
  logger.info('Shutting down after startup failure');
@@ -313,9 +321,9 @@ class BridgeProcess {
313
321
  logger.debug('Creating x402 fetch middleware for payment signing');
314
322
  const wallet = this.x402Wallet;
315
323
  const getToolByName = (name) => {
316
- return this.cachedTools?.find((t) => t.name === name);
324
+ return this.client?.getCachedTools()?.find((t) => t.name === name);
317
325
  };
318
- customFetch = createX402FetchMiddleware(fetch, { wallet, getToolByName });
326
+ customFetch = createX402FetchMiddleware(proxyFetch, { wallet, getToolByName });
319
327
  }
320
328
  const clientConfig = {
321
329
  clientInfo: { name: 'mcpc', version: mcpcVersion },
@@ -336,12 +344,13 @@ class BridgeProcess {
336
344
  ...(customFetch && { customFetch }),
337
345
  listChanged: {
338
346
  tools: {
339
- autoRefresh: true,
340
- onChanged: (error, tools) => {
341
- logger.debug('Tools list changed', { error, count: tools?.length });
342
- if (tools) {
343
- this.cachedTools = tools;
344
- logger.debug(`Updated cached tools list (${tools.length} tools)`);
347
+ autoRefresh: false,
348
+ onChanged: () => {
349
+ logger.debug('Tools list changed notification received, refreshing all tools...');
350
+ if (this.client) {
351
+ this.client.listAllTools({ refreshCache: true }).catch((err) => {
352
+ logger.warn('Failed to refresh tools cache:', err);
353
+ });
345
354
  }
346
355
  this.broadcastNotification('tools/list_changed');
347
356
  this.updateNotificationTimestamp('tools').catch((err) => {
@@ -401,17 +410,10 @@ class BridgeProcess {
401
410
  logger.info(`MCP-Session-Id saved for resumption: ${newMcpSessionId}`);
402
411
  }
403
412
  await updateSession(this.options.sessionName, sessionUpdate);
404
- if (this.x402Wallet) {
405
- try {
406
- const toolsResult = await this.client.listTools();
407
- if (toolsResult.tools) {
408
- this.cachedTools = toolsResult.tools;
409
- logger.debug(`Pre-populated tools cache (${this.cachedTools.length} tools) for x402`);
410
- }
411
- }
412
- catch (error) {
413
- logger.warn('Failed to pre-populate tools cache for x402:', error);
414
- }
413
+ if (serverDetails.capabilities?.tools) {
414
+ await this.client.listAllTools({ refreshCache: true }).catch((err) => {
415
+ logger.warn('Failed to pre-populate tools cache:', err);
416
+ });
415
417
  }
416
418
  }
417
419
  async startProxyServer() {
@@ -445,9 +447,9 @@ class BridgeProcess {
445
447
  startKeepalive() {
446
448
  logger.debug(`Starting keepalive ping every ${KEEPALIVE_INTERVAL_MS / 1000}s`);
447
449
  this.keepaliveInterval = setInterval(() => {
448
- this.sendKeepalivePing().catch((error) => {
450
+ this.sendKeepalivePing().catch(async (error) => {
449
451
  logger.error('Keepalive ping failed:', error);
450
- this.handlePossibleExpiration(error);
452
+ await this.handlePossibleExpiration(error);
451
453
  });
452
454
  }, KEEPALIVE_INTERVAL_MS);
453
455
  this.keepaliveInterval.unref();
@@ -471,20 +473,27 @@ class BridgeProcess {
471
473
  logger.error('Failed to update lastSeenAt:', error);
472
474
  }
473
475
  }
474
- handlePossibleExpiration(error) {
476
+ async handlePossibleExpiration(error) {
477
+ let status = null;
475
478
  if (isSessionExpiredError(error.message)) {
476
479
  logger.warn('Session appears to be expired, marking as expired and shutting down');
477
- this.markSessionStatusAndExit('expired').catch((e) => {
478
- logger.error('Failed to mark session as expired:', e);
479
- process.exit(1);
480
- });
480
+ status = 'expired';
481
481
  }
482
482
  else if (isAuthenticationError(error.message)) {
483
483
  logger.warn('Authentication rejected, marking session as unauthorized and shutting down');
484
- this.markSessionStatusAndExit('unauthorized').catch((e) => {
485
- logger.error('Failed to mark session as unauthorized:', e);
486
- process.exit(1);
487
- });
484
+ status = 'unauthorized';
485
+ }
486
+ if (status) {
487
+ try {
488
+ await updateSession(this.options.sessionName, { status });
489
+ logger.info(`Session ${this.options.sessionName} marked as ${status}`);
490
+ }
491
+ catch (e) {
492
+ logger.error('Failed to update session status:', e);
493
+ }
494
+ setTimeout(() => {
495
+ this.shutdown().catch(() => process.exit(1));
496
+ }, 100);
488
497
  }
489
498
  }
490
499
  async markSessionStatusAndExit(status) {
@@ -641,7 +650,7 @@ class BridgeProcess {
641
650
  const params = message.params;
642
651
  if (params.useTask && this.client.supportsTasksForToolCall()) {
643
652
  if (params.detach) {
644
- const taskUpdate = await this.client.callToolDetached(params.name, params.arguments);
653
+ const taskUpdate = await this.client.callToolDetached(params.name, params.arguments, params._meta);
645
654
  this.activeTasks.set(taskUpdate.taskId, {
646
655
  taskId: taskUpdate.taskId,
647
656
  status: taskUpdate.status,
@@ -675,7 +684,7 @@ class BridgeProcess {
675
684
  onUpdate(update);
676
685
  }, params.name);
677
686
  try {
678
- result = await this.client.callToolWithTask(params.name, params.arguments, wrappedOnUpdate);
687
+ result = await this.client.callToolWithTask(params.name, params.arguments, wrappedOnUpdate, params._meta);
679
688
  }
680
689
  finally {
681
690
  for (const [tid, task] of this.activeTasks) {
@@ -693,7 +702,7 @@ class BridgeProcess {
693
702
  }
694
703
  }
695
704
  else {
696
- result = await this.client.callTool(params.name, params.arguments);
705
+ result = await this.client.callTool(params.name, params.arguments, params._meta);
697
706
  }
698
707
  break;
699
708
  }
@@ -782,6 +791,11 @@ class BridgeProcess {
782
791
  }
783
792
  break;
784
793
  }
794
+ case 'listAllTools': {
795
+ const params = message.params;
796
+ result = await this.client.listAllTools(params);
797
+ break;
798
+ }
785
799
  default:
786
800
  throw new ClientError(`Unknown MCP method: ${message.method}`);
787
801
  }
@@ -795,8 +809,8 @@ class BridgeProcess {
795
809
  }
796
810
  catch (error) {
797
811
  logger.error('Failed to forward MCP request to server:', error);
812
+ await this.handlePossibleExpiration(error);
798
813
  this.sendError(socket, error, message.id);
799
- this.handlePossibleExpiration(error);
800
814
  }
801
815
  }
802
816
  async persistActiveTask(taskId, toolName) {
@@ -848,7 +862,13 @@ class BridgeProcess {
848
862
  const message = {
849
863
  type: 'response',
850
864
  error: {
851
- code: error instanceof ClientError ? 1 : error instanceof NetworkError ? 3 : 2,
865
+ code: error instanceof ClientError
866
+ ? 1
867
+ : error instanceof NetworkError
868
+ ? 3
869
+ : error instanceof AuthError
870
+ ? 4
871
+ : 2,
852
872
  message: error.message,
853
873
  },
854
874
  };
@@ -966,7 +986,7 @@ async function main() {
966
986
  }
967
987
  const x402 = args.includes('--x402');
968
988
  const insecure = args.includes('--insecure');
969
- setGlobalDispatcher(new EnvHttpProxyAgent(insecure ? { connect: { rejectUnauthorized: false } } : {}));
989
+ initProxy({ insecure });
970
990
  try {
971
991
  const bridgeOptions = {
972
992
  sessionName,