@apify/mcpc 0.2.3 → 0.2.6

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 (94) hide show
  1. package/CHANGELOG.md +58 -2
  2. package/README.md +87 -35
  3. package/dist/bridge/index.js +63 -5
  4. package/dist/bridge/index.js.map +1 -1
  5. package/dist/cli/commands/auth.d.ts +1 -0
  6. package/dist/cli/commands/auth.d.ts.map +1 -1
  7. package/dist/cli/commands/auth.js +7 -0
  8. package/dist/cli/commands/auth.js.map +1 -1
  9. package/dist/cli/commands/clean.d.ts.map +1 -1
  10. package/dist/cli/commands/clean.js +13 -2
  11. package/dist/cli/commands/clean.js.map +1 -1
  12. package/dist/cli/commands/grep.d.ts +4 -0
  13. package/dist/cli/commands/grep.d.ts.map +1 -1
  14. package/dist/cli/commands/grep.js +121 -11
  15. package/dist/cli/commands/grep.js.map +1 -1
  16. package/dist/cli/commands/prompts.d.ts.map +1 -1
  17. package/dist/cli/commands/prompts.js +7 -26
  18. package/dist/cli/commands/prompts.js.map +1 -1
  19. package/dist/cli/commands/resources.d.ts.map +1 -1
  20. package/dist/cli/commands/resources.js +9 -3
  21. package/dist/cli/commands/resources.js.map +1 -1
  22. package/dist/cli/commands/sessions.d.ts +26 -2
  23. package/dist/cli/commands/sessions.d.ts.map +1 -1
  24. package/dist/cli/commands/sessions.js +191 -16
  25. package/dist/cli/commands/sessions.js.map +1 -1
  26. package/dist/cli/commands/tasks.d.ts +1 -0
  27. package/dist/cli/commands/tasks.d.ts.map +1 -1
  28. package/dist/cli/commands/tasks.js +11 -0
  29. package/dist/cli/commands/tasks.js.map +1 -1
  30. package/dist/cli/commands/tools.d.ts +6 -1
  31. package/dist/cli/commands/tools.d.ts.map +1 -1
  32. package/dist/cli/commands/tools.js +43 -15
  33. package/dist/cli/commands/tools.js.map +1 -1
  34. package/dist/cli/commands/x402.d.ts.map +1 -1
  35. package/dist/cli/commands/x402.js +6 -2
  36. package/dist/cli/commands/x402.js.map +1 -1
  37. package/dist/cli/index.js +308 -91
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/cli/output.d.ts +5 -0
  40. package/dist/cli/output.d.ts.map +1 -1
  41. package/dist/cli/output.js +103 -16
  42. package/dist/cli/output.js.map +1 -1
  43. package/dist/cli/parser.d.ts +4 -0
  44. package/dist/cli/parser.d.ts.map +1 -1
  45. package/dist/cli/parser.js +50 -16
  46. package/dist/cli/parser.js.map +1 -1
  47. package/dist/cli/shell.d.ts.map +1 -1
  48. package/dist/cli/shell.js +27 -4
  49. package/dist/cli/shell.js.map +1 -1
  50. package/dist/core/mcp-client.d.ts +1 -0
  51. package/dist/core/mcp-client.d.ts.map +1 -1
  52. package/dist/core/mcp-client.js +14 -0
  53. package/dist/core/mcp-client.js.map +1 -1
  54. package/dist/lib/auth/oauth-flow.d.ts +1 -0
  55. package/dist/lib/auth/oauth-flow.d.ts.map +1 -1
  56. package/dist/lib/auth/oauth-flow.js +60 -16
  57. package/dist/lib/auth/oauth-flow.js.map +1 -1
  58. package/dist/lib/auth/oauth-provider.d.ts +2 -0
  59. package/dist/lib/auth/oauth-provider.d.ts.map +1 -1
  60. package/dist/lib/auth/oauth-provider.js +4 -0
  61. package/dist/lib/auth/oauth-provider.js.map +1 -1
  62. package/dist/lib/bridge-client.d.ts +1 -0
  63. package/dist/lib/bridge-client.d.ts.map +1 -1
  64. package/dist/lib/bridge-client.js.map +1 -1
  65. package/dist/lib/bridge-manager.d.ts +4 -1
  66. package/dist/lib/bridge-manager.d.ts.map +1 -1
  67. package/dist/lib/bridge-manager.js +97 -27
  68. package/dist/lib/bridge-manager.js.map +1 -1
  69. package/dist/lib/cleanup.d.ts +5 -0
  70. package/dist/lib/cleanup.d.ts.map +1 -1
  71. package/dist/lib/cleanup.js +38 -1
  72. package/dist/lib/cleanup.js.map +1 -1
  73. package/dist/lib/config.d.ts.map +1 -1
  74. package/dist/lib/config.js +5 -1
  75. package/dist/lib/config.js.map +1 -1
  76. package/dist/lib/errors.d.ts.map +1 -1
  77. package/dist/lib/errors.js +15 -8
  78. package/dist/lib/errors.js.map +1 -1
  79. package/dist/lib/session-client.d.ts +1 -0
  80. package/dist/lib/session-client.d.ts.map +1 -1
  81. package/dist/lib/session-client.js +10 -4
  82. package/dist/lib/session-client.js.map +1 -1
  83. package/dist/lib/sessions.d.ts +1 -0
  84. package/dist/lib/sessions.d.ts.map +1 -1
  85. package/dist/lib/sessions.js +52 -4
  86. package/dist/lib/sessions.js.map +1 -1
  87. package/dist/lib/types.d.ts +5 -1
  88. package/dist/lib/types.d.ts.map +1 -1
  89. package/dist/lib/utils.d.ts +16 -3
  90. package/dist/lib/utils.d.ts.map +1 -1
  91. package/dist/lib/utils.js +125 -9
  92. package/dist/lib/utils.js.map +1 -1
  93. package/docs/TODOs.md +11 -0
  94. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -7,18 +7,71 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.6] - 2026-04-15
