@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.
- package/CHANGELOG.md +58 -2
- package/README.md +87 -35
- package/dist/bridge/index.js +63 -5
- package/dist/bridge/index.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +1 -0
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +7 -0
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/clean.d.ts.map +1 -1
- package/dist/cli/commands/clean.js +13 -2
- package/dist/cli/commands/clean.js.map +1 -1
- package/dist/cli/commands/grep.d.ts +4 -0
- package/dist/cli/commands/grep.d.ts.map +1 -1
- package/dist/cli/commands/grep.js +121 -11
- package/dist/cli/commands/grep.js.map +1 -1
- package/dist/cli/commands/prompts.d.ts.map +1 -1
- package/dist/cli/commands/prompts.js +7 -26
- package/dist/cli/commands/prompts.js.map +1 -1
- package/dist/cli/commands/resources.d.ts.map +1 -1
- package/dist/cli/commands/resources.js +9 -3
- package/dist/cli/commands/resources.js.map +1 -1
- package/dist/cli/commands/sessions.d.ts +26 -2
- package/dist/cli/commands/sessions.d.ts.map +1 -1
- package/dist/cli/commands/sessions.js +191 -16
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/commands/tasks.d.ts +1 -0
- package/dist/cli/commands/tasks.d.ts.map +1 -1
- package/dist/cli/commands/tasks.js +11 -0
- package/dist/cli/commands/tasks.js.map +1 -1
- package/dist/cli/commands/tools.d.ts +6 -1
- package/dist/cli/commands/tools.d.ts.map +1 -1
- package/dist/cli/commands/tools.js +43 -15
- package/dist/cli/commands/tools.js.map +1 -1
- package/dist/cli/commands/x402.d.ts.map +1 -1
- package/dist/cli/commands/x402.js +6 -2
- package/dist/cli/commands/x402.js.map +1 -1
- package/dist/cli/index.js +308 -91
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts +5 -0
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +103 -16
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/parser.d.ts +4 -0
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +50 -16
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/shell.d.ts.map +1 -1
- package/dist/cli/shell.js +27 -4
- package/dist/cli/shell.js.map +1 -1
- package/dist/core/mcp-client.d.ts +1 -0
- package/dist/core/mcp-client.d.ts.map +1 -1
- package/dist/core/mcp-client.js +14 -0
- package/dist/core/mcp-client.js.map +1 -1
- package/dist/lib/auth/oauth-flow.d.ts +1 -0
- package/dist/lib/auth/oauth-flow.d.ts.map +1 -1
- package/dist/lib/auth/oauth-flow.js +60 -16
- package/dist/lib/auth/oauth-flow.js.map +1 -1
- package/dist/lib/auth/oauth-provider.d.ts +2 -0
- package/dist/lib/auth/oauth-provider.d.ts.map +1 -1
- package/dist/lib/auth/oauth-provider.js +4 -0
- package/dist/lib/auth/oauth-provider.js.map +1 -1
- package/dist/lib/bridge-client.d.ts +1 -0
- package/dist/lib/bridge-client.d.ts.map +1 -1
- package/dist/lib/bridge-client.js.map +1 -1
- package/dist/lib/bridge-manager.d.ts +4 -1
- package/dist/lib/bridge-manager.d.ts.map +1 -1
- package/dist/lib/bridge-manager.js +97 -27
- package/dist/lib/bridge-manager.js.map +1 -1
- package/dist/lib/cleanup.d.ts +5 -0
- package/dist/lib/cleanup.d.ts.map +1 -1
- package/dist/lib/cleanup.js +38 -1
- package/dist/lib/cleanup.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +5 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +15 -8
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/session-client.d.ts +1 -0
- package/dist/lib/session-client.d.ts.map +1 -1
- package/dist/lib/session-client.js +10 -4
- package/dist/lib/session-client.js.map +1 -1
- package/dist/lib/sessions.d.ts +1 -0
- package/dist/lib/sessions.d.ts.map +1 -1
- package/dist/lib/sessions.js +52 -4
- package/dist/lib/sessions.js.map +1 -1
- package/dist/lib/types.d.ts +5 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +16 -3
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +125 -9
- package/dist/lib/utils.js.map +1 -1
- package/docs/TODOs.md +11 -0
- 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
|
-
-
|
|
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.
|
|
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
|
|
314
|
-
|
|
|
315
|
-
|
|
|
316
|
-
|
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
|
|
|
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
|
-
|
|
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`**
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
- If
|
|
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
|
-
|
|
388
|
-
|
|
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
|
-
|
|
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
|
|
729
|
-
|
|
|
730
|
-
| `--amount <usd>`
|
|
731
|
-
| `--expiry <seconds
|
|
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)
|
|
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 |
|
|
1210
|
-
| [steipete/mcporter](https://github.com/steipete/mcporter) | TS | ~3.5k |
|
|
1211
|
-
| [IBM/mcp-cli](https://github.com/IBM/mcp-cli) | Python | ~1.9k |
|
|
1212
|
-
| [knowsuchagency/mcp2cli](https://github.com/knowsuchagency/mcp2cli) | Python | ~1.8k | 5 / ~76
|
|
1213
|
-
| [f/mcptools](https://github.com/f/mcptools) | Go | ~1.5k |
|
|
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
|
|
package/dist/bridge/index.js
CHANGED
|
@@ -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 =
|
|
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);
|