@apify/mcpc 0.2.0-beta.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -38
- package/README.md +91 -21
- package/dist/cli/commands/grep.d.ts +13 -0
- package/dist/cli/commands/grep.d.ts.map +1 -0
- package/dist/cli/commands/grep.js +373 -0
- package/dist/cli/commands/grep.js.map +1 -0
- package/dist/cli/commands/sessions.d.ts +11 -0
- package/dist/cli/commands/sessions.d.ts.map +1 -1
- package/dist/cli/commands/sessions.js +10 -4
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/index.js +74 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts +3 -0
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +22 -21
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +4 -0
- package/dist/cli/parser.js.map +1 -1
- package/docs/TODOs.md +22 -47
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,10 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## [0.2.0] - 2026-03-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
11
13
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
+
- New `mcpc grep <pattern>` command to search tools, resources, prompts, and instructions across all active sessions, with regex, type filters, and single-session search support
|
|
15
|
+
- New `tasks-list`, `tasks-get`, `tasks-cancel` commands for managing async tasks on the server
|
|
16
|
+
- `--task` flag for `tools-call` to opt-in to task execution with progress spinner; `--detach` to start a task and return the task ID immediately; press ESC during `--task` to detach on the fly
|
|
17
|
+
- `--insecure` global option to skip TLS certificate verification
|
|
18
|
+
- `--client-id` and `--client-secret` options for `mcpc login`, for servers that don't support dynamic client registration
|
|
19
|
+
- `--no-profile` option for `connect` to skip OAuth profile auto-detection
|
|
20
|
+
- `mcpc login` now falls back to accepting a pasted callback URL when the browser cannot be opened (e.g. headless servers, containers)
|
|
21
|
+
- `tools-list` now shows inline parameter signatures (e.g. `read_file(path: string, +4 optional)`) for quick scanning without `--full`
|
|
22
|
+
- `mcpc @session` now shows available tools list from bridge cache (no extra server call)
|
|
14
23
|
|
|
15
24
|
### Changed
|
|
16
25
|
|
|
@@ -27,47 +36,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
27
36
|
|
|
28
37
|
Direct one-shot URL access (e.g. `mcpc mcp.apify.com tools-list`) is removed; create a session first with `mcpc connect`.
|
|
29
38
|
|
|
30
|
-
-
|
|
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
|
|
39
|
+
- Revised session states: `unauthorized` (401/403), `disconnected` (bridge alive but server unreachable >2min), and `expired` (session ID rejected), each with actionable guidance
|
|
32
40
|
- When `--profile` is not specified, only the `default` profile is used; non-default profiles require an explicit `--profile` flag
|
|
33
|
-
- `@napi-rs/keyring` native addon is now loaded lazily
|
|
34
|
-
- `
|
|
35
|
-
-
|
|
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
|
|
41
|
+
- `@napi-rs/keyring` native addon is now loaded lazily; falls back to `~/.mcpc/credentials.json` when unavailable
|
|
42
|
+
- `--header` / `-H` option is now specific to the `connect` command instead of being a global option
|
|
43
|
+
- Tools cache now fetches all pages on startup and on `tools/list_changed` notifications
|
|
54
44
|
|
|
55
45
|
### Fixed
|
|
56
46
|
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
- Auth errors wrapped in `NetworkError` by bridge IPC are now detected on first health check, avoiding unnecessary bridge restart
|
|
47
|
+
- HTTP proxy support (`HTTP_PROXY`/`HTTPS_PROXY`) now works for MCP server connections, OAuth token refresh, and x402 payment signing
|
|
48
|
+
- Explicit `--header "Authorization: ..."` now takes precedence over auto-detected OAuth profiles
|
|
49
|
+
- Fixed auth loss when reconnecting an unauthorized session via `mcpc connect`
|
|
50
|
+
- Session restart now auto-detects the `default` OAuth profile created after the session was established
|
|
62
51
|
- `--timeout` flag now correctly propagates to MCP requests via session bridge
|
|
63
52
|
- `--task` and `--detach` tool calls now correctly send task creation parameters to the server
|
|
64
|
-
- Bridge now forwards `logging/message` notifications
|
|
53
|
+
- Bridge now forwards `logging/message` notifications to connected clients
|
|
65
54
|
- IPC buffer between CLI and bridge process is now capped at 10 MB, preventing unbounded memory growth
|
|
66
|
-
- `
|
|
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
|
|
55
|
+
- Fixed `mcpc help <command>` showing truncated usage line
|
|
71
56
|
|
|
72
57
|
## [0.1.10] - 2026-03-01
|
|
73
58
|
|
|
@@ -184,7 +169,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
184
169
|
- Interactive shell mode
|
|
185
170
|
- JSON output mode for scripting
|
|
186
171
|
|
|
187
|
-
[Unreleased]: https://github.com/apify/mcpc/compare/v0.
|
|
172
|
+
[Unreleased]: https://github.com/apify/mcpc/compare/v0.2.0...HEAD
|
|
173
|
+
[0.2.0]: https://github.com/apify/mcpc/compare/v0.1.10...v0.2.0
|
|
188
174
|
[0.1.10]: https://github.com/apify/mcpc/compare/v0.1.9...v0.1.10
|
|
189
175
|
[0.1.9]: https://github.com/apify/mcpc/compare/v0.1.8...v0.1.9
|
|
190
176
|
[0.1.8]: https://github.com/apify/mcpc/compare/v0.1.7...v0.1.8
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ After all, UNIX-compatible shell script is THE most universal coding language.
|
|
|
13
13
|
|
|
14
14
|
- 🌎 **Compatible** - Works with any MCP server over Streamable HTTP or stdio.
|
|
15
15
|
- 🔄 **Persistent sessions** - Keep multiple server connections alive simultaneously.
|
|
16
|
-
- 🔧 **Strong MCP support** - Instructions, tools, resources, prompts, dynamic discovery.
|
|
16
|
+
- 🔧 **Strong MCP support** - Instructions, tools, resources, prompts, async tasks, dynamic discovery.
|
|
17
17
|
- 🔌 **Code mode** - JSON output enables integration with CLI tools like `jq` and scripting.
|
|
18
18
|
- 🤖 **AI sandboxing** - MCP proxy server to securely access authenticated sessions from AI-generated code.
|
|
19
19
|
- 🔒 **Secure** - Full OAuth 2.1 support, OS keychain for credentials storage.
|
|
@@ -129,13 +129,15 @@ Commands:
|
|
|
129
129
|
login <server> Interactively login to a server using OAuth and save profile
|
|
130
130
|
logout <server> Delete an authentication profile for a server
|
|
131
131
|
clean [resources...] Clean up mcpc data (sessions, profiles, logs, all)
|
|
132
|
+
grep <pattern> Search tools and instructions across all active sessions
|
|
132
133
|
x402 [subcommand] [args...] Configure an x402 payment wallet (EXPERIMENTAL)
|
|
133
134
|
help [command] [subcommand] Show help for a specific command
|
|
134
135
|
|
|
135
136
|
MCP session commands (after connecting):
|
|
136
|
-
<@session> Show MCP server info and
|
|
137
|
-
<@session> tools
|
|
138
|
-
<@session> tools-
|
|
137
|
+
<@session> Show MCP server info, capabilities, and tools
|
|
138
|
+
<@session> grep <pattern> Search tools, resources, or prompts
|
|
139
|
+
<@session> tools-list List all server tools
|
|
140
|
+
<@session> tools-get <name> Get tool details and schema
|
|
139
141
|
<@session> tools-call <name> [arg:=val ... | <json> | <stdin]
|
|
140
142
|
<@session> prompts-list
|
|
141
143
|
<@session> prompts-get <name> [arg:=val ... | <json> | <stdin]
|
|
@@ -265,6 +267,45 @@ mcpc @apify shell
|
|
|
265
267
|
Shell commands: `help`, `exit`/`quit`/Ctrl+D, Ctrl+C to cancel.
|
|
266
268
|
Arrow keys navigate history (saved to `~/.mcpc/history`).
|
|
267
269
|
|
|
270
|
+
### Grep (search across sessions)
|
|
271
|
+
|
|
272
|
+
`mcpc grep` searches tools, resources, and prompts across all active sessions or within a single session:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
# Search tools and server instructions in all active sessions
|
|
276
|
+
mcpc grep "search"
|
|
277
|
+
|
|
278
|
+
# Search within a single session
|
|
279
|
+
mcpc @apify grep "actor"
|
|
280
|
+
|
|
281
|
+
# Search resources and prompts instead of the default tools and instructions
|
|
282
|
+
mcpc grep "config" --resources --prompts
|
|
283
|
+
|
|
284
|
+
# Regex search
|
|
285
|
+
mcpc grep "search|find" -E
|
|
286
|
+
|
|
287
|
+
# Case-sensitive search (default is case-insensitive)
|
|
288
|
+
mcpc grep "Search" --case-sensitive
|
|
289
|
+
|
|
290
|
+
# Limit results
|
|
291
|
+
mcpc grep "e" -m 5
|
|
292
|
+
|
|
293
|
+
# JSON output for scripting
|
|
294
|
+
mcpc grep "actor" --json
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
By default, `grep` searches only tools. Use `--resources` or `--prompts` to search those types
|
|
298
|
+
(combine with `--tools` to include tools too). Sessions that are crashed or unavailable are shown
|
|
299
|
+
with their status rather than silently skipped.
|
|
300
|
+
|
|
301
|
+
The `grep` command is useful for **dynamic tool discovery**,
|
|
302
|
+
also called [Tool search tool](https://www.anthropic.com/engineering/advanced-tool-use) by Anthropic
|
|
303
|
+
or [Dynamic context discovery](https://cursor.com/blog/dynamic-context-discovery) by Cursor.
|
|
304
|
+
Rather than loading all tools into AI agent's context, the agent can use `grep` to discover the right tool
|
|
305
|
+
for the job, and only load the relevant tools into the context when needed to reduce token usage and improve accuracy.
|
|
306
|
+
|
|
307
|
+
<!-- TODO: explain this more, show diagram -->
|
|
308
|
+
|
|
268
309
|
### JSON mode
|
|
269
310
|
|
|
270
311
|
By default, `mcpc` prints output in Markdown-ish text format with colors, making it easy to read by both humans and AIs.
|
|
@@ -805,7 +846,7 @@ The bridge process manages the full MCP session lifecycle:
|
|
|
805
846
|
| 🔔 [**Notifications**](#list-change-notifications) | ✅ Supported |
|
|
806
847
|
| 📄 [**Pagination**](#pagination) | ✅ Supported |
|
|
807
848
|
| 🏓 [**Ping**](#ping) | ✅ Supported |
|
|
808
|
-
| ⏳ **Async tasks**
|
|
849
|
+
| ⏳ [**Async tasks**](#async-tasks) | ✅ Supported |
|
|
809
850
|
| 📁 **Roots** | 🚧 Planned |
|
|
810
851
|
| ❓ **Elicitation** | 🚧 Planned |
|
|
811
852
|
| 🔤 **Completion** | 🚧 Planned |
|
|
@@ -957,6 +998,35 @@ mcpc @apify ping
|
|
|
957
998
|
mcpc @apify ping --json
|
|
958
999
|
```
|
|
959
1000
|
|
|
1001
|
+
#### Async tasks
|
|
1002
|
+
|
|
1003
|
+
MCP servers can execute tools as [async tasks](https://modelcontextprotocol.io/specification/latest/server/utilities/tasks)
|
|
1004
|
+
that run in the background and report progress. `mcpc` supports the full task lifecycle:
|
|
1005
|
+
|
|
1006
|
+
```bash
|
|
1007
|
+
# Call a tool as a task (waits for completion, shows progress spinner)
|
|
1008
|
+
mcpc @apify tools-call long-running-job input:="data" --task
|
|
1009
|
+
|
|
1010
|
+
# Start a task and return immediately with the task ID
|
|
1011
|
+
mcpc @apify tools-call long-running-job input:="data" --detach
|
|
1012
|
+
|
|
1013
|
+
# List active tasks
|
|
1014
|
+
mcpc @apify tasks-list
|
|
1015
|
+
|
|
1016
|
+
# Check task status
|
|
1017
|
+
mcpc @apify tasks-get <taskId>
|
|
1018
|
+
|
|
1019
|
+
# Cancel a running task
|
|
1020
|
+
mcpc @apify tasks-cancel <taskId>
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
With `--task`, the CLI shows a progress spinner with elapsed time, server status messages,
|
|
1024
|
+
and progress notifications. Press **ESC** during execution to detach and get the task ID
|
|
1025
|
+
for later retrieval. With `--detach`, the task starts and returns the task ID immediately.
|
|
1026
|
+
|
|
1027
|
+
`tools-list` and `tools-get` show task support annotations per tool:
|
|
1028
|
+
`[task:optional]`, `[task:required]`, or `[task:forbidden]`.
|
|
1029
|
+
|
|
960
1030
|
## Configuration
|
|
961
1031
|
|
|
962
1032
|
You can configure `mcpc` using a config file, environment variables, or command-line flags.
|
|
@@ -1181,22 +1251,22 @@ See [CONTRIBUTING](./CONTRIBUTING.md) for development setup, architecture overvi
|
|
|
1181
1251
|
|
|
1182
1252
|
<!-- Stars and activity as of March 2026. -->
|
|
1183
1253
|
|
|
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 | ~
|
|
1187
|
-
| [steipete/mcporter](https://github.com/steipete/mcporter) | TS | ~
|
|
1188
|
-
| [IBM/mcp-cli](https://github.com/IBM/mcp-cli) | Python | ~1.9k | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ |
|
|
1189
|
-
| [
|
|
1190
|
-
| [
|
|
1191
|
-
| [
|
|
1192
|
-
| [
|
|
1193
|
-
| [
|
|
1194
|
-
| [
|
|
1195
|
-
| [mcpshim/mcpshim](https://github.com/mcpshim/mcpshim) | Go | ~
|
|
1196
|
-
| [evantahler/mcpx](https://github.com/evantahler/mcpx) | TS | ~26 | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | — |
|
|
1197
|
-
| [EstebanForge/mcp-cli-ent](https://github.com/EstebanForge/mcp-cli-ent) | Go | ~
|
|
1198
|
-
|
|
1199
|
-
**Legend:** ✅ = supported, ⚠️ = stale (no commits in 3+ months), **LLM** = requires/uses an LLM.
|
|
1254
|
+
| Tool | Lang | Stars | Active | Tools | Resources | Prompts | Tasks | Code mode | Sessions | OAuth | Stdio | HTTP | Tool search | LLM |
|
|
1255
|
+
| ----------------------------------------------------------------------- | ------ | ----: | ------ | ----- | --------- | ------- | ----- | --------- | -------- | ----- | ----- | ---- | ----------- | --- |
|
|
1256
|
+
| **[apify/mcpc](https://github.com/apify/mcpc)** | TS | ~400 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
|
|
1257
|
+
| [steipete/mcporter](https://github.com/steipete/mcporter) | TS | ~3.2k | ✅ | ✅ | — | — | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | — |
|
|
1258
|
+
| [IBM/mcp-cli](https://github.com/IBM/mcp-cli) | Python | ~1.9k | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ |
|
|
1259
|
+
| [knowsuchagency/mcp2cli](https://github.com/knowsuchagency/mcp2cli) | Python | ~1.7k | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — |
|
|
1260
|
+
| [f/mcptools](https://github.com/f/mcptools) | Go | ~1.5k | ⚠️ | ✅ | ✅ | ✅ | — | ✅ | — | — | ✅ | ✅ | — | — |
|
|
1261
|
+
| [philschmid/mcp-cli](https://github.com/philschmid/mcp-cli) | TS | ~1.0k | ✅ | ✅ | — | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — |
|
|
1262
|
+
| [adhikasp/mcp-client-cli](https://github.com/adhikasp/mcp-client-cli) | Python | ~670 | ⚠️ | ✅ | ✅ | ✅ | — | — | — | — | ✅ | — | — | ✅ |
|
|
1263
|
+
| [thellimist/clihub](https://github.com/thellimist/clihub) | Go | ~640 | ✅ | ✅ | — | — | — | — | — | ✅ | ✅ | ✅ | ✅ | — |
|
|
1264
|
+
| [wong2/mcp-cli](https://github.com/wong2/mcp-cli) | JS | ~430 | ⚠️ | ✅ | ✅ | ✅ | — | — | — | ✅ | — | ✅ | — | — |
|
|
1265
|
+
| [mcpshim/mcpshim](https://github.com/mcpshim/mcpshim) | Go | ~53 | ✅ | ✅ | — | — | — | ✅ | ✅ | ✅ | — | ✅ | ✅ | — |
|
|
1266
|
+
| [evantahler/mcpx](https://github.com/evantahler/mcpx) | TS | ~26 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | ✅ | — |
|
|
1267
|
+
| [EstebanForge/mcp-cli-ent](https://github.com/EstebanForge/mcp-cli-ent) | Go | ~15 | ✅ | ✅ | — | — | — | ✅ | ✅ | — | ✅ | ✅ | ✅ | — |
|
|
1268
|
+
|
|
1269
|
+
**Legend:** ✅ = supported, ⚠️ = stale (no commits in 3+ months), **Tasks** = [async tasks](https://modelcontextprotocol.io/specification/latest/server/utilities/tasks), **LLM** = requires/uses an LLM.
|
|
1200
1270
|
|
|
1201
1271
|
**Notes:**
|
|
1202
1272
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CommandOptions } from '../../lib/types.js';
|
|
2
|
+
export interface GrepOptions extends CommandOptions {
|
|
3
|
+
tools?: boolean | undefined;
|
|
4
|
+
resources?: boolean | undefined;
|
|
5
|
+
prompts?: boolean | undefined;
|
|
6
|
+
instructions?: boolean | undefined;
|
|
7
|
+
regex?: boolean | undefined;
|
|
8
|
+
caseSensitive?: boolean | undefined;
|
|
9
|
+
maxResults?: number | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function grepSession(session: string, pattern: string, options: GrepOptions): Promise<number>;
|
|
12
|
+
export declare function grepAllSessions(pattern: string, options: GrepOptions): Promise<number>;
|
|
13
|
+
//# sourceMappingURL=grep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/grep.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAA0B,cAAc,EAAe,MAAM,oBAAoB,CAAC;AAgB9F,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAoVD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CA+CjB;AAyBD,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAmL5F"}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { ClientError } from '../../lib/errors.js';
|
|
3
|
+
import { isProcessAlive } from '../../lib/utils.js';
|
|
4
|
+
import { consolidateSessions } from '../../lib/sessions.js';
|
|
5
|
+
import { withSessionClient } from '../../lib/session-client.js';
|
|
6
|
+
import { withMcpClient } from '../helpers.js';
|
|
7
|
+
import { formatJson, formatToolParamsInline, formatToolAnnotations, grayBacktick, inBackticks, } from '../output.js';
|
|
8
|
+
import { getBridgeStatus, formatBridgeStatus } from './sessions.js';
|
|
9
|
+
function buildMatcher(pattern, options) {
|
|
10
|
+
if (options.regex) {
|
|
11
|
+
let re;
|
|
12
|
+
try {
|
|
13
|
+
re = new RegExp(pattern, options.caseSensitive ? '' : 'i');
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
throw new ClientError(`Invalid regex pattern: ${pattern}\n${err instanceof Error ? err.message : String(err)}`);
|
|
17
|
+
}
|
|
18
|
+
return (text) => re.test(text);
|
|
19
|
+
}
|
|
20
|
+
if (options.caseSensitive) {
|
|
21
|
+
return (text) => text.includes(pattern);
|
|
22
|
+
}
|
|
23
|
+
const lowerPattern = pattern.toLowerCase();
|
|
24
|
+
return (text) => text.toLowerCase().includes(lowerPattern);
|
|
25
|
+
}
|
|
26
|
+
function getSearchTypes(options) {
|
|
27
|
+
const anyFilter = options.tools || options.resources || options.prompts || options.instructions;
|
|
28
|
+
return {
|
|
29
|
+
searchTools: anyFilter ? !!options.tools : true,
|
|
30
|
+
searchResources: !!options.resources,
|
|
31
|
+
searchPrompts: !!options.prompts,
|
|
32
|
+
searchInstructions: anyFilter ? !!options.instructions : true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function buildToolSearchText(tool, sessionName) {
|
|
36
|
+
return `${sessionName}/${tool.name} ${JSON.stringify(tool, null, 2)}`;
|
|
37
|
+
}
|
|
38
|
+
function buildResourceSearchText(resource, sessionName) {
|
|
39
|
+
return `${sessionName}/${resource.uri} ${JSON.stringify(resource, null, 2)}`;
|
|
40
|
+
}
|
|
41
|
+
function buildPromptSearchText(prompt, sessionName) {
|
|
42
|
+
return `${sessionName}/${prompt.name} ${JSON.stringify(prompt, null, 2)}`;
|
|
43
|
+
}
|
|
44
|
+
function buildInstructionsSearchText(instructions, sessionName) {
|
|
45
|
+
return `${sessionName} ${instructions}`;
|
|
46
|
+
}
|
|
47
|
+
async function searchClient(client, matcher, options, sessionName = '') {
|
|
48
|
+
const { searchTools, searchResources, searchPrompts, searchInstructions } = getSearchTypes(options);
|
|
49
|
+
const serverDetails = await client.getServerDetails();
|
|
50
|
+
const capabilities = serverDetails.capabilities;
|
|
51
|
+
const canListTools = searchTools && !!capabilities?.tools;
|
|
52
|
+
const canListResources = searchResources && !!capabilities?.resources;
|
|
53
|
+
const canListPrompts = searchPrompts && !!capabilities?.prompts;
|
|
54
|
+
const [toolsResult, resourcesResult, promptsResult] = await Promise.all([
|
|
55
|
+
canListTools ? client.listAllTools() : null,
|
|
56
|
+
canListResources ? fetchAllResources(client) : null,
|
|
57
|
+
canListPrompts ? fetchAllPrompts(client) : null,
|
|
58
|
+
]);
|
|
59
|
+
const matchedTools = (toolsResult?.tools ?? []).filter((t) => matcher(buildToolSearchText(t, sessionName)));
|
|
60
|
+
const matchedResources = (resourcesResult ?? []).filter((r) => matcher(buildResourceSearchText(r, sessionName)));
|
|
61
|
+
const matchedPrompts = (promptsResult ?? []).filter((p) => matcher(buildPromptSearchText(p, sessionName)));
|
|
62
|
+
const instructionsText = searchInstructions ? serverDetails.instructions : undefined;
|
|
63
|
+
const matchedInstructions = !!instructionsText && matcher(buildInstructionsSearchText(instructionsText, sessionName));
|
|
64
|
+
return {
|
|
65
|
+
tools: matchedTools,
|
|
66
|
+
resources: matchedResources,
|
|
67
|
+
prompts: matchedPrompts,
|
|
68
|
+
instructions: matchedInstructions,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function fetchAllResources(client) {
|
|
72
|
+
const all = [];
|
|
73
|
+
let cursor;
|
|
74
|
+
do {
|
|
75
|
+
const result = await client.listResources(cursor);
|
|
76
|
+
all.push(...result.resources);
|
|
77
|
+
cursor = result.nextCursor;
|
|
78
|
+
} while (cursor);
|
|
79
|
+
return all;
|
|
80
|
+
}
|
|
81
|
+
async function fetchAllPrompts(client) {
|
|
82
|
+
const all = [];
|
|
83
|
+
let cursor;
|
|
84
|
+
do {
|
|
85
|
+
const result = await client.listPrompts(cursor);
|
|
86
|
+
all.push(...result.prompts);
|
|
87
|
+
cursor = result.nextCursor;
|
|
88
|
+
} while (cursor);
|
|
89
|
+
return all;
|
|
90
|
+
}
|
|
91
|
+
function countMatches(result) {
|
|
92
|
+
return (result.tools.length +
|
|
93
|
+
result.resources.length +
|
|
94
|
+
result.prompts.length +
|
|
95
|
+
(result.instructions ? 1 : 0));
|
|
96
|
+
}
|
|
97
|
+
function countMatchesByType(result) {
|
|
98
|
+
return {
|
|
99
|
+
tools: result.tools.length,
|
|
100
|
+
resources: result.resources.length,
|
|
101
|
+
prompts: result.prompts.length,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function sumMatchesByType(results) {
|
|
105
|
+
return results.reduce((acc, r) => ({
|
|
106
|
+
tools: acc.tools + r.tools.length,
|
|
107
|
+
resources: acc.resources + r.resources.length,
|
|
108
|
+
prompts: acc.prompts + r.prompts.length,
|
|
109
|
+
}), { tools: 0, resources: 0, prompts: 0 });
|
|
110
|
+
}
|
|
111
|
+
function truncateResult(result, limit) {
|
|
112
|
+
const total = countMatches(result);
|
|
113
|
+
if (total <= limit)
|
|
114
|
+
return { result, truncated: 0 };
|
|
115
|
+
let remaining = limit;
|
|
116
|
+
const instructions = result.instructions && remaining > 0 ? true : false;
|
|
117
|
+
if (instructions)
|
|
118
|
+
remaining--;
|
|
119
|
+
const tools = result.tools.slice(0, remaining);
|
|
120
|
+
remaining -= tools.length;
|
|
121
|
+
const resources = result.resources.slice(0, remaining);
|
|
122
|
+
remaining -= resources.length;
|
|
123
|
+
const prompts = result.prompts.slice(0, remaining);
|
|
124
|
+
return {
|
|
125
|
+
result: { tools, resources, prompts, instructions },
|
|
126
|
+
truncated: total - limit,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function formatToolLine(tool) {
|
|
130
|
+
const bullet = chalk.dim('*');
|
|
131
|
+
const params = formatToolParamsInline(tool.inputSchema);
|
|
132
|
+
const parts = [];
|
|
133
|
+
const annotationsStr = formatToolAnnotations(tool.annotations);
|
|
134
|
+
if (annotationsStr)
|
|
135
|
+
parts.push(annotationsStr);
|
|
136
|
+
const toolAny = tool;
|
|
137
|
+
const execution = toolAny.execution;
|
|
138
|
+
const taskSupport = execution?.taskSupport;
|
|
139
|
+
if (taskSupport)
|
|
140
|
+
parts.push(`task:${taskSupport}`);
|
|
141
|
+
const suffix = parts.length > 0 ? ` ${chalk.gray(`[${parts.join(', ')}]`)}` : '';
|
|
142
|
+
return `${bullet} ${grayBacktick()}${chalk.cyan(tool.name)}${params}${grayBacktick()}${suffix}`;
|
|
143
|
+
}
|
|
144
|
+
function formatResourceLine(resource) {
|
|
145
|
+
const bullet = chalk.dim('*');
|
|
146
|
+
return `${bullet} ${inBackticks(resource.uri)}`;
|
|
147
|
+
}
|
|
148
|
+
function formatPromptLine(prompt) {
|
|
149
|
+
const bullet = chalk.dim('*');
|
|
150
|
+
return `${bullet} ${inBackticks(prompt.name)}`;
|
|
151
|
+
}
|
|
152
|
+
function formatResultSection(label, items, formatLine, indent) {
|
|
153
|
+
if (items.length === 0)
|
|
154
|
+
return [];
|
|
155
|
+
const lines = [];
|
|
156
|
+
lines.push(`${indent}${chalk.bold(`${label} (${items.length}):`)}`);
|
|
157
|
+
for (const item of items) {
|
|
158
|
+
lines.push(`${indent} ${formatLine(item)}`);
|
|
159
|
+
}
|
|
160
|
+
return lines;
|
|
161
|
+
}
|
|
162
|
+
function formatGrepResultHuman(result, indent = '') {
|
|
163
|
+
const lines = [];
|
|
164
|
+
if (result.instructions) {
|
|
165
|
+
lines.push(`${indent}${chalk.bold('Instructions')}`);
|
|
166
|
+
}
|
|
167
|
+
lines.push(...formatResultSection('Tools', result.tools, formatToolLine, indent));
|
|
168
|
+
lines.push(...formatResultSection('Resources', result.resources, formatResourceLine, indent));
|
|
169
|
+
lines.push(...formatResultSection('Prompts', result.prompts, formatPromptLine, indent));
|
|
170
|
+
return lines;
|
|
171
|
+
}
|
|
172
|
+
export async function grepSession(session, pattern, options) {
|
|
173
|
+
const matcher = buildMatcher(pattern, options);
|
|
174
|
+
const sessionRef = session.startsWith('@') ? session : `@${session}`;
|
|
175
|
+
return await withMcpClient(session, options, async (client) => {
|
|
176
|
+
const fullResult = await searchClient(client, matcher, options, sessionRef);
|
|
177
|
+
const total = countMatches(fullResult);
|
|
178
|
+
const { result, truncated } = options.maxResults != null
|
|
179
|
+
? truncateResult(fullResult, options.maxResults)
|
|
180
|
+
: { result: fullResult, truncated: 0 };
|
|
181
|
+
if (options.outputMode === 'json') {
|
|
182
|
+
const matchCounts = countMatchesByType(fullResult);
|
|
183
|
+
const jsonOutput = {
|
|
184
|
+
sessions: [
|
|
185
|
+
{
|
|
186
|
+
name: sessionRef,
|
|
187
|
+
status: 'live',
|
|
188
|
+
instructions: result.instructions,
|
|
189
|
+
tools: result.tools,
|
|
190
|
+
resources: result.resources,
|
|
191
|
+
prompts: result.prompts,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
totalMatches: {
|
|
195
|
+
...matchCounts,
|
|
196
|
+
...(truncated > 0 ? { truncated } : {}),
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
console.log(formatJson(jsonOutput));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
if (total === 0) {
|
|
203
|
+
console.log('No matches found.');
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const lines = formatGrepResultHuman(result);
|
|
207
|
+
lines.push('');
|
|
208
|
+
const suffix = truncated > 0 ? ` (showing ${countMatches(result)})` : '';
|
|
209
|
+
lines.push(chalk.dim(`${total} ${total === 1 ? 'match' : 'matches'}${suffix}.`));
|
|
210
|
+
console.log(lines.join('\n'));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return total > 0 ? 0 : 1;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function formatSkippedSession(skipped) {
|
|
217
|
+
const { dot, text } = formatBridgeStatus(skipped.status);
|
|
218
|
+
return `${chalk.cyan(skipped.name)} ${dot} ${text}`;
|
|
219
|
+
}
|
|
220
|
+
function isSessionQueryable(session) {
|
|
221
|
+
return !!session.pid && isProcessAlive(session.pid);
|
|
222
|
+
}
|
|
223
|
+
export async function grepAllSessions(pattern, options) {
|
|
224
|
+
const matcher = buildMatcher(pattern, options);
|
|
225
|
+
const { sessions } = await consolidateSessions(false);
|
|
226
|
+
const allSessionEntries = Object.values(sessions);
|
|
227
|
+
if (allSessionEntries.length === 0) {
|
|
228
|
+
if (options.outputMode === 'json') {
|
|
229
|
+
console.log(formatJson({ sessions: [], totalMatches: { tools: 0, resources: 0, prompts: 0 } }));
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
console.log(chalk.bold('No active sessions.'));
|
|
233
|
+
console.log(chalk.dim(' \u21B3 run: mcpc connect mcp.example.com @test'));
|
|
234
|
+
}
|
|
235
|
+
return 1;
|
|
236
|
+
}
|
|
237
|
+
const toSessionRef = (name) => (name.startsWith('@') ? name : `@${name}`);
|
|
238
|
+
const queryableSessions = [];
|
|
239
|
+
const skippedSessions = [];
|
|
240
|
+
for (const session of allSessionEntries) {
|
|
241
|
+
if (isSessionQueryable(session)) {
|
|
242
|
+
queryableSessions.push(session);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
const status = getBridgeStatus(session);
|
|
246
|
+
skippedSessions.push({
|
|
247
|
+
name: toSessionRef(session.name),
|
|
248
|
+
status,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const settled = await Promise.allSettled(queryableSessions.map(async (session) => {
|
|
253
|
+
const sessionName = toSessionRef(session.name);
|
|
254
|
+
const result = await withSessionClient(sessionName, async (client) => {
|
|
255
|
+
return searchClient(client, matcher, options, sessionName);
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
name: sessionName,
|
|
259
|
+
...result,
|
|
260
|
+
};
|
|
261
|
+
}));
|
|
262
|
+
const results = [];
|
|
263
|
+
const resultsWithMatches = [];
|
|
264
|
+
const errors = [];
|
|
265
|
+
for (const [i, outcome] of settled.entries()) {
|
|
266
|
+
if (outcome.status === 'fulfilled') {
|
|
267
|
+
results.push(outcome.value);
|
|
268
|
+
if (countMatches(outcome.value) > 0) {
|
|
269
|
+
resultsWithMatches.push(outcome.value);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const reason = outcome.reason;
|
|
274
|
+
errors.push({
|
|
275
|
+
name: toSessionRef(queryableSessions[i].name),
|
|
276
|
+
error: reason instanceof Error ? reason.message : String(reason),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const totalMatches = sumMatchesByType(results);
|
|
281
|
+
const totalMatchCount = results.reduce((sum, r) => sum + countMatches(r), 0);
|
|
282
|
+
let displayResultsWithMatches = resultsWithMatches;
|
|
283
|
+
let totalTruncated = 0;
|
|
284
|
+
if (options.maxResults != null) {
|
|
285
|
+
let remaining = options.maxResults;
|
|
286
|
+
displayResultsWithMatches = [];
|
|
287
|
+
for (const r of resultsWithMatches) {
|
|
288
|
+
if (remaining <= 0) {
|
|
289
|
+
totalTruncated += countMatches(r);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const { result: truncR, truncated } = truncateResult(r, remaining);
|
|
293
|
+
remaining -= countMatches(truncR);
|
|
294
|
+
totalTruncated += truncated;
|
|
295
|
+
displayResultsWithMatches.push({ ...r, ...truncR });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (options.outputMode === 'json') {
|
|
299
|
+
const truncatedResultsByName = new Map(displayResultsWithMatches.map((r) => [r.name, r]));
|
|
300
|
+
const sessionEntries = [
|
|
301
|
+
...results.map((r) => {
|
|
302
|
+
if (options.maxResults == null) {
|
|
303
|
+
return {
|
|
304
|
+
name: r.name,
|
|
305
|
+
status: 'live',
|
|
306
|
+
instructions: r.instructions,
|
|
307
|
+
tools: r.tools,
|
|
308
|
+
resources: r.resources,
|
|
309
|
+
prompts: r.prompts,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const display = truncatedResultsByName.get(r.name);
|
|
313
|
+
return {
|
|
314
|
+
name: r.name,
|
|
315
|
+
status: 'live',
|
|
316
|
+
instructions: display?.instructions ?? false,
|
|
317
|
+
tools: display?.tools ?? [],
|
|
318
|
+
resources: display?.resources ?? [],
|
|
319
|
+
prompts: display?.prompts ?? [],
|
|
320
|
+
};
|
|
321
|
+
}),
|
|
322
|
+
...errors.map((e) => ({
|
|
323
|
+
name: e.name,
|
|
324
|
+
status: 'error',
|
|
325
|
+
error: e.error,
|
|
326
|
+
})),
|
|
327
|
+
...skippedSessions.map((s) => ({
|
|
328
|
+
name: s.name,
|
|
329
|
+
status: s.status,
|
|
330
|
+
})),
|
|
331
|
+
];
|
|
332
|
+
const jsonOutput = {
|
|
333
|
+
sessions: sessionEntries,
|
|
334
|
+
totalMatches: {
|
|
335
|
+
...totalMatches,
|
|
336
|
+
...(totalTruncated > 0 ? { truncated: totalTruncated } : {}),
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
console.log(formatJson(jsonOutput));
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const lines = [];
|
|
343
|
+
for (const skipped of skippedSessions) {
|
|
344
|
+
lines.push(formatSkippedSession(skipped));
|
|
345
|
+
}
|
|
346
|
+
for (const r of displayResultsWithMatches) {
|
|
347
|
+
lines.push(chalk.cyan(r.name));
|
|
348
|
+
lines.push(...formatGrepResultHuman(r, ' '));
|
|
349
|
+
lines.push('');
|
|
350
|
+
}
|
|
351
|
+
for (const err of errors) {
|
|
352
|
+
lines.push(chalk.yellow(`Warning: ${err.name} \u2014 ${err.error}`));
|
|
353
|
+
}
|
|
354
|
+
if (totalMatchCount === 0 && skippedSessions.length === 0) {
|
|
355
|
+
console.log('No matches found.');
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
if (totalMatchCount > 0) {
|
|
359
|
+
const sessionCount = resultsWithMatches.length;
|
|
360
|
+
const showing = totalMatchCount - totalTruncated;
|
|
361
|
+
const suffix = totalTruncated > 0 ? ` (showing ${showing})` : '';
|
|
362
|
+
lines.push(chalk.dim(`${totalMatchCount} ${totalMatchCount === 1 ? 'match' : 'matches'} across ${sessionCount} ${sessionCount === 1 ? 'session' : 'sessions'}${suffix}.`));
|
|
363
|
+
}
|
|
364
|
+
else if (lines.length > 0) {
|
|
365
|
+
lines.push('');
|
|
366
|
+
lines.push('No matches found.');
|
|
367
|
+
}
|
|
368
|
+
console.log(lines.join('\n'));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return totalMatchCount > 0 ? 0 : 1;
|
|
372
|
+
}
|
|
373
|
+
//# sourceMappingURL=grep.js.map
|