11
+
12
+ ### Added
13
+
14
+ - New `tasks-result <taskId>` command that fetches the final `CallToolResult` payload of an async task via the MCP `tasks/result` method. Blocks until the task reaches a terminal state, then prints the payload using the same renderer as `tools-call` (`--json` returns the raw result).
15
+
16
+ ### Fixed
17
+
18
+ - `tools-get` "Call example" now wraps JSON values (arrays, objects, strings) in single quotes so they can be copy-pasted into a shell verbatim without being mangled by word-splitting (e.g., `outputFormats:='["markdown"]'` instead of `outputFormats:=["markdown"]`)
19
+
20
+ ## [0.2.5] - 2026-04-15
21
+
22
+ ### Added
23
+
24
+ - `mcpc connect <config-file>` connects all servers defined in a config file at once, auto-generating session names from entry names (e.g., `mcpc connect ~/.vscode/mcp.json`)
25
+ - `connect` auto-generates a session name when `@session` is omitted (e.g., `mcpc connect mcp.apify.com` creates `@apify`), reusing an existing session when auth settings match
26
+ - `--max-chars <n>` global option to truncate output to a given number of characters (ignored in `--json` mode)
27
+ - "Did you mean?" suggestions for unknown commands, including reversed names (e.g., `list-tools` → `tools-list`)
28
+ - `mcpc login --client-metadata-url <https-url>` flag adds support for [OAuth Client ID Metadata Documents (CIMD)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00). When the authorization server advertises `client_id_metadata_document_supported: true`, mcpc uses the URL as the `client_id`; otherwise it falls back to Dynamic Client Registration.
29
+
30
+ ### Changed
31
+
32
+ - Session info JSON output (`mcpc @session --json`, `mcpc connect --json`) returns `toolNames` (array of strings) instead of full `tools` objects
33
+ - `--schema` and `--schema-mode` options scoped to `tools-get` and `tools-call` only (removed from `prompts-get`)
34
+
35
+ ### Fixed
36
+
37
+ - `connect` now verifies the server responds before reporting success; shows a warning with the actual error when the server is unreachable
38
+ - HTTP 404 during initial connect no longer misclassified as "session expired"; error messages now include the actual HTTP error and server URL
39
+ - `mcpc login --json` now writes interactive prompts to stderr, so stdout contains only the final JSON result and is safe to pipe to `jq` or redirect to a file
40
+
41
+ ## [0.2.4] - 2026-04-07
42
+
43
+ ### Security
44
+
45
+ - Fixed XSS vulnerability in OAuth callback server: error messages from query parameters are now HTML-escaped before rendering
46
+ - Replaced `exec()` with `execFile()` in browser opening to prevent potential shell injection via crafted OAuth authorization URLs
47
+ - Added Host header validation to OAuth callback server to mitigate DNS rebinding attacks
48
+ - Set restrictive directory permissions (`0o700`) on `~/.mcpc/` and subdirectories to prevent local privilege escalation on shared systems
49
+
50
+ ### Fixed
51
+
52
+ - Bridge ignores stored OAuth access token when no refresh token is provided; servers that don't issue refresh tokens now work correctly by using the access token as a static Bearer header
53
+ - Session incorrectly marked as `unauthorized` when access token expires but refresh token is still valid; bridge now attempts token refresh before giving up
54
+ - "ESC to detach" hint now shows immediately in the spinner when using `--task`, instead of waiting for the server to return a task ID
55
+
10
56
  ## [0.2.3] - 2026-03-31
