openclaw-mcp-router 1.0.1 โ†’ 1.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.
@@ -0,0 +1,14 @@
1
+ ## Summary
2
+ -
3
+
4
+ ## Why
5
+ -
6
+
7
+ ## Validation
8
+ - [ ] `npm test` passes locally
9
+
10
+ ## Documentation Checklist
11
+ - [ ] README updated (if behavior/config/user workflow changed)
12
+ - [ ] docs/ updated to match README and implementation
13
+ - [ ] No README/docs drift introduced
14
+ - [ ] skills/ content updated to match behavior/config changes (if applicable)
@@ -0,0 +1,10 @@
1
+ openclaw
2
+ model-context-protocol
3
+ mcp
4
+ mcp-router
5
+ ai-agents
6
+ semantic-search
7
+ tool-discovery
8
+ lancedb
9
+ ollama
10
+ agent-tools
package/README.md CHANGED
@@ -1,129 +1,180 @@
1
1
  # OpenClaw MCP Router ๐Ÿš€
2
2
 
3
- **OpenClaw MCP Router** is a dynamic tool discovery layer for [OpenClaw](https://openclaw.ai). It uses semantic vector search to eliminate **Context Bloat** by routing only the necessary Model Context Protocol (MCP) tool schemas to your agent on-demand.
3
+ **OpenClaw MCP Router** is a dynamic **MCP (Model Context Protocol) tool router** for OpenClaw. It uses semantic search to discover the right MCP tools at runtime, so agents avoid loading huge tool catalogs into the system prompt.
4
4
 
5
- ## โšก The Problem: Context Window Exhaustion
6
5
 
7
- Modern MCP catalogs are growing. Loading every tool schema upfront is expensive and inefficient:
6
+ Instead of injecting every MCP schema up front, it provides two lightweight meta-tools:
8
7
 
9
- * **Token Waste:** 5 MCP servers with 50+ tools can burn **55kโ€“134k tokens** before your agent even says "Hello."
10
- * **Performance Hit:** Massive system prompts degrade reasoning accuracy (the "lost in the middle" phenomenon).
11
- * **Cost:** High token usage leads to higher API costs for every turn of the conversation.
8
+ - `mcp_search` โ†’ discover the right tool at runtime
9
+ - `mcp_call` โ†’ execute as JSON fallback
12
10
 
13
- ## ๐Ÿ› ๏ธ The Solution: Semantic Tool Routing
11
+ This cuts context bloat, improves tool selection quality, and reduces token cost on large MCP server inventories.
14
12
 
15
- Instead of a full schema dump, this plugin registers two lightweight "Meta-Tools":
13
+ ---
16
14
 
17
- 1. **`mcp_search(query)`**: Uses **Ollama** and **LanceDB** to perform a semantic search. It returns only the top-N most relevant tool definitions (reducing overhead by ~95%).
18
- 2. **`mcp_call(tool_name, params)`**: Dynamically resolves the owning MCP server and executes the call.
15
+ ## Keywords
19
16
 
20
- > **Result:** Your agent "asks" for the tools it needs, keeping the context window clean and the reasoning sharp.
17
+ OpenClaw plugin, MCP router, Model Context Protocol, tool discovery, semantic tool search, AI agent tooling, LanceDB, Ollama embeddings.
21
18
 
22
- ---
19
+ ## Why this exists
23
20
 
24
- ## ๐Ÿš€ Quick Start
21
+ Large MCP catalogs are expensive in prompt space.
25
22
 
26
- ### 1. Prerequisites
23
+ - **Token waste:** tens of thousands of tokens before first user turn
24
+ - **Reasoning quality loss:** "lost in the middle" on oversized prompts
25
+ - **Higher cost:** more prompt tokens every turn
27
26
 
28
- Ensure you have **Ollama** running locally with an embedding model:
27
+ MCP Router applies Anthropic's tool-search pattern so only relevant tools are surfaced when needed.
29
28
 
30
- ```bash
31
- ollama pull embeddinggemma
29
+ Refs:
30
+ - Tool search / advanced tool use: <https://www.anthropic.com/engineering/advanced-tool-use>
31
+ - Code execution with MCP: <https://www.anthropic.com/engineering/code-execution-with-mcp>
32
32
 
33
- ```
33
+ ---
34
34
 
35
- ### 2. Installation
35
+ ## Core model
36
36
 
37
- ```bash
38
- openclaw plugins install openclaw-mcp-router
37
+ ### 1) Index time (`reindex`)
38
+ - Connect to configured MCP servers
39
+ - List tools
40
+ - Embed tool text
41
+ - Store vectors in LanceDB
42
+ - Register toolโ†’server ownership
43
+ - *(Optional)* generate CLI artifacts via `mcporter generate-cli`
39
44
 
40
- ```
45
+ ### 2) Runtime (`mcp_search`)
46
+ - Semantic search over indexed tools
47
+ - Default schema verbosity is adaptive:
48
+ - if `mcporter` is available: compact cards by default
49
+ - if `mcporter` is not available: include JSON params by default
50
+ - Full JSON schema can always be forced with `include_schema=true`
41
51
 
42
- ### 3. Setup & Indexing
52
+ ### 3) Execute (`mcp_call`)
53
+ - JSON-based execution path (classic MCP params flow)
43
54
 
44
- Run the interactive wizard to configure your servers and automatically update your `alsoAllow` permissions:
55
+ ---
45
56
 
46
- ```bash
47
- openclaw openclaw-mcp-router setup
48
- openclaw openclaw-mcp-router reindex
57
+ ## CLI-first behavior (new)
49
58
 
50
- ```
59
+ Router is now optimized for a CLI-first workflow:
60
+
61
+ - Prefer: `mcporter call <server>.<tool> ...`
62
+ - Fallback: `mcp_call(tool_name, params_json)`
63
+
64
+ `mcp_search` adapts to environment: compact when mcporter is present, schema-forward when it is not (so agents can drive `mcp_call` reliably).
51
65
 
52
66
  ---
53
67
 
54
- ## โš™๏ธ Configuration
68
+ ## Use cases
55
69
 
56
- The plugin is highly configurable via `~/.openclaw/openclaw.json`.
70
+ - Route large MCP tool catalogs without prompt bloat
71
+ - Improve tool selection for coding and automation agents
72
+ - Keep system prompts small while preserving broad MCP capability
73
+ - Enable CLI-first MCP execution with JSON fallback
57
74
 
58
- ### Server Management
75
+ ## Quick start
59
76
 
60
- You can manage servers via the **Interactive TUI**:
77
+ ### Prerequisites
61
78
 
62
79
  ```bash
63
- openclaw openclaw-mcp-router control
80
+ ollama pull embeddinggemma
81
+ ```
82
+
83
+ ### Install
64
84
 
85
+ ```bash
86
+ openclaw plugins install openclaw-mcp-router
65
87
  ```
66
88
 
67
- ### Manual Schema Example
89
+ ### Setup + index
68
90
 
69
- For power users, add servers directly to your `plugins.entries`:
91
+ ```bash
92
+ openclaw openclaw-mcp-router setup
93
+ openclaw openclaw-mcp-router reindex
94
+ ```
95
+
96
+ The setup wizard auto-detects whether `mcporter` is installed and suggests a sensible default for `mcp_search` schema verbosity.
70
97
 
71
- | Key | Description | Default |
72
- | --- | --- | --- |
73
- | `topK` | Number of tools returned per search | `5` |
74
- | `minScore` | Similarity threshold (0.0 - 1.0) | `0.3` |
75
- | `maxRetries` | Connection attempts for slow servers | `3` |
98
+ ---
99
+
100
+ ## Key configuration
101
+
102
+ In `~/.openclaw/openclaw.json` under `plugins.entries.openclaw-mcp-router.config`:
76
103
 
77
104
  ```json5
78
- // ~/.openclaw/openclaw.json
79
105
  {
80
- "plugins": {
81
- "entries": {
82
- "openclaw-mcp-router": {
83
- "enabled": true,
84
- "config": {
85
- "servers": [{ "name": "filesystem", "transport": "stdio", "command": "npx", "args": ["..."] }],
86
- "embedding": { "provider": "ollama", "model": "embeddinggemma" }
87
- }
88
- }
89
- }
106
+ "search": {
107
+ "topK": 5,
108
+ "minScore": 0.3
109
+ // includeParametersDefault optional:
110
+ // true -> always include params
111
+ // false -> always compact
112
+ // unset -> auto (based on mcporter availability)
113
+ },
114
+ "indexer": {
115
+ "connectTimeout": 60000,
116
+ "maxRetries": 3,
117
+ "initialRetryDelay": 2000,
118
+ "maxRetryDelay": 30000,
119
+ "maxChunkChars": 500,
120
+ "overlapChars": 100,
121
+ "generateCliArtifacts": false
90
122
  }
91
123
  }
92
-
93
124
  ```
94
125
 
95
- ---
126
+ ### Notes
96
127
 
97
- ## ๐Ÿง  How It Works: Under the Hood
128
+ - `search.includeParametersDefault` is optional; if omitted, router auto-decides based on mcporter availability.
129
+ - `indexer.generateCliArtifacts=true` enables best-effort per-server `mcporter generate-cli` during reindex.
130
+ - `mcp_call` stays the classic JSON meta-tool (no backend mode flag).
98
131
 
99
- 1. **Indexing:** During `reindex`, the router connects to all configured MCP servers, fetches their manifests, and generates vector embeddings for every tool description.
100
- 2. **Storage:** These embeddings are stored in a local **LanceDB** instance for sub-millisecond retrieval.
101
- 3. **Runtime Discovery:** * Agent detects a task (e.g., "Analyze this CSV").
102
- * Agent calls `mcp_search("read or analyze csv files")`.
103
- * Router returns the `filesystem` tool schema.
104
- * Agent executes the tool via `mcp_call`.
132
+ ---
105
133
 
134
+ ## Server management
106
135
 
136
+ ```bash
137
+ openclaw openclaw-mcp-router control
138
+ openclaw openclaw-mcp-router list
139
+ openclaw openclaw-mcp-router add <name> <command-or-url> [...]
140
+ openclaw openclaw-mcp-router reindex
141
+ ```
107
142
 
108
143
  ---
109
144
 
110
- ## ๐Ÿ“ˆ Performance & Benchmarks
145
+ ## MCPorter inspiration
146
+
147
+ Huge thanks to **@steipete** and [mcporter](https://github.com/steipete/mcporter) for the CLI-first MCP execution model inspiration.
111
148
 
112
- Based on the [Anthropic Tool Search](https://www.anthropic.com/engineering/advanced-tool-use) pattern, dynamic routing can improve tool selection accuracy significantly:
149
+ ---
150
+
151
+ ## Documentation
113
152
 
114
- * **Standard Loading:** ~49% Accuracy (Large catalogs)
115
- * **Dynamic Routing:** **~88% Accuracy** (Opus 4.5 benchmarks)
153
+ - Architecture + flow details: `docs/CLI_FIRST_WORKFLOW.md`
154
+ - Plugin config schema: `openclaw.plugin.json`
155
+ - Skill usage examples: `skills/mcp-router/`
116
156
 
117
157
  ---
118
158
 
119
- ## ๐Ÿค Contributing
159
+ ## Contributing
160
+
161
+ PRs welcome โ€” especially around:
162
+ - better reranking
163
+ - hybrid retrieval (vector + lexical)
164
+
165
+ PR hygiene:
166
+ - keep README + docs in sync for every behavior/config/workflow change
167
+ - keep `skills/` guidance in sync when user-facing behavior changes
168
+ - run test suite before opening PR
169
+
170
+ ## License
120
171
 
121
- We are looking to implement **Hybrid Search (BM25)** and **LLM-based Reranking**. If you're interested in improving LLM orchestration efficiency, we'd love your help!
172
+ MIT
122
173
 
123
- 1. Fork the repo.
124
- 2. Create your feature branch.
125
- 3. Submit a PR.
126
174
 
127
- ## ๐Ÿ“„ License
175
+ ## GitHub SEO checklist
128
176
 
129
- Released under the [MIT License](https://www.google.com/search?q=LICENSE).
177
+ - Set repo description to include: `OpenClaw`, `MCP`, `Model Context Protocol`, `semantic search`
178
+ - Add GitHub topics (see `.github/topics.txt`)
179
+ - Pin this repository on your profile if it's a flagship plugin
180
+ - Link this repo from related blog posts, docs, and demos
@@ -0,0 +1,76 @@
1
+ # CLI-First Workflow (MCP Router + MCPorter)
2
+
3
+ This document explains the intended execution model:
4
+
5
+ 1. Reindex tools + metadata
6
+ 2. Search tools with adaptive schema verbosity
7
+ 3. Prefer CLI calls when available
8
+ 4. Use `mcp_call` as classic JSON fallback
9
+
10
+ ## Reindex behavior
11
+
12
+ During `openclaw openclaw-mcp-router reindex`:
13
+
14
+ - Router connects to enabled MCP servers
15
+ - Lists tools
16
+ - Chunks/embeds descriptions
17
+ - Stores vectors + metadata in LanceDB
18
+
19
+ Optional:
20
+ - If `indexer.generateCliArtifacts=true`, router runs best-effort `mcporter generate-cli` per server.
21
+ - Generation failures do **not** block indexing.
22
+
23
+ ## `mcp_search` behavior
24
+
25
+ `mcp_search` supports adaptive defaults:
26
+
27
+ - If `mcporter` is installed: default to compact cards (save tokens)
28
+ - If `mcporter` is not installed: include JSON params by default (so agents can call `mcp_call` reliably)
29
+
30
+ Overrides:
31
+ - request-level: `include_schema=true|false`
32
+ - config-level: `search.includeParametersDefault=true|false`
33
+ - if unset, auto mode is used
34
+
35
+ ## `mcp_call` behavior
36
+
37
+ `mcp_call` remains the original JSON meta-tool path:
38
+
39
+ - resolve tool owner by registry
40
+ - open MCP connection
41
+ - call tool with `params_json`
42
+ - return content/errors
43
+
44
+ No backend mode flag is required.
45
+
46
+ ## Recommended config
47
+
48
+ ```json5
49
+ {
50
+ "plugins": {
51
+ "entries": {
52
+ "openclaw-mcp-router": {
53
+ "enabled": true,
54
+ "config": {
55
+ "search": {
56
+ "topK": 5,
57
+ "minScore": 0.3
58
+ // includeParametersDefault optional (true|false)
59
+ },
60
+ "indexer": {
61
+ "generateCliArtifacts": true
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ ## Acknowledgement
71
+
72
+ Special thanks to **@steipete** and **MCPorter**:
73
+ <https://github.com/steipete/mcporter>
74
+
75
+ Anthropic reference:
76
+ <https://www.anthropic.com/engineering/code-execution-with-mcp>
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-mcp-router",
3
3
  "name": "MCP Router",
4
- "description": "Semantic search across MCP tool catalogs. Reduces context bloat from ~77kโ†’8.7k tokens by dynamically surfacing only relevant tools.",
4
+ "description": "Semantic search across MCP tool catalogs. Reduces context bloat from ~77k\u21928.7k tokens by dynamically surfacing only relevant tools.",
5
5
  "configSchema": {
6
6
  "type": "object",
7
7
  "additionalProperties": false,
@@ -12,14 +12,45 @@
12
12
  "additionalProperties": {
13
13
  "type": "object",
14
14
  "properties": {
15
- "command": { "type": "string" },
16
- "args": { "type": "array", "items": { "type": "string" } },
17
- "env": { "type": "object", "additionalProperties": { "type": "string" } },
18
- "url": { "type": "string" },
19
- "serverUrl": { "type": "string" },
20
- "type": { "type": "string", "enum": ["stdio", "sse", "http"] },
21
- "headers": { "type": "object", "additionalProperties": { "type": "string" } },
22
- "timeout": { "type": "number", "description": "Per-server connect timeout in ms; overrides indexer.connectTimeout" }
15
+ "command": {
16
+ "type": "string"
17
+ },
18
+ "args": {
19
+ "type": "array",
20
+ "items": {
21
+ "type": "string"
22
+ }
23
+ },
24
+ "env": {
25
+ "type": "object",
26
+ "additionalProperties": {
27
+ "type": "string"
28
+ }
29
+ },
30
+ "url": {
31
+ "type": "string"
32
+ },
33
+ "serverUrl": {
34
+ "type": "string"
35
+ },
36
+ "type": {
37
+ "type": "string",
38
+ "enum": [
39
+ "stdio",
40
+ "sse",
41
+ "http"
42
+ ]
43
+ },
44
+ "headers": {
45
+ "type": "object",
46
+ "additionalProperties": {
47
+ "type": "string"
48
+ }
49
+ },
50
+ "timeout": {
51
+ "type": "number",
52
+ "description": "Per-server connect timeout in ms; overrides indexer.connectTimeout"
53
+ }
23
54
  }
24
55
  }
25
56
  },
@@ -32,16 +63,50 @@
32
63
  "description": "Legacy server array format. Prefer mcpServers dict.",
33
64
  "items": {
34
65
  "type": "object",
35
- "required": ["name", "transport"],
66
+ "required": [
67
+ "name",
68
+ "transport"
69
+ ],
36
70
  "properties": {
37
- "name": { "type": "string" },
38
- "transport": { "type": "string", "enum": ["stdio", "sse", "http"] },
39
- "command": { "type": "string" },
40
- "args": { "type": "array", "items": { "type": "string" } },
41
- "env": { "type": "object", "additionalProperties": { "type": "string" } },
42
- "url": { "type": "string" },
43
- "headers": { "type": "object", "additionalProperties": { "type": "string" } },
44
- "timeout": { "type": "number", "description": "Per-server connect timeout in ms; overrides indexer.connectTimeout" }
71
+ "name": {
72
+ "type": "string"
73
+ },
74
+ "transport": {
75
+ "type": "string",
76
+ "enum": [
77
+ "stdio",
78
+ "sse",
79
+ "http"
80
+ ]
81
+ },
82
+ "command": {
83
+ "type": "string"
84
+ },
85
+ "args": {
86
+ "type": "array",
87
+ "items": {
88
+ "type": "string"
89
+ }
90
+ },
91
+ "env": {
92
+ "type": "object",
93
+ "additionalProperties": {
94
+ "type": "string"
95
+ }
96
+ },
97
+ "url": {
98
+ "type": "string"
99
+ },
100
+ "headers": {
101
+ "type": "object",
102
+ "additionalProperties": {
103
+ "type": "string"
104
+ }
105
+ },
106
+ "timeout": {
107
+ "type": "number",
108
+ "description": "Per-server connect timeout in ms; overrides indexer.connectTimeout"
109
+ }
45
110
  }
46
111
  }
47
112
  },
@@ -49,34 +114,98 @@
49
114
  "type": "object",
50
115
  "description": "Indexer retry and timeout settings",
51
116
  "properties": {
52
- "connectTimeout": { "type": "number", "description": "Per-server default connect timeout in ms (default: 60000)" },
53
- "maxRetries": { "type": "number", "minimum": 0, "description": "Retry attempts per server, 0 = no retry (default: 3)" },
54
- "initialRetryDelay": { "type": "number", "description": "Initial backoff delay in ms (default: 2000)" },
55
- "maxRetryDelay": { "type": "number", "description": "Max backoff cap in ms (default: 30000)" },
56
- "maxChunkChars": { "type": "number", "minimum": 0, "description": "Max chars per chunk for long tool descriptions. 0 = disable chunking (default: 500)" },
57
- "overlapChars": { "type": "number", "minimum": 0, "description": "Overlap chars between adjacent chunks (default: 100)" }
117
+ "connectTimeout": {
118
+ "type": "number",
119
+ "description": "Per-server default connect timeout in ms (default: 60000)"
120
+ },
121
+ "maxRetries": {
122
+ "type": "number",
123
+ "minimum": 0,
124
+ "description": "Retry attempts per server, 0 = no retry (default: 3)"
125
+ },
126
+ "initialRetryDelay": {
127
+ "type": "number",
128
+ "description": "Initial backoff delay in ms (default: 2000)"
129
+ },
130
+ "maxRetryDelay": {
131
+ "type": "number",
132
+ "description": "Max backoff cap in ms (default: 30000)"
133
+ },
134
+ "maxChunkChars": {
135
+ "type": "number",
136
+ "minimum": 0,
137
+ "description": "Max chars per chunk for long tool descriptions. 0 = disable chunking (default: 500)"
138
+ },
139
+ "overlapChars": {
140
+ "type": "number",
141
+ "minimum": 0,
142
+ "description": "Overlap chars between adjacent chunks (default: 100)"
143
+ },
144
+ "generateCliArtifacts": {
145
+ "type": "boolean",
146
+ "description": "Generate mcporter CLI artifacts during reindex (default: false)"
147
+ }
58
148
  }
59
149
  },
60
150
  "embedding": {
61
151
  "type": "object",
62
152
  "properties": {
63
- "provider": { "type": "string", "enum": ["openai", "gemini", "voyage", "mistral", "ollama"] },
64
- "model": { "type": "string" },
65
- "baseUrl": { "type": "string" },
66
- "url": { "type": "string", "description": "Deprecated: use baseUrl. Old Ollama URL (gets /v1 appended)." },
67
- "apiKey": { "type": "string" },
68
- "headers": { "type": "object", "additionalProperties": { "type": "string" } }
153
+ "provider": {
154
+ "type": "string",
155
+ "enum": [
156
+ "openai",
157
+ "gemini",
158
+ "voyage",
159
+ "mistral",
160
+ "ollama"
161
+ ]
162
+ },
163
+ "model": {
164
+ "type": "string"
165
+ },
166
+ "baseUrl": {
167
+ "type": "string"
168
+ },
169
+ "url": {
170
+ "type": "string",
171
+ "description": "Deprecated: use baseUrl. Old Ollama URL (gets /v1 appended)."
172
+ },
173
+ "apiKey": {
174
+ "type": "string"
175
+ },
176
+ "headers": {
177
+ "type": "object",
178
+ "additionalProperties": {
179
+ "type": "string"
180
+ }
181
+ }
69
182
  }
70
183
  },
71
184
  "vectorDb": {
72
185
  "type": "object",
73
- "properties": { "path": { "type": "string" } }
186
+ "properties": {
187
+ "path": {
188
+ "type": "string"
189
+ }
190
+ }
74
191
  },
75
192
  "search": {
76
193
  "type": "object",
77
194
  "properties": {
78
- "topK": { "type": "number", "minimum": 1, "maximum": 20 },
79
- "minScore": { "type": "number", "minimum": 0, "maximum": 1 }
195
+ "topK": {
196
+ "type": "number",
197
+ "minimum": 1,
198
+ "maximum": 20
199
+ },
200
+ "minScore": {
201
+ "type": "number",
202
+ "minimum": 0,
203
+ "maximum": 1
204
+ },
205
+ "includeParametersDefault": {
206
+ "type": "boolean",
207
+ "description": "Include full input schema by default in mcp_search results. If omitted, defaults automatically based on mcporter availability."
208
+ }
80
209
  }
81
210
  }
82
211
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "openclaw-mcp-router",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
- "description": "Dynamic MCP tool router for OpenClaw โ€” semantic search over large MCP catalogs to eliminate context bloat",
5
+ "description": "OpenClaw MCP router plugin for Model Context Protocol (MCP): semantic tool search and dynamic tool calling to reduce prompt bloat",
6
6
  "type": "module",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -11,9 +11,13 @@
11
11
  },
12
12
  "keywords": [
13
13
  "openclaw",
14
+ "openclaw-plugin",
14
15
  "mcp",
15
- "plugin",
16
- "vector-search",
16
+ "model-context-protocol",
17
+ "mcp-router",
18
+ "tool-discovery",
19
+ "semantic-search",
20
+ "ai-agents",
17
21
  "lancedb",
18
22
  "ollama"
19
23
  ],
@@ -44,5 +48,9 @@
44
48
  "skills": [
45
49
  "./skills"
46
50
  ]
51
+ },
52
+ "homepage": "https://github.com/lunarmoon26/openclaw-mcp-router#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/lunarmoon26/openclaw-mcp-router/issues"
47
55
  }
48
56
  }
@@ -21,13 +21,23 @@ If a relevant tool exists, use it with `mcp_call`.
21
21
  - Use an action-oriented query: `"create github pull request"`, `"query postgres"`, `"send slack message"`.
22
22
  2. **Select tool**
23
23
  - Prefer best intent match + feasible required params.
24
- 3. **Read schema**
25
- - Identify required fields, types, enums, nested structure.
26
- 4. **Call tool**
27
- - `mcp_call("exact_tool_name", "{...valid JSON...}")`
24
+ 3. **Decide execution path**
25
+ - If `mcporter` is available, prefer CLI invocation style shown in search hints.
26
+ - Otherwise, use `mcp_call` with JSON params.
27
+ 4. **Read schema when needed**
28
+ - Use `include_schema=true` on `mcp_search` when full parameter detail is required.
28
29
  5. **Recover on failure**
29
30
  - Fix schema/type mismatch or re-search with rewritten query.
30
31
 
32
+ ## Adaptive `mcp_search` Defaults
33
+
34
+ - Default schema verbosity can be auto-configured by environment:
35
+ - `mcporter` installed โ†’ compact search cards by default
36
+ - `mcporter` not installed โ†’ include parameter schema by default
37
+ - Overrides:
38
+ - Per call: `include_schema=true|false`
39
+ - Config: `search.includeParametersDefault=true|false`
40
+
31
41
  ## Query Rewrite Ladder (Deterministic)
32
42
 
33
43
  If search quality is poor, retry in this order:
@@ -50,7 +60,7 @@ When multiple tools match, rank by:
50
60
 
51
61
  ## `mcp_call` Parameter Checklist
52
62
 
53
- `params_json` must be a **JSON string**.
63
+ `mcp_call` is the classic MCP JSON meta-tool. `params_json` must be a **JSON string**.
54
64
 
55
65
  - Include all required fields.
56
66
  - Match exact types (`42` vs `"42"`, `true` vs `"true"`).
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: mcp-server-manager
3
+ description: Manage MCP servers for openclaw-mcp-router (add/list/enable/disable/remove/reindex/setup/control), including choosing openclaw.json vs ~/.openclaw/openclaw-mcp-router/.mcp.json and validating server state after changes. Use when the user asks to add new MCP capabilities, troubleshoot missing tools, rotate credentials/env vars, or maintain MCP server inventory.
4
+ ---
5
+
6
+ # MCP Server Manager
7
+
8
+ Manage MCP server lifecycle through `openclaw-mcp-router` commands first; edit config files directly only when CLI paths cannot express the change.
9
+
10
+ ## Command Surface
11
+
12
+ Use these exact commands:
13
+
14
+ - `openclaw openclaw-mcp-router setup`
15
+ - `openclaw openclaw-mcp-router control`
16
+ - `openclaw openclaw-mcp-router add <name> <command-or-url> [args...] [--transport stdio|sse|http] [--env KEY=VALUE ...] [--timeout <ms>] [--file]`
17
+ - `openclaw openclaw-mcp-router list`
18
+ - `openclaw openclaw-mcp-router enable <name>`
19
+ - `openclaw openclaw-mcp-router disable <name>`
20
+ - `openclaw openclaw-mcp-router remove <name>`
21
+ - `openclaw openclaw-mcp-router reindex [--server <name>]`
22
+
23
+ ## Source of Truth and Precedence
24
+
25
+ Server definitions can come from two places:
26
+
27
+ 1. inline plugin config (`plugins.entries.openclaw-mcp-router.config.mcpServers` in `~/.openclaw/openclaw.json`)
28
+ 2. file-based config (`~/.openclaw/openclaw-mcp-router/.mcp.json` by default, or configured `mcpServersFile`)
29
+
30
+ Resolution rules:
31
+
32
+ - Both sources are merged.
33
+ - Name collisions are resolved with inline `mcpServers` winning over file-based entries.
34
+ - Disabled servers (`disabled: true`) are skipped during indexing.
35
+
36
+ ## Standard Workflow
37
+
38
+ 1. Inspect current state:
39
+ - `openclaw openclaw-mcp-router list`
40
+ 2. Apply change (add/enable/disable/remove).
41
+ 3. Reindex:
42
+ - Full: `openclaw openclaw-mcp-router reindex`
43
+ - Single server: `openclaw openclaw-mcp-router reindex --server <name>`
44
+ 4. Verify:
45
+ - `openclaw openclaw-mcp-router list`
46
+ - confirm status is `ok` and tool count is non-zero when expected.
47
+
48
+ ## Add Server Patterns
49
+
50
+ ### Stdio server (default transport)
51
+
52
+ ```bash
53
+ openclaw openclaw-mcp-router add filesystem npx -y @modelcontextprotocol/server-filesystem /path/to/root
54
+ ```
55
+
56
+ ### HTTP/SSE server
57
+
58
+ ```bash
59
+ openclaw openclaw-mcp-router add notion https://mcp.example.com --transport http
60
+ ```
61
+
62
+ ### Store in `.mcp.json` instead of `openclaw.json`
63
+
64
+ ```bash
65
+ openclaw openclaw-mcp-router add github npx -y @modelcontextprotocol/server-github --env GITHUB_TOKEN=${GITHUB_TOKEN} --file
66
+ ```
67
+
68
+ ## Operational Notes
69
+
70
+ - Always run `reindex` after add/remove and after most enable/disable changes.
71
+ - If a server fails to connect, retry with larger timeout (for slow startup servers):
72
+ - `--timeout 120000`
73
+ - For env updates, re-run `add` with the same name (entry is replaced), then reindex.
74
+ - Prefer `--file` when keeping credentials and frequently changing servers outside `openclaw.json`.
75
+
76
+ ## Troubleshooting Quick Checks
77
+
78
+ - No results in `mcp_search`: verify server is enabled and indexed.
79
+ - `list` shows `failed`: inspect endpoint/command/env and rerun reindex.
80
+ - Server missing: check whether it exists in inline config vs `.mcp.json` and whether an inline entry with same name is overriding it.
81
+
82
+
83
+ ## Setup Behavior (mcporter-aware)
84
+
85
+ - During `openclaw openclaw-mcp-router setup`, detect whether `mcporter` is installed and suggest `mcp_search` schema defaults accordingly.
86
+ - First-install guidance:
87
+ - no `mcporter`: keep params visible by default (`search.includeParametersDefault=true`)
88
+ - with `mcporter`: prefer compact cards by default (`search.includeParametersDefault=false`)
89
+ - This can still be overridden by users later in plugin config.
Binary file
package/src/config.ts CHANGED
@@ -41,13 +41,16 @@ export type IndexerConfig = {
41
41
  maxChunkChars: number;
42
42
  /** Overlap characters between adjacent chunks (default: 100) */
43
43
  overlapChars: number;
44
+ /** Generate mcporter CLI artifacts during reindex (default: false). */
45
+ generateCliArtifacts: boolean;
44
46
  };
45
47
 
48
+
46
49
  export type McpRouterConfig = {
47
50
  servers: McpServerConfig[];
48
51
  embedding: EmbeddingConfig;
49
52
  vectorDb: { path: string };
50
- search: { topK: number; minScore: number };
53
+ search: { topK: number; minScore: number; includeParametersDefault?: boolean };
51
54
  indexer: IndexerConfig;
52
55
  };
53
56
 
@@ -356,6 +359,7 @@ export function parseConfig(raw: unknown, opts?: ParseConfigOpts): McpRouterConf
356
359
  const search = {
357
360
  topK: typeof srchRaw.topK === "number" ? Math.min(20, Math.max(1, srchRaw.topK)) : 5,
358
361
  minScore: typeof srchRaw.minScore === "number" ? srchRaw.minScore : 0.3,
362
+ includeParametersDefault: typeof srchRaw.includeParametersDefault === "boolean" ? srchRaw.includeParametersDefault : undefined,
359
363
  };
360
364
 
361
365
  // โ”€โ”€ indexer defaults โ”€โ”€
@@ -367,6 +371,7 @@ export function parseConfig(raw: unknown, opts?: ParseConfigOpts): McpRouterConf
367
371
  maxRetryDelay: typeof idxRaw.maxRetryDelay === "number" ? idxRaw.maxRetryDelay : 30_000,
368
372
  maxChunkChars: typeof idxRaw.maxChunkChars === "number" ? Math.max(0, idxRaw.maxChunkChars) : 500,
369
373
  overlapChars: typeof idxRaw.overlapChars === "number" ? Math.max(0, idxRaw.overlapChars) : 100,
374
+ generateCliArtifacts: typeof idxRaw.generateCliArtifacts === "boolean" ? idxRaw.generateCliArtifacts : false,
370
375
  };
371
376
 
372
377
  return { servers, embedding, vectorDb, search, indexer };
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs";
2
+ import { spawnSync } from "node:child_process";
2
3
  import path from "node:path";
3
4
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
4
5
  import { parseConfig } from "./config.js";
@@ -10,6 +11,16 @@ import { createMcpCallTool } from "./tools/mcp-call-tool.js";
10
11
  import { createMcpSearchTool } from "./tools/mcp-search-tool.js";
11
12
  import { McpToolVectorStore } from "./vector-store.js";
12
13
 
14
+
15
+ function detectMcporterInstalled(): boolean {
16
+ try {
17
+ const r = spawnSync("mcporter", ["--version"], { stdio: "ignore" });
18
+ return r.status === 0;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
13
24
  const mcpRouterPlugin = {
14
25
  id: EXTENSION_ID,
15
26
  name: "OpenClaw MCP Router",
@@ -179,12 +190,14 @@ const mcpRouterPlugin = {
179
190
  }
180
191
 
181
192
  // Register tools as optional so the agent only sees them when alsoAllow is set
193
+ const hasMcporter = detectMcporterInstalled();
194
+
182
195
  api.registerTool(
183
- createMcpSearchTool({ store, embeddings, cfg: cfg.search }),
196
+ createMcpSearchTool({ store, embeddings, cfg: cfg.search, hasMcporter }) as never,
184
197
  { optional: true },
185
198
  );
186
199
  api.registerTool(
187
- createMcpCallTool({ registry, logger: api.logger }),
200
+ createMcpCallTool({ registry, logger: api.logger }) as never,
188
201
  { optional: true },
189
202
  );
190
203
 
package/src/indexer.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
1
4
  import type { Embeddings } from "./embeddings.js";
2
5
  import type { McpRegistry } from "./mcp-registry.js";
3
6
  import { McpClient } from "./mcp-client.js";
@@ -48,6 +51,49 @@ export function abortableDelay(ms: number, signal?: AbortSignal): Promise<void>
48
51
  });
49
52
  }
50
53
 
54
+
55
+ async function generateCliForServer(params: {
56
+ serverCfg: McpServerConfig;
57
+ cfg: McpRouterConfig;
58
+ logger: IndexerLogger;
59
+ }): Promise<void> {
60
+ const { serverCfg, cfg, logger } = params;
61
+ // Best-effort CLI artifact generation inspired by mcporter.
62
+ // Stores generated artifacts alongside router state.
63
+ const outDir = path.join(path.dirname(cfg.vectorDb.path), "generated-clis");
64
+ const outFile = path.join(outDir, `${serverCfg.name}.ts`);
65
+ fs.mkdirSync(outDir, { recursive: true });
66
+
67
+ const args = ["-y", "mcporter", "generate-cli"];
68
+ if (serverCfg.transport === "stdio") {
69
+ if (!serverCfg.command) return;
70
+ const cmd = [serverCfg.command, ...(serverCfg.args ?? [])].join(" ");
71
+ args.push("--command", cmd);
72
+ } else if (serverCfg.url) {
73
+ args.push("--server", serverCfg.url);
74
+ } else {
75
+ return;
76
+ }
77
+
78
+ args.push("--name", serverCfg.name, "--output", outFile);
79
+
80
+ await new Promise<void>((resolve) => {
81
+ const child = spawn("npx", args, { stdio: ["ignore", "pipe", "pipe"], env: process.env });
82
+ let stderr = "";
83
+ child.stderr.on("data", (d) => (stderr += String(d)));
84
+ child.on("close", (code) => {
85
+ if (code === 0) {
86
+ logger.info(`${EXTENSION_ID}: generated CLI artifact for server "${serverCfg.name}" at ${outFile}`);
87
+ } else {
88
+ logger.warn(
89
+ `${EXTENSION_ID}: failed to generate CLI artifact for "${serverCfg.name}" (best-effort): ${stderr.trim() || `exit ${String(code)}`}`,
90
+ );
91
+ }
92
+ resolve();
93
+ });
94
+ });
95
+ }
96
+
51
97
  /**
52
98
  * Connect to all configured MCP servers in parallel, list their tools,
53
99
  * embed each tool description, and upsert into the vector store.
@@ -131,6 +177,17 @@ async function indexServer(params: {
131
177
  await client.connect({ signal, timeout: connectTimeout });
132
178
  const tools = await client.listTools();
133
179
 
180
+ // Clear all existing rows for this server before re-adding the fresh tool list.
181
+ // This prevents stale entries when tools are removed, renamed, or change between
182
+ // single-chunk and multi-chunk representations across reindexes.
183
+ await store.deleteServer(serverCfg.name);
184
+
185
+ // Optional: best-effort generation of per-server CLI wrapper via mcporter.
186
+ // Indexing continues even if generation fails.
187
+ if (cfg.indexer.generateCliArtifacts) {
188
+ await generateCliForServer({ serverCfg, cfg, logger });
189
+ }
190
+
134
191
  let indexed = 0;
135
192
  let failed = 0;
136
193
 
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
2
3
  import { parseConfig, type McpServerConfig, type McpTransportKind } from "../config.js";
3
4
  import {
@@ -10,6 +11,16 @@ import {
10
11
  } from "./config-writer.js";
11
12
  import { CMD_REINDEX, EXTENSION_ID } from "../constants.js";
12
13
 
14
+
15
+ function detectMcporterInstalled(): boolean {
16
+ try {
17
+ const r = spawnSync("mcporter", ["--version"], { stdio: "ignore" });
18
+ return r.status === 0;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
13
24
  function abortIfCancel(value: unknown): void {
14
25
  if (isCancel(value)) {
15
26
  cancel("Setup cancelled.");
@@ -167,7 +178,22 @@ export async function runSetupCommand(): Promise<void> {
167
178
  overlapChars = parseInt(rawOverlapChars as string, 10) || 100;
168
179
  }
169
180
 
170
- // Step 4: Write config
181
+ // Step 4: Search verbosity defaults
182
+ // First install assumption: no mcporter => include params by default.
183
+ const hasMcporter = detectMcporterInstalled();
184
+ const schemaPreference = await confirm({
185
+ message: hasMcporter
186
+ ? "mcporter detected. Use compact mcp_search output by default?"
187
+ : "mcporter not detected. Keep full params in mcp_search by default?",
188
+ initialValue: true,
189
+ });
190
+ abortIfCancel(schemaPreference);
191
+
192
+ const includeParametersDefault = hasMcporter
193
+ ? !Boolean(schemaPreference)
194
+ : Boolean(schemaPreference);
195
+
196
+ // Step 5: Write config
171
197
  // Build mcpServers dict (key = server name, value = entry without name field)
172
198
  const mcpServers: Record<string, unknown> = {};
173
199
  for (const srv of servers) {
@@ -181,7 +207,7 @@ export async function runSetupCommand(): Promise<void> {
181
207
  model: embeddingModel,
182
208
  url: ollamaUrl as string,
183
209
  },
184
- search: { topK, minScore },
210
+ search: { topK, minScore, includeParametersDefault },
185
211
  indexer: { maxChunkChars, overlapChars },
186
212
  };
187
213
 
@@ -540,6 +540,7 @@ describe("parseConfig", () => {
540
540
  maxRetryDelay: 30_000,
541
541
  maxChunkChars: 500,
542
542
  overlapChars: 100,
543
+ generateCliArtifacts: false,
543
544
  });
544
545
  });
545
546
 
@@ -561,6 +562,7 @@ describe("parseConfig", () => {
561
562
  maxRetryDelay: 15_000,
562
563
  maxChunkChars: 4000,
563
564
  overlapChars: 400,
565
+ generateCliArtifacts: false,
564
566
  });
565
567
  });
566
568
 
@@ -19,7 +19,7 @@ const mockCfg: McpRouterConfig = {
19
19
  embedding: { provider: "ollama", model: "embeddinggemma", baseUrl: "http://localhost:11434/v1" },
20
20
  vectorDb: { path: "/tmp/test-lancedb" },
21
21
  search: { topK: 5, minScore: 0.3 },
22
- indexer: { connectTimeout: 60_000, maxRetries: 3, initialRetryDelay: 2_000, maxRetryDelay: 30_000, maxChunkChars: 500, overlapChars: 100 },
22
+ indexer: { connectTimeout: 60_000, maxRetries: 3, initialRetryDelay: 2_000, maxRetryDelay: 30_000, maxChunkChars: 500, overlapChars: 100, generateCliArtifacts: false },
23
23
  };
24
24
 
25
25
  function makeStore() {
@@ -71,6 +71,7 @@ describe("runIndexer", () => {
71
71
 
72
72
  expect(result.indexed).toBe(2);
73
73
  expect(result.failed).toBe(0);
74
+ expect(store.deleteServer).toHaveBeenCalledWith("fs");
74
75
  expect(store.upsertTool).toHaveBeenCalledTimes(2);
75
76
  expect(registry.registerToolOwner).toHaveBeenCalledWith("read_file", "fs");
76
77
  expect(registry.registerToolOwner).toHaveBeenCalledWith("list_dir", "fs");
@@ -155,6 +156,8 @@ describe("runIndexer", () => {
155
156
 
156
157
  // 1 tool per server ร— 2 servers
157
158
  expect(result.indexed).toBe(2);
159
+ expect(store.deleteServer).toHaveBeenCalledWith("server1");
160
+ expect(store.deleteServer).toHaveBeenCalledWith("server2");
158
161
  });
159
162
 
160
163
  // โ”€โ”€ Retry behavior โ”€โ”€
@@ -396,6 +399,7 @@ describe("runIndexer", () => {
396
399
  });
397
400
 
398
401
  expect(result.indexed).toBe(1); // counts tools, not chunks
402
+ expect(store.deleteServer).toHaveBeenCalledWith("fs");
399
403
  expect(store.upsertTool).not.toHaveBeenCalled();
400
404
  expect(store.deleteToolChunks).toHaveBeenCalledWith("fs", "big_tool");
401
405
  expect(store.addToolEntries).toHaveBeenCalledTimes(1);
@@ -446,6 +450,7 @@ describe("runIndexer", () => {
446
450
  });
447
451
 
448
452
  expect(result.indexed).toBe(1);
453
+ expect(store.deleteServer).toHaveBeenCalledWith("fs");
449
454
  // Should use single upsertTool even for long description
450
455
  expect(store.upsertTool).toHaveBeenCalledTimes(1);
451
456
  expect(store.deleteToolChunks).not.toHaveBeenCalled();
@@ -23,6 +23,7 @@ function makeTool(storeResults = makeStore()) {
23
23
  store: storeResults as never,
24
24
  embeddings: makeEmbeddings() as never,
25
25
  cfg: { topK: 5, minScore: 0.3 },
26
+ hasMcporter: false,
26
27
  });
27
28
  }
28
29
 
@@ -43,6 +44,7 @@ describe(`${TOOL_MCP_SEARCH} tool`, () => {
43
44
  store: store as never,
44
45
  embeddings: makeEmbeddings() as never,
45
46
  cfg: { topK: 5, minScore: 0.3 },
47
+ hasMcporter: false,
46
48
  });
47
49
 
48
50
  const result = await tool.execute("id", { query: "list files" });
@@ -69,6 +71,7 @@ describe(`${TOOL_MCP_SEARCH} tool`, () => {
69
71
  store: store as never,
70
72
  embeddings: makeEmbeddings() as never,
71
73
  cfg: { topK: 5, minScore: 0.3 },
74
+ hasMcporter: false,
72
75
  });
73
76
 
74
77
  const result = await tool.execute("id", { query: "read file" });
@@ -84,6 +87,7 @@ describe(`${TOOL_MCP_SEARCH} tool`, () => {
84
87
  store: store as never,
85
88
  embeddings: makeEmbeddings() as never,
86
89
  cfg: { topK: 5, minScore: 0.3 },
90
+ hasMcporter: false,
87
91
  });
88
92
 
89
93
  await tool.execute("id", { query: "test", limit: 999 });
@@ -115,9 +119,10 @@ describe(`${TOOL_MCP_SEARCH} tool`, () => {
115
119
  store: store as never,
116
120
  embeddings: makeEmbeddings() as never,
117
121
  cfg: { topK: 5, minScore: 0.3 },
122
+ hasMcporter: false,
118
123
  });
119
124
 
120
- const result = await tool.execute("id", { query: "test" });
125
+ const result = await tool.execute("id", { query: "test", include_schema: true });
121
126
  expect((result.content[0] as { text: string }).text).toContain("truncated");
122
127
  });
123
128
 
@@ -127,6 +132,7 @@ describe(`${TOOL_MCP_SEARCH} tool`, () => {
127
132
  store: store as never,
128
133
  embeddings: makeEmbeddings() as never,
129
134
  cfg: { topK: 5, minScore: 0.3 },
135
+ hasMcporter: false,
130
136
  });
131
137
 
132
138
  await tool.execute("id", { query: "test", limit: 10 });
@@ -140,6 +146,7 @@ describe(`${TOOL_MCP_SEARCH} tool`, () => {
140
146
  store: store as never,
141
147
  embeddings: makeEmbeddings() as never,
142
148
  cfg: { topK: 5, minScore: 0.3 },
149
+ hasMcporter: false,
143
150
  });
144
151
 
145
152
  await tool.execute("id", { query: "test", limit: 999 });
@@ -188,6 +195,7 @@ describe(`${TOOL_MCP_SEARCH} tool`, () => {
188
195
  store: store as never,
189
196
  embeddings: makeEmbeddings() as never,
190
197
  cfg: { topK: 5, minScore: 0.3 },
198
+ hasMcporter: false,
191
199
  });
192
200
 
193
201
  const result = await tool.execute("id", { query: "read file" });
@@ -209,6 +217,7 @@ describe(`${TOOL_MCP_SEARCH} tool`, () => {
209
217
  store: makeStore() as never,
210
218
  embeddings: embeddings as never,
211
219
  cfg: { topK: 5, minScore: 0.3 },
220
+ hasMcporter: false,
212
221
  });
213
222
 
214
223
  const result = await tool.execute("id", { query: "files" });
@@ -6,7 +6,8 @@ import type { McpToolVectorStore } from "../vector-store.js";
6
6
  type SearchDeps = {
7
7
  store: McpToolVectorStore;
8
8
  embeddings: Embeddings;
9
- cfg: { topK: number; minScore: number };
9
+ cfg: { topK: number; minScore: number; includeParametersDefault?: boolean };
10
+ hasMcporter: boolean;
10
11
  };
11
12
 
12
13
  /** Extract a string param tolerating both camelCase and snake_case keys. */
@@ -15,9 +16,32 @@ function readStringParam(params: Record<string, unknown>, key: string): string |
15
16
  return typeof val === "string" ? val : undefined;
16
17
  }
17
18
 
19
+ function readBoolParam(params: Record<string, unknown>, key: string): boolean | undefined {
20
+ const val = params[key] ?? params[key.replace(/([A-Z])/g, "_$1").toLowerCase()];
21
+ return typeof val === "boolean" ? val : undefined;
22
+ }
23
+
24
+ function buildSignature(toolName: string, paramsJson: string): string {
25
+ try {
26
+ const schema = JSON.parse(paramsJson) as {
27
+ properties?: Record<string, { type?: string }>;
28
+ required?: string[];
29
+ };
30
+ const props = schema.properties ?? {};
31
+ const required = new Set(schema.required ?? []);
32
+ const parts = Object.entries(props).map(([name, def]) => {
33
+ const type = def?.type ?? "unknown";
34
+ return required.has(name) ? `${name}: ${type}` : `${name}?: ${type}`;
35
+ });
36
+ return `${toolName}(${parts.join(", ")})`;
37
+ } catch {
38
+ return `${toolName}(...)`;
39
+ }
40
+ }
41
+
18
42
  /**
19
43
  * mcp_search โ€” semantic search over indexed MCP tool definitions.
20
- * Returns formatted tool cards with name, description, and parameter schema.
44
+ * Returns compact cards by default (signature + CLI hint). Full JSON schema is optional.
21
45
  */
22
46
  export function createMcpSearchTool(deps: SearchDeps) {
23
47
  return {
@@ -25,7 +49,7 @@ export function createMcpSearchTool(deps: SearchDeps) {
25
49
  label: "MCP Search",
26
50
  description:
27
51
  "Search for MCP tools by describing what you want to do. " +
28
- "Returns matching tool definitions with their parameter schemas. " +
52
+ "Returns matching tool definitions with compact signatures by default (schema optional). " +
29
53
  `Use this before ${TOOL_MCP_CALL} to find the right tool name.`,
30
54
  parameters: Type.Object({
31
55
  query: Type.String({
@@ -36,6 +60,11 @@ export function createMcpSearchTool(deps: SearchDeps) {
36
60
  description: "Max tools to return (default 5, max 20).",
37
61
  }),
38
62
  ),
63
+ include_schema: Type.Optional(
64
+ Type.Boolean({
65
+ description: "Include full JSON parameter schema in results. Default is auto (enabled when mcporter is unavailable).",
66
+ }),
67
+ ),
39
68
  }),
40
69
 
41
70
  async execute(_toolCallId: string, params: Record<string, unknown>) {
@@ -49,6 +78,10 @@ export function createMcpSearchTool(deps: SearchDeps) {
49
78
 
50
79
  const rawLimit = typeof params.limit === "number" ? params.limit : deps.cfg.topK;
51
80
  const limit = Math.max(1, Math.min(20, rawLimit));
81
+ const includeSchema =
82
+ readBoolParam(params, "include_schema") ??
83
+ deps.cfg.includeParametersDefault ??
84
+ !deps.hasMcporter;
52
85
 
53
86
  let vector: number[];
54
87
  try {
@@ -101,25 +134,37 @@ export function createMcpSearchTool(deps: SearchDeps) {
101
134
  const cards = results
102
135
  .map((r, i) => {
103
136
  const scoreStr = `${(r.score * 100).toFixed(0)}%`;
104
- // Truncate large parameter schemas to keep context size bounded
105
- const paramsStr =
106
- r.entry.parameters_json.length > 2000
107
- ? r.entry.parameters_json.slice(0, 2000) + "\n... (truncated)"
108
- : r.entry.parameters_json;
137
+ const signature = buildSignature(r.entry.tool_name, r.entry.parameters_json);
138
+ const cliHint = `mcporter call ${r.entry.server_name}.${r.entry.tool_name} '{...}'`;
139
+ const fallbackHint = `${TOOL_MCP_CALL}(\"${r.entry.tool_name}\", '{...}')`;
140
+
141
+ const schemaBlock = includeSchema
142
+ ? `\n**Parameters (JSON Schema):**\n\`\`\`json\n${
143
+ r.entry.parameters_json.length > 2000
144
+ ? r.entry.parameters_json.slice(0, 2000) + "\n... (truncated)"
145
+ : r.entry.parameters_json
146
+ }\n\`\`\``
147
+ : "";
109
148
 
110
149
  return (
111
150
  `### ${i + 1}. ${r.entry.tool_name} (server: ${r.entry.server_name}, score: ${scoreStr})\n` +
112
151
  `**Description:** ${r.entry.description}\n` +
113
- `**Parameters:**\n\`\`\`json\n${paramsStr}\n\`\`\``
152
+ `**Signature:** \`${signature}\`\n` +
153
+ `**Preferred (CLI):** \`${cliHint}\`\n` +
154
+ `**Fallback (JSON):** \`${fallbackHint}\`` +
155
+ schemaBlock
114
156
  );
115
157
  })
116
158
  .join("\n\n");
117
159
 
118
- const text = `Found ${results.length} matching tool(s):\n\n${cards}`;
160
+ const text =
161
+ `Found ${results.length} matching tool(s). ` +
162
+ `${includeSchema ? "Including full schema." : "Using compact mode (set include_schema=true for full JSON schema; schema auto-enables when mcporter is unavailable)."}\n\n` +
163
+ cards;
119
164
 
120
165
  return {
121
166
  content: [{ type: "text", text }],
122
- details: { count: results.length },
167
+ details: { count: results.length, includeSchema },
123
168
  };
124
169
  },
125
170
  };
package/tsconfig.json CHANGED
@@ -3,7 +3,8 @@
3
3
  "target": "ESNext",
4
4
  "module": "ESNext",
5
5
  "moduleResolution": "bundler",
6
- "strict": true
6
+ "strict": true,
7
+ "skipLibCheck": true
7
8
  },
8
9
  "include": ["src/**/*"]
9
10
  }