11
57
 
12
58
  ## [0.2.2] - 2026-03-31
13
59
 
14
60
  ## [0.2.1] - 2026-03-30
61
+
15
62
  ### Added
63
+
16
64
  - Secure x402 wallet storage using OS keychain integration with fallback to `wallets.json` for compatibility
17
65
  - QR code display for wallet address in `x402 init`, `x402 import`, and `x402 info` commands, allowing users to scan and fund the wallet directly from the terminal
18
66
 
19
67
  ### Changed
20
68
 
21
- - Release process migrated from local `scripts/publish.sh` to GitHub Actions; `npm run release` now triggers the CI workflow instead of running locally
69
+ - Auto-reconnect crashed and unauthorized bridge processes in the background when enumerating sessions (`mcpc` or `mcpc grep`), with a 10-second cooldown between reconnection attempts. Unauthorized sessions benefit from OAuth tokens refreshed by other sessions sharing the same profile.
70
+
71
+ ### Fixed
72
+
73
+ - Fixed expired sessions falsely showing as `live` after auto-reconnect — the bridge now detects when the server did not resume the original MCP session (including when no session ID is returned) and marks the session as `expired`
74
+ - Bridge sends first keepalive ping 5 seconds after startup (instead of waiting the full 30-second interval) to detect stale sessions earlier
22
75
 
23
76
  ## [0.2.0] - 2026-03-24
24
77
 
@@ -182,7 +235,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
182
235
  - Interactive shell mode
183
236
  - JSON output mode for scripting
184
237
 
185
- [Unreleased]: https://github.com/apify/mcpc/compare/v0.2.3...HEAD
238
+ [Unreleased]: https://github.com/apify/mcpc/compare/v0.2.6...HEAD
239
+ [0.2.6]: https://github.com/apify/mcpc/compare/v0.2.5...v0.2.6
240
+ [0.2.5]: https://github.com/apify/mcpc/compare/v0.2.4...v0.2.5
241
+ [0.2.4]: https://github.com/apify/mcpc/compare/v0.2.3...v0.2.4
186
242
  [0.2.3]: https://github.com/apify/mcpc/compare/v0.2.2...v0.2.3
187
243
  [0.2.2]: https://github.com/apify/mcpc/compare/v0.2.1...v0.2.2
188
244
  [0.2.1]: https://github.com/apify/mcpc/compare/v0.2.0...v0.2.1
package/README.md CHANGED
@@ -251,7 +251,7 @@ By default, `grep` searches only tools. Use `--resources` or `--prompts` to sear
251
251
  (combine with `--tools` to include tools too). Sessions that are crashed or unavailable are shown
252
252
  with their status rather than silently skipped.
253
253
 
254
- The `grep` command is useful for **dynamic tool discovery**,
254
+ The `grep` command is useful for **dynamic tool discovery**,
255
255
  also called [Tool search tool](https://www.anthropic.com/engineering/advanced-tool-use) by Anthropic
256
256
  or [Dynamic context discovery](https://cursor.com/blog/dynamic-context-discovery) by Cursor.
257
257
  Rather than loading all tools into AI agent's context, the agent can use `grep` to discover the right tool
@@ -310,13 +310,15 @@ Still, sessions can fail due to network disconnects, bridge process crash, or se
310
310
 
311
311
  **Session states:**
312
312
 
313
- | State | Meaning |
314
- | --------------------- | ------------------------------------------------------------------------------------------------- |
315
- | 🟢 **`live`** | Bridge process running and server responding |
316
- | 🟡 **`disconnected`** | Bridge process running but server unreachable; auto-recovers when server responds |
317
- | 🟡 **`crashed`** | Bridge process crashed or was killed; auto-restarts on next use |
318
- | 🔴 **`unauthorized`** | Server rejected authentication (401/403) or token refresh failed; requires `login` then `restart` |
319
- | 🔴 **`expired`** | Server rejected session ID (404); requires `restart` |
313
+ | State | Meaning |
314
+ | -------------------- | -------------------------------------------------------------------------------------------------- |
315
+ | 🟢**`live`** | Bridge process running and server responding |
316
+ | 🟡**`connecting`** | Initial bridge startup in progress (`mcpc connect`) |
317
+ | 🟡**`reconnecting`** | Bridge crashed or lost auth; auto-reconnecting in the background |
318
+ | 🟡**`disconnected`** | Bridge process running but server unreachable; auto-recovers when server responds |
319
+ | 🟡**`crashed`** | Bridge process crashed or was killed; auto-reconnects in the background |
320
+ | 🔴**`unauthorized`** | Server rejected authentication (401/403) or token refresh failed; auto-reconnects or needs `login` |
321
+ | 🔴**`expired`** | Server rejected session ID (404); requires `restart` |
320
322
 
321
323
  Here's how `mcpc` handles various bridge process and server connection states:
322
324
 
@@ -326,14 +328,17 @@ Here's how `mcpc` handles various bridge process and server connection states:
326
328
  The bridge will keep trying to reconnect in the background and will return to 🟢 **`live`** once the server responds again.
327
329
  - If **server rejects authentication** (HTTP 401 or 403) or token refresh fails,
328
330
  the session is marked 🔴 **`unauthorized`**.
329
- You need to re-authenticate with `mcpc login <server>` and then `mcpc @my-session restart`.
331
+ `mcpc` will auto-reconnect in the background if another session sharing the same OAuth profile has refreshed the tokens.
332
+ Otherwise, re-authenticate with `mcpc login <server>` and then `mcpc @my-session restart`.
330
333
  - If **server rejects the session ID** (HTTP 404), indicating the MCP session is no longer valid,
331
334
  the session is marked 🔴 **`expired`**.
332
335
  You need to restart the session with `mcpc @my-session restart` to establish a new connection.
333
- - If the **bridge process crashes**, `mcpc` will mark the session as 🟡 **`crashed`** on first use.
334
- Next time you run `mcpc @my-session ...`, it will attempt to restart the bridge process.
335
- - If bridge **restart succeeds**, everything starts again (see above).
336
- - If bridge **restart fails**, `mcpc @my-session ...` returns error, and session remains marked 🟡 **`crashed`**.
336
+ - If the **bridge process crashes**, `mcpc` will mark the session as 🟡 **`crashed`**
337
+ and auto-reconnect the bridge in the background. You can also trigger reconnection manually
338
+ by running any `mcpc @my-session ...` command.
339
+ - If reconnection **succeeds** and the server resumes the MCP session, the session returns to 🟢 **`live`**.
340
+ - If the server issues a **new session ID** instead of resuming, the session is marked 🔴 **`expired`**.
341
+ - If reconnection **fails**, the session remains 🟡 **`crashed`** and retries after a 10-second cooldown.
337
342
 
338
343
  Note that `mcpc` never automatically removes sessions from the list.
339
344
  Instead, it keeps them flagged as 🟡 **`crashed`**, 🔴 **`unauthorized`**, or 🔴 **`expired`**,
@@ -383,9 +388,12 @@ mcpc @apify tools-list
383
388
 
384
389
  ### OAuth profiles
385
390
 
386
- For OAuth-enabled remote MCP servers, `mcpc` implements the full OAuth 2.1 flow with PKCE,
387
- including `WWW-Authenticate` header discovery, server metadata discovery, client ID metadata documents,
388
- dynamic client registration, and automatic token refresh.
391
+ For OAuth-enabled remote MCP servers, `mcpc` implements the full OAuth 2.1 flow with PKCE as
392
+ mandated by the [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization):
393
+ `WWW-Authenticate` 401 challenges, Protected Resource Metadata and authorization server metadata
394
+ discovery, all three [client registration approaches](#client-registration-approaches),
395
+ [resource indicators (RFC 8707)](https://www.rfc-editor.org/rfc/rfc8707), and automatic
396
+ refresh-token rotation.
389
397
 
390
398
  The OAuth authentication **always** needs to be initiated by the user calling the `login` command,
391
399
  which opens a web browser with login screen. `mcpc` never opens the web browser on its own.
@@ -428,6 +436,34 @@ mcpc logout mcp.apify.com
428
436
  mcpc logout mcp.apify.com --profile work
429
437
  ```
430
438
 
439
+ ### Client registration approaches
440
+
441
+ When logging in, `mcpc` supports all three OAuth client registration approaches defined in the
442
+ [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-registration-approaches),
443
+ picking the one the authorization server advertises in its OAuth metadata:
444
+
445
+ | **Approach** | **`mcpc login` flags** |
446
+ |:----------------------------------------| :--------------------------------------------- |
447
+ | **Pre-registration** | `--client-id` (and optional `--client-secret`) |
448
+ | **Client ID Metadata Documents (CIMD)** | `--client-metadata-url <https-url>` |
449
+ | **Dynamic Client Registration (DCR)** | _(default, no flags needed)_ |
450
+
451
+ ```bash
452
+ # Pre-registered OAuth client (public or confidential)
453
+ mcpc login mcp.example.com --client-id <id> [--client-secret <secret>]
454
+
455
+ # Client ID Metadata Documents (CIMD): URL points to a JSON document served over HTTPS.
456
+ # Used only if the authorization server advertises client_id_metadata_document_supported: true;
457
+ # otherwise mcpc falls back to Dynamic Client Registration.
458
+ mcpc login mcp.example.com --client-metadata-url https://example.com/mcpc-client.json
459
+
460
+ # Dynamic Client Registration (DCR): default when the server has a registration_endpoint.
461
+ mcpc login mcp.apify.com
462
+ ```
463
+
464
+ See the [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-registration-approaches)
465
+ for details on each approach and the format of Client ID Metadata Documents.
466
+
431
467
  ### Authentication precedence
432
468
 
433
469
  When multiple authentication methods are available, `mcpc` uses this precedence order:
@@ -455,6 +491,11 @@ exclusive. Providing both will result in a clear error. Use one or the other.
455
491
  - If authentication fails (expired/invalid) → Fail with an error
456
492
  2. **Profile doesn't exist**: Fail with an error
457
493
 
494
+ **When `--x402` is specified (without `--profile`):**
495
+
496
+ - OAuth profile auto-detection is skipped, since x402 serves as the payment/auth mechanism
497
+ - If you also pass `--profile`, the specified profile is still used alongside x402
498
+
458
499
  **When `--no-profile` is specified:**
459
500
 
460
501
  - Skip all OAuth profile detection and connect anonymously
@@ -495,6 +536,9 @@ mcpc connect mcp.apify.com @apify-personal
495
536
  # Explicit bearer token - skips profile auto-detection:
496
537
  mcpc connect mcp.apify.com @apify --header "Authorization: Bearer ${APIFY_TOKEN}"
497
538
 
539
+ # x402 payment - skips default profile auto-detection:
540
+ mcpc connect mcp.apify.com @apify --x402
541
+
498
542
  # Anonymous - skips default profile even if it exists:
499
543
  mcpc connect mcp.apify.com @apify-anon --no-profile
500
544
  ```
@@ -617,12 +661,15 @@ For a complete example script, see [`docs/examples/company-lookup.sh`](./docs/ex
617
661
 
618
662
  ### Schema validation
619
663
 
620
- Validate tool/prompt schemas using the `--schema` option to detect breaking changes early:
664
+ The `tools-get` and `tools-call` commands support `--schema` to validate a tool's schema against an expected snapshot. This helps detect breaking changes early in scripts and CI:
621
665
 
622
666
  ```bash
623
667
  # Save expected schema
624
668
  mcpc --json @apify tools-get search-actors > expected.json
625
669
 
670
+ # Validate without calling (read-only check)
671
+ mcpc @apify tools-get search-actors --schema expected.json
672
+
626
673
  # Validate before calling (fails if schema changed incompatibly)
627
674
  mcpc @apify tools-call search-actors --schema expected.json keywords:="test"
628
675
  ```
@@ -725,10 +772,10 @@ mcpc x402 sign <base64-payment-required> --amount 1.00 --expiry 3600 --json
725
772
 
726
773
  **Options:**
727
774
 
728
- | Option | Description |
729
- | ------------------- | ---------------------------------------------------------------- |
730
- | `--amount <usd>` | Override the payment amount in USD (e.g. `0.50` for $0.50) |
731
- | `--expiry <seconds>`| Override the payment expiry in seconds from now (e.g. `3600`) |
775
+ | Option | Description |
776
+ | -------------------- | ------------------------------------------------------------- |
777
+ | `--amount <usd>` | Override the payment amount in USD (e.g. `0.50` for $0.50) |
778
+ | `--expiry <seconds>` | Override the payment expiry in seconds from now (e.g. `3600`) |
732
779
 
733
780
  The command outputs the signed `PAYMENT-SIGNATURE` header value and an MCP config snippet
734
781
  that can be used directly with other MCP clients.
@@ -799,7 +846,7 @@ The bridge process manages the full MCP session lifecycle:
799
846
  | 🔔 [**Notifications**](#list-change-notifications) | ✅ Supported |
800
847
  | 📄 [**Pagination**](#pagination) | ✅ Supported |
801
848
  | 🏓 [**Ping**](#ping) | ✅ Supported |
802
- | ⏳ [**Async tasks**](#async-tasks) | ✅ Supported |
849
+ | ⏳ [**Async tasks**](#async-tasks) | ✅ Supported |
803
850
  | 📁 **Roots** | 🚧 Planned |
804
851
  | ❓ **Elicitation** | 🚧 Planned |
805
852
  | 🔤 **Completion** | 🚧 Planned |
@@ -969,6 +1016,9 @@ mcpc @apify tasks-list
969
1016
  # Check task status
970
1017
  mcpc @apify tasks-get <taskId>
971
1018
 
1019
+ # Get the task result (blocks until the task reaches a terminal state)
1020
+ mcpc @apify tasks-result <taskId>
1021
+
972
1022
  # Cancel a running task
973
1023
  mcpc @apify tasks-cancel <taskId>
974
1024
  ```
@@ -976,6 +1026,8 @@ mcpc @apify tasks-cancel <taskId>
976
1026
  With `--task`, the CLI shows a progress spinner with elapsed time, server status messages,
977
1027
  and progress notifications. Press **ESC** during execution to detach and get the task ID
978
1028
  for later retrieval. With `--detach`, the task starts and returns the task ID immediately.
1029
+ Use `tasks-result <taskId>` to fetch the final `CallToolResult` payload once the task
1030
+ completes.
979
1031
 
980
1032
  `tools-list` and `tools-get` show task support annotations per tool:
981
1033
  `[task:optional]`, `[task:required]`, or `[task:forbidden]`.
@@ -1205,19 +1257,19 @@ See [CONTRIBUTING](./CONTRIBUTING.md) for development setup, architecture overvi
1205
1257
  <!-- Stars, contributors, commits, and activity as of March 2026. -->
1206
1258
 
1207
1259
  | Tool | Lang | Stars | Contrib / Commits | Active | Tools | Resources | Prompts | Tasks | Code mode | Sessions | OAuth | Stdio | HTTP | Tool search | x402 | LLM |
1208
- | ----------------------------------------------------------------------- | ------ | ----: | -----------------: | ------ | ----- | --------- | ------- | ----- | --------- | -------- | ----- | ----- | ---- | ----------- | ---- | --- |
1209
- | **[apify/mcpc](https://github.com/apify/mcpc)** | TS | ~420 | 7 / ~510 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — |
1210
- | [steipete/mcporter](https://github.com/steipete/mcporter) | TS | ~3.5k | 24 / ~570 | ✅ | ✅ | — | — | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | — | — |
1211
- | [IBM/mcp-cli](https://github.com/IBM/mcp-cli) | Python | ~1.9k | 22 / ~790 | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | — | ✅ |
1212
- | [knowsuchagency/mcp2cli](https://github.com/knowsuchagency/mcp2cli) | Python | ~1.8k | 5 / ~76 | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
1213
- | [f/mcptools](https://github.com/f/mcptools) | Go | ~1.5k | 15 / ~170 | ⚠️ | ✅ | ✅ | ✅ | — | ✅ | — | — | ✅ | ✅ | — | — | — |
1214
- | [philschmid/mcp-cli](https://github.com/philschmid/mcp-cli) | TS | ~1.1k | 2 / ~30 | ✅ | ✅ | — | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — | — |
1215
- | [adhikasp/mcp-client-cli](https://github.com/adhikasp/mcp-client-cli) | Python | ~670 | 6 / ~110 | ⚠️ | ✅ | ✅ | ✅ | — | — | — | — | ✅ | — | — | — | ✅ |
1216
- | [thellimist/clihub](https://github.com/thellimist/clihub) | Go | ~640 | 1 / ~60 | ✅ | ✅ | — | — | — | — | — | ✅ | ✅ | ✅ | ✅ | — | — |
1217
- | [wong2/mcp-cli](https://github.com/wong2/mcp-cli) | JS | ~430 | 4 / ~63 | ⚠️ | ✅ | ✅ | ✅ | — | — | — | ✅ | — | ✅ | — | — | — |
1218
- | [mcpshim/mcpshim](https://github.com/mcpshim/mcpshim) | Go | ~54 | 1 / ~13 | ✅ | ✅ | — | — | — | ✅ | ✅ | ✅ | — | ✅ | ✅ | — | — |
1219
- | [evantahler/mcpx](https://github.com/evantahler/mcpx) | TS | ~28 | 1 / ~64 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | — | — |
1220
- | [EstebanForge/mcp-cli-ent](https://github.com/EstebanForge/mcp-cli-ent) | Go | ~15 | ~2 / ~46 | ✅ | ✅ | — | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — | — |
1260
+ | ----------------------------------------------------------------------- | ------ | ----: | ----------------: | ------ | ----- | --------- | ------- | ----- | --------- | -------- | ----- | ----- | ---- | ----------- | ---- | --- |
1261
+ | **[apify/mcpc](https://github.com/apify/mcpc)** | TS | ~420 | 7 / ~510 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — |
1262
+ | [steipete/mcporter](https://github.com/steipete/mcporter) | TS | ~3.5k | 24 / ~570 | ✅ | ✅ | — | — | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | — | — |
1263
+ | [IBM/mcp-cli](https://github.com/IBM/mcp-cli) | Python | ~1.9k | 22 / ~790 | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | — | ✅ |
1264
+ | [knowsuchagency/mcp2cli](https://github.com/knowsuchagency/mcp2cli) | Python | ~1.8k | 5 / ~76 | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
1265
+ | [f/mcptools](https://github.com/f/mcptools) | Go | ~1.5k | 15 / ~170 | ⚠️ | ✅ | ✅ | ✅ | — | ✅ | — | — | ✅ | ✅ | — | — | — |
1266
+ | [philschmid/mcp-cli](https://github.com/philschmid/mcp-cli) | TS | ~1.1k | 2 / ~30 | ✅ | ✅ | — | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — | — |
1267
+ | [adhikasp/mcp-client-cli](https://github.com/adhikasp/mcp-client-cli) | Python | ~670 | 6 / ~110 | ⚠️ | ✅ | ✅ | ✅ | — | — | — | — | ✅ | — | — | — | ✅ |
1268
+ | [thellimist/clihub](https://github.com/thellimist/clihub) | Go | ~640 | 1 / ~60 | ✅ | ✅ | — | — | — | — | — | ✅ | ✅ | ✅ | ✅ | — | — |
1269
+ | [wong2/mcp-cli](https://github.com/wong2/mcp-cli) | JS | ~430 | 4 / ~63 | ⚠️ | ✅ | ✅ | ✅ | — | — | — | ✅ | — | ✅ | — | — | — |
1270
+ | [mcpshim/mcpshim](https://github.com/mcpshim/mcpshim) | Go | ~54 | 1 / ~13 | ✅ | ✅ | — | — | — | ✅ | ✅ | ✅ | — | ✅ | ✅ | — | — |
1271
+ | [evantahler/mcpx](https://github.com/evantahler/mcpx) | TS | ~28 | 1 / ~64 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | — | — |
1272
+ | [EstebanForge/mcp-cli-ent](https://github.com/EstebanForge/mcp-cli-ent) | Go | ~15 | ~2 / ~46 | ✅ | ✅ | — | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — | — |
1221
1273
 
1222
1274
  **Legend:** ✅ = supported, ⚠️ = stale (no commits in 3+ months), **Contrib / Commits** = contributors / total commits, **Tasks** = [async tasks](https://modelcontextprotocol.io/specification/latest/server/utilities/tasks), **x402** = [x402 payment protocol](https://www.x402.org/) support, **LLM** = requires/uses an LLM.
1223
1275
 
@@ -6,7 +6,7 @@ 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, AuthError, isAuthenticationError } from '../lib/index.js';
9
+ import { ClientError, ServerError, 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';
@@ -45,7 +45,7 @@ class BridgeProcess {
45
45
  mcpClientReadyRejecter;
46
46
  constructor(options) {
47
47
  this.options = options;
48
- this.socketPath = getSocketPath(options.sessionName);
48
+ this.socketPath = getSocketPath(options.sessionName, process.pid);
49
49
  this.mcpClientReady = new Promise((resolve, reject) => {
50
50
  this.mcpClientReadyResolver = resolve;
51
51
  this.mcpClientReadyRejecter = reject;
@@ -58,6 +58,7 @@ class BridgeProcess {
58
58
  logger.info(`Received auth credentials for profile: ${credentials.profileName}`);
59
59
  logger.debug(` serverUrl: ${credentials.serverUrl}`);
60
60
  logger.debug(` refreshToken: ${credentials.refreshToken ? 'present' : 'MISSING'}`);
61
+ logger.debug(` accessToken: ${credentials.accessToken ? 'present' : 'MISSING'}`);
61
62
  logger.debug(` clientId: ${credentials.clientId ? 'present' : 'MISSING'}`);
62
63
  logger.debug(` headers: ${credentials.headers ? Object.keys(credentials.headers).length : 0}`);
63
64
  if (credentials.refreshToken && credentials.clientId) {
@@ -119,8 +120,18 @@ class BridgeProcess {
119
120
  else if (credentials.refreshToken && !credentials.clientId) {
120
121
  logger.warn('Refresh token provided but client ID is missing - token refresh will not work');
121
122
  }
123
+ else if (credentials.accessToken && !credentials.refreshToken) {
124
+ this.headers = {
125
+ ...this.headers,
126
+ Authorization: `Bearer ${credentials.accessToken}`,
127
+ };
128
+ logger.debug('Using OAuth access token as static Bearer header (no refresh token available)');
129
+ }
122
130
  if (credentials.headers) {
123
- this.headers = credentials.headers;
131
+ this.headers = {
132
+ ...this.headers,
133
+ ...credentials.headers,
134
+ };
124
135
  logger.debug(`Stored headers "${Object.keys(this.headers).join(', ')}" in memory`);
125
136
  }
126
137
  if (this.authCredentialsResolver) {
@@ -237,7 +248,9 @@ class BridgeProcess {
237
248
  classifiedError = new AuthError(errorMsg);
238
249
  }
239
250
  this.mcpClientReadyRejecter(classifiedError);
240
- if (isSessionExpiredError(errorMsg)) {
251
+ if (isSessionExpiredError(errorMsg, {
252
+ hadActiveSession: !!this.options.mcpSessionId,
253
+ })) {
241
254
  logger.warn('Session rejected by server (expired session ID), marking as expired');
242
255
  try {
243
256
  await updateSession(this.options.sessionName, { status: 'expired' });
@@ -406,8 +419,20 @@ class BridgeProcess {
406
419
  });
407
420
  const serverDetails = await this.client.getServerDetails();
408
421
  const newMcpSessionId = this.client.getMcpSessionId();
422
+ if (this.options.mcpSessionId && newMcpSessionId !== this.options.mcpSessionId) {
423
+ logger.warn(`Server did not resume MCP session ` +
424
+ `(expected ${this.options.mcpSessionId}, got ${newMcpSessionId ?? 'none'}). Marking as expired.`);
425
+ throw new Error(`Session expired: server did not resume MCP session ` +
426
+ `(expected ${this.options.mcpSessionId}, got ${newMcpSessionId ?? 'none'})`);
427
+ }
428
+ if (this.options.mcpSessionId) {
429
+ logger.info('Verifying resumed session with ping...');
430
+ await this.client.ping();
431
+ logger.info('Session verification ping succeeded');
432
+ }
409
433
  const sessionUpdate = {
410
434
  lastSeenAt: new Date().toISOString(),
435
+ status: 'active',
411
436
  };
412
437
  if (serverDetails.protocolVersion) {
413
438
  sessionUpdate.protocolVersion = serverDetails.protocolVersion;
@@ -422,6 +447,10 @@ class BridgeProcess {
422
447
  await updateSession(this.options.sessionName, sessionUpdate);
423
448
  if (serverDetails.capabilities?.tools) {
424
449
  await this.client.listAllTools({ refreshCache: true }).catch((err) => {
450
+ const errMsg = err.message || '';
451
+ if (isSessionExpiredError(errMsg) || isAuthenticationError(errMsg)) {
452
+ throw err;
453
+ }
425
454
  logger.warn('Failed to pre-populate tools cache:', err);
426
455
  });
427
456
  }
@@ -456,6 +485,13 @@ class BridgeProcess {
456
485
  }
457
486
  startKeepalive() {
458
487
  logger.debug(`Starting keepalive ping every ${KEEPALIVE_INTERVAL_MS / 1000}s`);
488
+ const earlyPing = setTimeout(() => {
489
+ this.sendKeepalivePing().catch(async (error) => {
490
+ logger.error('Initial keepalive ping failed:', error);
491
+ await this.handlePossibleExpiration(error);
492
+ });
493
+ }, 5_000);
494
+ earlyPing.unref();
459
495
  this.keepaliveInterval = setInterval(() => {
460
496
  this.sendKeepalivePing().catch(async (error) => {
461
497
  logger.error('Keepalive ping failed:', error);
@@ -484,12 +520,29 @@ class BridgeProcess {
484
520
  }
485
521
  }
486
522
  async handlePossibleExpiration(error) {
523
+ if (error instanceof ServerError) {
524
+ const originalError = error.details?.originalError;
525
+ if (originalError && originalError.name === 'McpError') {
526
+ return;
527
+ }
528
+ }
487
529
  let status = null;
488
- if (isSessionExpiredError(error.message)) {
530
+ if (isSessionExpiredError(error.message, { hadActiveSession: true })) {
489
531
  logger.warn('Session appears to be expired, marking as expired and shutting down');
490
532
  status = 'expired';
491
533
  }
492
534
  else if (isAuthenticationError(error.message)) {
535
+ if (this.tokenManager) {
536
+ try {
537
+ logger.info('Authentication error detected, attempting token refresh before giving up...');
538
+ await this.tokenManager.refreshAccessToken();
539
+ logger.info('Token refresh succeeded — session will recover on next request');
540
+ return;
541
+ }
542
+ catch (refreshError) {
543
+ logger.warn('Token refresh also failed, marking session as unauthorized:', refreshError);
544
+ }
545
+ }
493
546
  logger.warn('Authentication rejected, marking session as unauthorized and shutting down');
494
547
  status = 'unauthorized';
495
548
  }
@@ -802,6 +855,11 @@ class BridgeProcess {
802
855
  result = await this.client.getTask(params.taskId);
803
856
  break;
804
857
  }
858
+ case 'getTaskResult': {
859
+ const params = message.params;
860
+ result = await this.client.getTaskResult(params.taskId);
861
+ break;
862
+ }
805
863
  case 'cancelTask': {
806
864
  const params = message.params;
807
865
  result = await this.client.cancelTask(params.taskId);