muonroi-cli 1.4.1 → 1.6.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.
Files changed (194) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +122 -122
  3. package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
  4. package/dist/src/agent-harness/__tests__/mock-model.spec.js +48 -1
  5. package/dist/src/agent-harness/mock-model.d.ts +11 -0
  6. package/dist/src/agent-harness/mock-model.js +21 -0
  7. package/dist/src/cli/cost-forensics.js +12 -12
  8. package/dist/src/council/__tests__/clarification-prompt.test.js +51 -0
  9. package/dist/src/council/__tests__/clarifier-ready-gate.test.js +32 -0
  10. package/dist/src/council/__tests__/decisions-lock.test.js +17 -1
  11. package/dist/src/council/__tests__/oauth-reachable.test.d.ts +1 -0
  12. package/dist/src/council/__tests__/oauth-reachable.test.js +31 -0
  13. package/dist/src/council/__tests__/parse-outcome-fallback.test.js +11 -0
  14. package/dist/src/council/clarifier.js +9 -1
  15. package/dist/src/council/debate.js +5 -1
  16. package/dist/src/council/decisions-lock.js +3 -3
  17. package/dist/src/council/index.js +12 -5
  18. package/dist/src/council/leader.d.ts +0 -17
  19. package/dist/src/council/leader.js +22 -15
  20. package/dist/src/council/planner.js +1 -1
  21. package/dist/src/council/prompts.js +63 -57
  22. package/dist/src/council/types.d.ts +7 -0
  23. package/dist/src/ee/__tests__/ee-onboarding.test.d.ts +1 -0
  24. package/dist/src/ee/__tests__/ee-onboarding.test.js +32 -0
  25. package/dist/src/ee/artifact-cache.d.ts +56 -0
  26. package/dist/src/ee/artifact-cache.js +155 -0
  27. package/dist/src/ee/artifact-cache.test.d.ts +1 -0
  28. package/dist/src/ee/artifact-cache.test.js +69 -0
  29. package/dist/src/ee/auth.d.ts +9 -0
  30. package/dist/src/ee/auth.js +19 -0
  31. package/dist/src/ee/ee-onboarding.d.ts +5 -0
  32. package/dist/src/ee/ee-onboarding.js +76 -0
  33. package/dist/src/ee/search.js +7 -5
  34. package/dist/src/ee/search.test.d.ts +1 -0
  35. package/dist/src/ee/search.test.js +23 -0
  36. package/dist/src/generated/version.d.ts +1 -1
  37. package/dist/src/generated/version.js +1 -1
  38. package/dist/src/headless/output.js +6 -4
  39. package/dist/src/headless/output.test.js +4 -3
  40. package/dist/src/index.js +20 -1
  41. package/dist/src/mcp/__tests__/auto-setup.test.js +74 -0
  42. package/dist/src/mcp/__tests__/client-pool.spec.d.ts +1 -0
  43. package/dist/src/mcp/__tests__/client-pool.spec.js +98 -0
  44. package/dist/src/mcp/__tests__/parallel-build.spec.d.ts +1 -0
  45. package/dist/src/mcp/__tests__/parallel-build.spec.js +67 -0
  46. package/dist/src/mcp/__tests__/smart-filter.test.js +56 -0
  47. package/dist/src/mcp/auto-setup.js +56 -2
  48. package/dist/src/mcp/client-pool.d.ts +46 -0
  49. package/dist/src/mcp/client-pool.js +212 -0
  50. package/dist/src/mcp/oauth-callback.js +2 -2
  51. package/dist/src/mcp/parse-headers.test.js +14 -14
  52. package/dist/src/mcp/runtime.d.ts +28 -0
  53. package/dist/src/mcp/runtime.js +117 -51
  54. package/dist/src/mcp/self-verify-runner.d.ts +14 -0
  55. package/dist/src/mcp/self-verify-runner.js +38 -0
  56. package/dist/src/mcp/setup-guide-text.d.ts +9 -0
  57. package/dist/src/mcp/setup-guide-text.js +84 -0
  58. package/dist/src/mcp/smart-filter.js +49 -0
  59. package/dist/src/mcp/smoke.test.js +43 -43
  60. package/dist/src/mcp/tools-server.d.ts +7 -0
  61. package/dist/src/mcp/tools-server.js +19 -22
  62. package/dist/src/models/catalog.json +349 -349
  63. package/dist/src/ops/__tests__/doctor-ee-health.test.js +21 -0
  64. package/dist/src/ops/doctor.d.ts +3 -2
  65. package/dist/src/ops/doctor.js +47 -11
  66. package/dist/src/ops/doctor.test.js +4 -3
  67. package/dist/src/orchestrator/__tests__/mcp-capability-block.test.d.ts +1 -0
  68. package/dist/src/orchestrator/__tests__/mcp-capability-block.test.js +39 -0
  69. package/dist/src/orchestrator/__tests__/project-stack.test.d.ts +1 -0
  70. package/dist/src/orchestrator/__tests__/project-stack.test.js +65 -0
  71. package/dist/src/orchestrator/batch-turn-runner.js +7 -11
  72. package/dist/src/orchestrator/compaction.d.ts +2 -0
  73. package/dist/src/orchestrator/compaction.js +14 -1
  74. package/dist/src/orchestrator/compaction.test.js +25 -1
  75. package/dist/src/orchestrator/message-processor.js +72 -32
  76. package/dist/src/orchestrator/orchestrator.js +26 -0
  77. package/dist/src/orchestrator/prompts.d.ts +51 -0
  78. package/dist/src/orchestrator/prompts.js +257 -134
  79. package/dist/src/orchestrator/scope-ceiling.js +6 -1
  80. package/dist/src/orchestrator/scope-reminder.d.ts +12 -0
  81. package/dist/src/orchestrator/scope-reminder.js +16 -0
  82. package/dist/src/orchestrator/scope-reminder.test.js +22 -1
  83. package/dist/src/orchestrator/stream-runner.js +23 -15
  84. package/dist/src/orchestrator/subagent-compactor.d.ts +14 -5
  85. package/dist/src/orchestrator/subagent-compactor.js +30 -8
  86. package/dist/src/orchestrator/subagent-compactor.spec.js +18 -0
  87. package/dist/src/orchestrator/text-tool-call-detector.test.js +13 -13
  88. package/dist/src/pil/__tests__/clarity-gate.test.js +24 -215
  89. package/dist/src/pil/__tests__/config.test.js +1 -17
  90. package/dist/src/pil/__tests__/discovery.test.js +144 -11
  91. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +7 -2
  92. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  93. package/dist/src/pil/__tests__/layer16-clarity.test.js +32 -116
  94. package/dist/src/pil/__tests__/layer4-gsd.test.js +37 -0
  95. package/dist/src/pil/__tests__/layer6-output.test.js +158 -18
  96. package/dist/src/pil/__tests__/llm-classify.test.js +49 -2
  97. package/dist/src/pil/__tests__/surface-compaction-artifacts.test.d.ts +1 -0
  98. package/dist/src/pil/__tests__/surface-compaction-artifacts.test.js +112 -0
  99. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  100. package/dist/src/pil/agent-operating-contract.js +2 -0
  101. package/dist/src/pil/agent-operating-contract.test.js +7 -2
  102. package/dist/src/pil/cheap-model-playbook.js +35 -35
  103. package/dist/src/pil/cheap-model-workbooks.js +16 -13
  104. package/dist/src/pil/clarity-gate.d.ts +21 -19
  105. package/dist/src/pil/clarity-gate.js +26 -153
  106. package/dist/src/pil/config.d.ts +9 -1
  107. package/dist/src/pil/config.js +15 -4
  108. package/dist/src/pil/discovery.js +211 -136
  109. package/dist/src/pil/layer1-intent.d.ts +12 -0
  110. package/dist/src/pil/layer1-intent.js +283 -38
  111. package/dist/src/pil/layer1-intent.test.js +210 -4
  112. package/dist/src/pil/layer16-clarity.d.ts +25 -11
  113. package/dist/src/pil/layer16-clarity.js +19 -306
  114. package/dist/src/pil/layer3-ee-injection.d.ts +19 -0
  115. package/dist/src/pil/layer3-ee-injection.js +96 -4
  116. package/dist/src/pil/layer4-gsd.js +18 -6
  117. package/dist/src/pil/layer6-output.d.ts +2 -0
  118. package/dist/src/pil/layer6-output.js +151 -25
  119. package/dist/src/pil/llm-classify.d.ts +26 -0
  120. package/dist/src/pil/llm-classify.js +34 -5
  121. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  122. package/dist/src/pil/native-capabilities-workbook.js +82 -76
  123. package/dist/src/pil/pipeline.js +15 -9
  124. package/dist/src/pil/schema.d.ts +8 -0
  125. package/dist/src/pil/schema.js +12 -1
  126. package/dist/src/pil/task-tier-map.js +4 -0
  127. package/dist/src/pil/types.d.ts +11 -1
  128. package/dist/src/product-loop/done-gate.js +3 -3
  129. package/dist/src/product-loop/loop-driver.js +18 -18
  130. package/dist/src/product-loop/progress-snapshot.js +4 -4
  131. package/dist/src/providers/auth/gemini-oauth.js +6 -15
  132. package/dist/src/providers/auth/grok-oauth.js +6 -15
  133. package/dist/src/providers/auth/openai-oauth.js +6 -15
  134. package/dist/src/providers/mcp-vision-bridge.js +48 -48
  135. package/dist/src/reporter/index.js +1 -1
  136. package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
  137. package/dist/src/scaffold/bb-quality-gate.js +5 -5
  138. package/dist/src/scaffold/continuation-prompt.js +60 -60
  139. package/dist/src/scaffold/init-new.js +453 -453
  140. package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
  141. package/dist/src/self-qa/agentic-loop.js +24 -19
  142. package/dist/src/self-qa/spec-emitter.js +26 -23
  143. package/dist/src/storage/__tests__/migrations.test.js +2 -2
  144. package/dist/src/storage/interaction-log.js +5 -5
  145. package/dist/src/storage/migrations.js +122 -122
  146. package/dist/src/storage/sessions.js +42 -42
  147. package/dist/src/storage/transcript.js +91 -84
  148. package/dist/src/storage/usage.js +14 -14
  149. package/dist/src/storage/workspaces.js +12 -12
  150. package/dist/src/tools/__tests__/native-tools.test.d.ts +1 -0
  151. package/dist/src/tools/__tests__/native-tools.test.js +53 -0
  152. package/dist/src/tools/git-safety.d.ts +61 -0
  153. package/dist/src/tools/git-safety.js +141 -0
  154. package/dist/src/tools/git-safety.test.d.ts +1 -0
  155. package/dist/src/tools/git-safety.test.js +111 -0
  156. package/dist/src/tools/native-tools.d.ts +31 -0
  157. package/dist/src/tools/native-tools.js +273 -0
  158. package/dist/src/tools/registry-ee-query.test.js +18 -1
  159. package/dist/src/tools/registry-git-safety.test.d.ts +7 -0
  160. package/dist/src/tools/registry-git-safety.test.js +92 -0
  161. package/dist/src/tools/registry.js +52 -6
  162. package/dist/src/ui/__tests__/markdown-render.test.d.ts +1 -0
  163. package/dist/src/ui/__tests__/markdown-render.test.js +48 -0
  164. package/dist/src/ui/app.js +0 -0
  165. package/dist/src/ui/components/message-view.js +4 -1
  166. package/dist/src/ui/components/structured-response-view.js +7 -3
  167. package/dist/src/ui/components/tool-group.js +7 -1
  168. package/dist/src/ui/markdown-render.d.ts +41 -0
  169. package/dist/src/ui/markdown-render.js +223 -0
  170. package/dist/src/ui/markdown.d.ts +10 -0
  171. package/dist/src/ui/markdown.js +12 -35
  172. package/dist/src/ui/slash/council-inspect.js +4 -4
  173. package/dist/src/ui/slash/export.js +4 -4
  174. package/dist/src/ui/utils/text.d.ts +8 -0
  175. package/dist/src/ui/utils/text.js +16 -0
  176. package/dist/src/ui/utils/text.test.d.ts +1 -0
  177. package/dist/src/ui/utils/text.test.js +23 -0
  178. package/dist/src/usage/ledger.js +48 -15
  179. package/dist/src/utils/__tests__/footprint-gitignore.test.d.ts +1 -0
  180. package/dist/src/utils/__tests__/footprint-gitignore.test.js +50 -0
  181. package/dist/src/utils/clipboard-image.js +23 -23
  182. package/dist/src/utils/open-url.d.ts +56 -0
  183. package/dist/src/utils/open-url.js +58 -0
  184. package/dist/src/utils/open-url.test.d.ts +1 -0
  185. package/dist/src/utils/open-url.test.js +86 -0
  186. package/dist/src/utils/settings.d.ts +12 -0
  187. package/dist/src/utils/settings.js +48 -0
  188. package/dist/src/utils/side-question.js +2 -2
  189. package/dist/src/utils/skills.js +3 -3
  190. package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
  191. package/dist/src/verify/environment.js +2 -1
  192. package/package.json +1 -1
  193. package/dist/src/pil/layer16-clarity.test.js +0 -31
  194. /package/dist/src/{pil/layer16-clarity.test.d.ts → council/__tests__/clarification-prompt.test.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ import { type MCPClient } from "@ai-sdk/mcp";
1
2
  import type { ToolSet } from "ai";
2
3
  import type { McpServerConfig } from "../utils/settings.js";
3
4
  export interface McpToolBundle {
@@ -8,4 +9,31 @@ export interface McpToolBundle {
8
9
  export interface McpBuildOptions {
9
10
  onOAuthRequired?: (serverId: string, url: URL) => void;
10
11
  }
12
+ /**
13
+ * Total wall-clock budget for building the MCP tool set. Servers connect in
14
+ * PARALLEL and whatever has connected by the deadline is returned; slower
15
+ * servers are reported in `.errors` (and closed if they connect late) instead
16
+ * of sinking the whole bundle. Default 2500ms; override with
17
+ * MUONROI_MCP_BUILD_DEADLINE_MS (500–20000).
18
+ *
19
+ * Phase 1c — the OLD design built servers SEQUENTIALLY under an outer race
20
+ * (message-processor) that discarded EVERYTHING on timeout, so one slow `npx`
21
+ * stdio spawn starved a fast HTTP server and left the agent blind to MCP tools
22
+ * that were actually reachable (live: muonroi-docs ~300ms dropped behind slow
23
+ * npx servers, session f6f7881a5fae). Parallel + partial-at-deadline fixes it.
24
+ */
25
+ export declare function getMcpBuildDeadlineMs(): number;
26
+ export interface ConnectedServer {
27
+ tools: ToolSet;
28
+ client: MCPClient;
29
+ /** OAuth provider teardown, when one was created for this server. */
30
+ cleanup?: () => void;
31
+ }
32
+ /**
33
+ * Connect ONE server and build its prefixed, output-capped tool set. Throws on
34
+ * any failure; the caller owns lifecycle of the returned client/cleanup.
35
+ * Exported so the cross-turn client pool (client-pool.ts) can reuse it as its
36
+ * connect primitive.
37
+ */
38
+ export declare function connectOneServer(rawServer: McpServerConfig, opts?: McpBuildOptions): Promise<ConnectedServer>;
11
39
  export declare function buildMcpToolSet(servers: McpServerConfig[], opts?: McpBuildOptions): Promise<McpToolBundle>;
@@ -82,70 +82,136 @@ function toTransport(server, authProvider) {
82
82
  ...(authProvider ? { authProvider: authProvider } : {}),
83
83
  };
84
84
  }
85
+ /**
86
+ * Total wall-clock budget for building the MCP tool set. Servers connect in
87
+ * PARALLEL and whatever has connected by the deadline is returned; slower
88
+ * servers are reported in `.errors` (and closed if they connect late) instead
89
+ * of sinking the whole bundle. Default 2500ms; override with
90
+ * MUONROI_MCP_BUILD_DEADLINE_MS (500–20000).
91
+ *
92
+ * Phase 1c — the OLD design built servers SEQUENTIALLY under an outer race
93
+ * (message-processor) that discarded EVERYTHING on timeout, so one slow `npx`
94
+ * stdio spawn starved a fast HTTP server and left the agent blind to MCP tools
95
+ * that were actually reachable (live: muonroi-docs ~300ms dropped behind slow
96
+ * npx servers, session f6f7881a5fae). Parallel + partial-at-deadline fixes it.
97
+ */
98
+ export function getMcpBuildDeadlineMs() {
99
+ const v = Number(process.env.MUONROI_MCP_BUILD_DEADLINE_MS);
100
+ if (Number.isFinite(v) && v >= 500 && v <= 20_000)
101
+ return v;
102
+ return 2500;
103
+ }
104
+ /**
105
+ * Connect ONE server and build its prefixed, output-capped tool set. Throws on
106
+ * any failure; the caller owns lifecycle of the returned client/cleanup.
107
+ * Exported so the cross-turn client pool (client-pool.ts) can reuse it as its
108
+ * connect primitive.
109
+ */
110
+ export async function connectOneServer(rawServer, opts) {
111
+ // Hydrate env vars from the OS keychain before spawning — e.g. inject
112
+ // TAVILY_API_KEY for the tavily MCP if stored via the research-onboarding wizard.
113
+ const server = await hydrateServerEnv(rawServer);
114
+ let authProvider;
115
+ let cleanup;
116
+ if (server.transport !== "stdio" && opts?.onOAuthRequired) {
117
+ const oauthResult = await createOAuthProviderWithCallback({
118
+ serverId: server.id,
119
+ onAuthorizationUrl: (url) => opts.onOAuthRequired(server.id, url),
120
+ });
121
+ authProvider = oauthResult.provider;
122
+ cleanup = oauthResult.close;
123
+ }
124
+ const client = await createMCPClient({
125
+ transport: toTransport(server, authProvider),
126
+ name: `muonroi-cli-${server.id}`,
127
+ version: "1.0.0",
128
+ });
129
+ const mcpTools = await client.tools();
130
+ const prefix = mcpToolPrefix(server);
131
+ const tools = {};
132
+ for (const [name, tool] of Object.entries(mcpTools)) {
133
+ // OpenAI/DeepSeek function-name regex: ^[a-zA-Z0-9_-]+$. MCP spec does not
134
+ // restrict server-side tool names, so we sanitize here. The tool's execute()
135
+ // closure still calls the MCP server with the original name.
136
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "_");
137
+ const prefixedName = `${prefix}__${safeName}`;
138
+ const stripped = stripMcpInputSchema(tool);
139
+ // Cap MCP tool output the same way built-in tools are capped so the raw
140
+ // server payload doesn't stream into context uncapped. See cap-tool-result.ts.
141
+ const baseExecute = stripped.execute;
142
+ tools[prefixedName] = {
143
+ ...stripped,
144
+ description: `[MCP ${server.label}] ${tool.description ?? name}`,
145
+ ...(typeof baseExecute === "function"
146
+ ? { execute: async (args, options) => capMcpToolResult(await baseExecute(args, options)) }
147
+ : {}),
148
+ };
149
+ }
150
+ return { tools, client, cleanup };
151
+ }
85
152
  export async function buildMcpToolSet(servers, opts) {
86
153
  const tools = {};
87
154
  const errors = [];
88
155
  const clients = [];
89
156
  const cleanups = [];
90
- for (const rawServer of servers) {
91
- if (!rawServer.enabled)
92
- continue;
157
+ const enabled = servers.filter((s) => s.enabled);
158
+ const slots = enabled.map((s) => ({ label: s.label, done: false }));
159
+ const attempts = enabled.map((rawServer, i) => {
93
160
  const validation = validateMcpServerConfig(rawServer);
94
161
  if (!validation.ok) {
95
- errors.push(`${rawServer.label}: ${validation.error}`);
96
- continue;
162
+ slots[i] = { label: rawServer.label, done: true, error: validation.error };
163
+ return Promise.resolve();
97
164
  }
98
- try {
99
- // Hydrate env vars from the OS keychain before spawning — e.g. inject
100
- // TAVILY_API_KEY for the tavily MCP if the user stored it via the
101
- // research-onboarding wizard.
102
- const server = await hydrateServerEnv(rawServer);
103
- let authProvider;
104
- if (server.transport !== "stdio" && opts?.onOAuthRequired) {
105
- const oauthResult = await createOAuthProviderWithCallback({
106
- serverId: server.id,
107
- onAuthorizationUrl: (url) => opts.onOAuthRequired(server.id, url),
108
- });
109
- authProvider = oauthResult.provider;
110
- cleanups.push(oauthResult.close);
165
+ return connectOneServer(rawServer, opts).then((result) => {
166
+ slots[i] = { label: rawServer.label, done: true, result };
167
+ }, (error) => {
168
+ slots[i] = {
169
+ label: rawServer.label,
170
+ done: true,
171
+ error: error instanceof Error ? error.message : String(error),
172
+ };
173
+ });
174
+ });
175
+ const deadlineMs = getMcpBuildDeadlineMs();
176
+ let deadlineTimer;
177
+ const deadline = new Promise((resolve) => {
178
+ deadlineTimer = setTimeout(resolve, deadlineMs);
179
+ deadlineTimer.unref?.();
180
+ });
181
+ await Promise.race([Promise.allSettled(attempts), deadline]);
182
+ if (deadlineTimer)
183
+ clearTimeout(deadlineTimer);
184
+ for (let i = 0; i < slots.length; i++) {
185
+ const slot = slots[i];
186
+ if (slot.done) {
187
+ if (slot.error) {
188
+ errors.push(`${slot.label}: ${slot.error}`);
111
189
  }
112
- const client = await createMCPClient({
113
- transport: toTransport(server, authProvider),
114
- name: `muonroi-cli-${server.id}`,
115
- version: "1.0.0",
116
- });
117
- clients.push(client);
118
- const mcpTools = await client.tools();
119
- const prefix = mcpToolPrefix(server);
120
- for (const [name, tool] of Object.entries(mcpTools)) {
121
- // OpenAI/DeepSeek function-name regex: ^[a-zA-Z0-9_-]+$. MCP spec
122
- // does not restrict server-side tool names, so we sanitize here.
123
- // The tool's execute() closure still calls the MCP server with the
124
- // original name — we only rename what the LLM sees.
125
- const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "_");
126
- const prefixedName = `${prefix}__${safeName}`;
127
- const stripped = stripMcpInputSchema(tool);
128
- // Cap MCP tool output the same way built-in tools are capped
129
- // (`truncateOutput` / MAX_TOOL_OUTPUT_CHARS). Without this wrap the raw
130
- // server payload streams into the model context uncapped — a cost leak
131
- // that hits cheap models hardest. See cap-tool-result.ts.
132
- const baseExecute = stripped.execute;
133
- tools[prefixedName] = {
134
- ...stripped,
135
- description: `[MCP ${server.label}] ${tool.description ?? name}`,
136
- ...(typeof baseExecute === "function"
137
- ? {
138
- execute: async (args, options) => capMcpToolResult(await baseExecute(args, options)),
139
- }
140
- : {}),
141
- };
190
+ else if (slot.result) {
191
+ Object.assign(tools, slot.result.tools);
192
+ clients.push(slot.result.client);
193
+ if (slot.result.cleanup)
194
+ cleanups.push(slot.result.cleanup);
142
195
  }
143
196
  }
144
- catch (error) {
145
- const message = error instanceof Error ? error.message : String(error);
146
- errors.push(`${rawServer.label}: ${message}`);
197
+ else {
198
+ // Still connecting at the deadline: report it and close it if/when it
199
+ // eventually connects so the child process / socket doesn't leak.
200
+ errors.push(`${slot.label}: not ready within ${deadlineMs}ms (slow MCP server — excluded this turn)`);
201
+ void attempts[i]?.then(() => {
202
+ const late = slots[i]?.result;
203
+ if (late) {
204
+ late.cleanup?.();
205
+ void late.client.close().catch(() => { });
206
+ }
207
+ });
147
208
  }
148
209
  }
210
+ // Surface (not swallow) any server that didn't make it — never silently
211
+ // degrade to "builtins only" without a trace.
212
+ if (errors.length > 0) {
213
+ console.error(`[MCP] ${errors.length} server(s) unavailable this turn: ${errors.join(" | ")}`);
214
+ }
149
215
  return {
150
216
  tools,
151
217
  errors,
@@ -0,0 +1,14 @@
1
+ /**
2
+ * src/mcp/self-verify-runner.ts
3
+ *
4
+ * The default self-verify Runner (drives runSelfVerify / runAgenticLoop in
5
+ * process) plus a process-shared JobManager singleton. Shared by BOTH surfaces:
6
+ * the native in-CLI selfverify_* builtins (src/tools/native-tools.ts) and the
7
+ * muonroi-tools MCP server (src/mcp/tools-server.ts, for external agents) — so a
8
+ * run started on either surface is visible to both, and there is one job space.
9
+ */
10
+ import { JobManager, type Runner } from "./self-verify-jobs.js";
11
+ /** Default runner: drives the real self-verify functions in-process. */
12
+ export declare const defaultRunner: Runner;
13
+ /** Process-shared self-verify JobManager (created lazily on first use). */
14
+ export declare function getSelfVerifyJobManager(): JobManager;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * src/mcp/self-verify-runner.ts
3
+ *
4
+ * The default self-verify Runner (drives runSelfVerify / runAgenticLoop in
5
+ * process) plus a process-shared JobManager singleton. Shared by BOTH surfaces:
6
+ * the native in-CLI selfverify_* builtins (src/tools/native-tools.ts) and the
7
+ * muonroi-tools MCP server (src/mcp/tools-server.ts, for external agents) — so a
8
+ * run started on either surface is visible to both, and there is one job space.
9
+ */
10
+ import { JobManager } from "./self-verify-jobs.js";
11
+ /** Default runner: drives the real self-verify functions in-process. */
12
+ export const defaultRunner = {
13
+ async tier1(opts, log) {
14
+ // signal intentionally not forwarded: runSelfVerify/runAgenticLoop do not yet
15
+ // accept an AbortSignal. cancel() marks the job and discards the late result.
16
+ const { runSelfVerify } = await import("../self-qa/index.js");
17
+ return runSelfVerify({
18
+ baseRef: opts.since,
19
+ maxScenarios: opts.max,
20
+ emitSpecs: opts.emit,
21
+ specOutDir: opts.out,
22
+ log,
23
+ });
24
+ },
25
+ async agentic(opts, log) {
26
+ const { createLLMBrain, runAgenticLoop } = await import("../self-qa/agentic-loop.js");
27
+ const brain = await createLLMBrain({ modelId: opts.llm });
28
+ return runAgenticLoop({ goal: opts.goal, brain, maxTurns: opts.turns ?? 20, log });
29
+ },
30
+ };
31
+ let shared = null;
32
+ /** Process-shared self-verify JobManager (created lazily on first use). */
33
+ export function getSelfVerifyJobManager() {
34
+ if (!shared)
35
+ shared = new JobManager(defaultRunner);
36
+ return shared;
37
+ }
38
+ //# sourceMappingURL=self-verify-runner.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * src/mcp/setup-guide-text.ts
3
+ *
4
+ * Single source of the muonroi-cli setup guide, shared by BOTH surfaces that
5
+ * expose it: the native in-CLI `setup_guide` builtin (src/tools/native-tools.ts)
6
+ * and the muonroi-tools MCP server (src/mcp/tools-server.ts, for external agents).
7
+ * Keeping it here avoids duplicating ~70 lines across the two.
8
+ */
9
+ export declare const SETUP_GUIDE_TEXT = "# muonroi-cli Setup Guide\n\n## Install (zero runtime deps \u2014 recommended)\nLinux / macOS:\n curl -fsSL https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.sh | bash\n\nWindows PowerShell:\n irm https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.ps1 | iex\n\nBun (requires Bun >= 1.3):\n bun add -g muonroi-cli\n # (npm install -g is NOT supported \u2014 TUI engine uses Bun-only ESM features)\n\nThe installers fetch a pre-compiled single binary from GitHub Releases.\n\n## First run\n- Wizard appears automatically.\n- Lists supported providers (DeepSeek + SiliconFlow ready; others via BYOK).\n- Four credential options: paste key, Bitwarden sync (B in /providers), keys export/import (encrypted bundle), or skip for later.\n- Keys land in OS keychain (keytar). Settings written to ~/.muonroi-cli/user-settings.json.\n- Role routing (leader/implement/verify/research) is configured for you.\n\nAfter setup: run `muonroi-cli doctor` to validate.\n\n## Essential commands\n- Interactive TUI: `muonroi-cli` (or `node dist/index.js` after build)\n- Headless one-shot: `muonroi-cli --prompt \"your task\" --max-tool-rounds 8`\n- Health + MCP nudge: `muonroi-cli doctor`\n- Update: `muonroi-cli update` (or set \"autoUpdate\": true in user-settings)\n- Keys move between machines: `muonroi-cli keys export file.json` then import on target\n- Native tools MCP (for external agents): `muonroi-cli tools-mcp` (stdio)\n- Harness driver MCP: `muonroi-cli mcp-driver`\n\n## MCP integration (for Claude Desktop, Cursor, other agents)\nAdd to your MCP client config:\n\n{\n \"mcpServers\": {\n \"muonroi-tools\": {\n \"command\": \"bun\",\n \"args\": [\"run\", \"/absolute/path/to/muonroi-cli/src/index.ts\", \"tools-mcp\"]\n }\n }\n}\n\n(Use absolute path. After `bun run build`: \"node\", \"dist/index.js\", \"tools-mcp\")\n\nThe CLI's OWN inner agent exposes these as NATIVE in-process tools (no MCP self-spawn):\n- setup_guide (this document)\n- ee_query / ee_health / ee_feedback \u2014 Experience Engine semantic recall + compaction checkpoints + feedback for learning\n- usage_forensics <id-prefix> \u2014 per-session cost/token forensics (peak input, cache hits, anomalies)\n- lsp_query \u2014 goToDefinition, findReferences, hover, symbols, call hierarchy etc.\n- selfverify_* \u2014 Tier-1 heuristic + Tier-2 agentic self-QA harness runs (start/poll/result/cancel/list)\n\nFor BB/.NET template recipes and package docs, also connect an external \"muonroi-docs\" MCP server if available (provides docs_search + setup_guide for the templates).\n\n## Development\ngit clone https://github.com/muonroi/muonroi-cli.git\ncd muonroi-cli && bun install\n\nbun run dev # run from source (TUI)\nbun run typecheck # tsc --noEmit\nbun run test # vitest (unit + headless)\nbunx vitest -c vitest.harness.config.ts run tests/harness/ # TUI E2E (named-pipes on Win, fd3/4 on POSIX)\nbun run build # or build:binary for standalone exe\n\nSee AGENTS.md (quick ref + rules), CLAUDE.md (harness verification), README.md.\n\n## Verify\nmuonroi-cli doctor\n# Checks runtimes, catalog load, keychain, MCP servers enabled, council research MCP nudge, EE reachability, recent error rate.\n# Any \"warn\" entries tell you exactly what to enable (e.g. tavily for web research in council).\n\nFor BB-aware scaffolding (/ideal on a muonroi-building-block target): ensure dotnet SDK + the three Muonroi.*.Template packages are installed via NuGet; doctor surfaces missing feed/template cases.\n";
@@ -0,0 +1,84 @@
1
+ /**
2
+ * src/mcp/setup-guide-text.ts
3
+ *
4
+ * Single source of the muonroi-cli setup guide, shared by BOTH surfaces that
5
+ * expose it: the native in-CLI `setup_guide` builtin (src/tools/native-tools.ts)
6
+ * and the muonroi-tools MCP server (src/mcp/tools-server.ts, for external agents).
7
+ * Keeping it here avoids duplicating ~70 lines across the two.
8
+ */
9
+ export const SETUP_GUIDE_TEXT = `# muonroi-cli Setup Guide
10
+
11
+ ## Install (zero runtime deps — recommended)
12
+ Linux / macOS:
13
+ curl -fsSL https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.sh | bash
14
+
15
+ Windows PowerShell:
16
+ irm https://raw.githubusercontent.com/muonroi/muonroi-cli/master/install.ps1 | iex
17
+
18
+ Bun (requires Bun >= 1.3):
19
+ bun add -g muonroi-cli
20
+ # (npm install -g is NOT supported — TUI engine uses Bun-only ESM features)
21
+
22
+ The installers fetch a pre-compiled single binary from GitHub Releases.
23
+
24
+ ## First run
25
+ - Wizard appears automatically.
26
+ - Lists supported providers (DeepSeek + SiliconFlow ready; others via BYOK).
27
+ - Four credential options: paste key, Bitwarden sync (B in /providers), keys export/import (encrypted bundle), or skip for later.
28
+ - Keys land in OS keychain (keytar). Settings written to ~/.muonroi-cli/user-settings.json.
29
+ - Role routing (leader/implement/verify/research) is configured for you.
30
+
31
+ After setup: run \`muonroi-cli doctor\` to validate.
32
+
33
+ ## Essential commands
34
+ - Interactive TUI: \`muonroi-cli\` (or \`node dist/index.js\` after build)
35
+ - Headless one-shot: \`muonroi-cli --prompt "your task" --max-tool-rounds 8\`
36
+ - Health + MCP nudge: \`muonroi-cli doctor\`
37
+ - Update: \`muonroi-cli update\` (or set "autoUpdate": true in user-settings)
38
+ - Keys move between machines: \`muonroi-cli keys export file.json\` then import on target
39
+ - Native tools MCP (for external agents): \`muonroi-cli tools-mcp\` (stdio)
40
+ - Harness driver MCP: \`muonroi-cli mcp-driver\`
41
+
42
+ ## MCP integration (for Claude Desktop, Cursor, other agents)
43
+ Add to your MCP client config:
44
+
45
+ {
46
+ "mcpServers": {
47
+ "muonroi-tools": {
48
+ "command": "bun",
49
+ "args": ["run", "/absolute/path/to/muonroi-cli/src/index.ts", "tools-mcp"]
50
+ }
51
+ }
52
+ }
53
+
54
+ (Use absolute path. After \`bun run build\`: "node", "dist/index.js", "tools-mcp")
55
+
56
+ The CLI's OWN inner agent exposes these as NATIVE in-process tools (no MCP self-spawn):
57
+ - setup_guide (this document)
58
+ - ee_query / ee_health / ee_feedback — Experience Engine semantic recall + compaction checkpoints + feedback for learning
59
+ - usage_forensics <id-prefix> — per-session cost/token forensics (peak input, cache hits, anomalies)
60
+ - lsp_query — goToDefinition, findReferences, hover, symbols, call hierarchy etc.
61
+ - selfverify_* — Tier-1 heuristic + Tier-2 agentic self-QA harness runs (start/poll/result/cancel/list)
62
+
63
+ For BB/.NET template recipes and package docs, also connect an external "muonroi-docs" MCP server if available (provides docs_search + setup_guide for the templates).
64
+
65
+ ## Development
66
+ git clone https://github.com/muonroi/muonroi-cli.git
67
+ cd muonroi-cli && bun install
68
+
69
+ bun run dev # run from source (TUI)
70
+ bun run typecheck # tsc --noEmit
71
+ bun run test # vitest (unit + headless)
72
+ bunx vitest -c vitest.harness.config.ts run tests/harness/ # TUI E2E (named-pipes on Win, fd3/4 on POSIX)
73
+ bun run build # or build:binary for standalone exe
74
+
75
+ See AGENTS.md (quick ref + rules), CLAUDE.md (harness verification), README.md.
76
+
77
+ ## Verify
78
+ muonroi-cli doctor
79
+ # Checks runtimes, catalog load, keychain, MCP servers enabled, council research MCP nudge, EE reachability, recent error rate.
80
+ # Any "warn" entries tell you exactly what to enable (e.g. tavily for web research in council).
81
+
82
+ For BB-aware scaffolding (/ideal on a muonroi-building-block target): ensure dotnet SDK + the three Muonroi.*.Template packages are installed via NuGet; doctor surfaces missing feed/template cases.
83
+ `;
84
+ //# sourceMappingURL=setup-guide-text.js.map
@@ -47,6 +47,37 @@ function hasDocsSignal(message) {
47
47
  return (/https?:\/\/\S+/i.test(message) ||
48
48
  /\b(docs?|documentation|api|sdk|library|libraries|framework|package|npm|pip|cargo|crate|gem|maven|nuget|install|migrat\w*|changelog|release\s*notes?|reference|usage|fetch|download|http|web|google|search\s+(the\s+)?web|news|weather|headlines|look\s*up|online|internet)\b/i.test(message));
49
49
  }
50
+ /**
51
+ * Matches a question ABOUT the Muonroi ecosystem — where muonroi-docs (the
52
+ * authoritative ecosystem source: BB/.NET recipes, package docs, open-core
53
+ * boundary) is exactly what's needed, even though the message carries no generic
54
+ * docs/api keyword. Deliberately ecosystem-specific so it only ever KEEPS
55
+ * muonroi-docs (never other docs servers). EN + VI.
56
+ */
57
+ function hasEcosystemSignal(message) {
58
+ return /\bmuonroi\b|\becosystem\b|hệ\s*sinh\s*thái|he\s*sinh\s*thai|building[-\s]?block|\bbb\b|open[-\s]?core/i.test(message);
59
+ }
60
+ /**
61
+ * Explicit "use a tool / MCP tool" intent. The filter only sees server *ids*,
62
+ * not their tool lists (MCP tools are fetched lazily at build time), so when the
63
+ * user asks to call a specific tool by name we cannot tell which server owns it.
64
+ * Dropping any optional server then risks stripping the exact tool requested.
65
+ *
66
+ * Live miss (session f6f7881a5fae): "bạn thử call tool setup_guide ... ( call
67
+ * tool chứ không phải đọc code )" carried no docs keyword, so `muonroi-docs`
68
+ * (id matches /docs/) was dropped — the model had no `setup_guide` tool and
69
+ * resorted to driving the server by hand over bash JSON-RPC, fabricating output.
70
+ *
71
+ * When this fires we keep ALL optional servers for the turn. Over-keeping costs
72
+ * tokens but never removes capability — this module's documented safe direction.
73
+ * EN + VI: "call/use/invoke/run the X tool", "tool call", "gọi/dùng/chạy tool".
74
+ */
75
+ function hasExplicitToolIntent(message) {
76
+ return (/\b(?:call|use|invoke|run|exercise|trigger|try)\s+(?:the\s+)?(?:mcp\s+)?(?:[a-z0-9_.-]+\s+)?tool(?:s)?\b/i.test(message) ||
77
+ /\btool[\s_-]?call\b/i.test(message) ||
78
+ /\bmcp\b[^\n]*\btool/i.test(message) ||
79
+ /\b(?:gọi|dùng|chạy|thử)\s+(?:tool|mcp)\b/i.test(message));
80
+ }
50
81
  /**
51
82
  * Filesystem-MCP tool names that 1:1 duplicate a first-class BUILTIN file tool.
52
83
  * The builtin `read_file`/`write_file`/`edit_file` are strictly better (read-
@@ -102,9 +133,27 @@ export function dropRedundantFsMcpTools(mcpTools, builtinToolNames) {
102
133
  export function filterMcpServersByMessage(servers, userMessage, opts = {}) {
103
134
  if (opts.disabled)
104
135
  return servers;
136
+ // Explicit "call the X tool" intent → keep every server this turn. We can't map
137
+ // a named tool back to its server from config alone (tool lists load lazily),
138
+ // so dropping any optional server risks stripping the exact tool requested.
139
+ if (hasExplicitToolIntent(userMessage))
140
+ return servers;
105
141
  const browser = hasBrowserSignal(userMessage);
106
142
  const docs = hasDocsSignal(userMessage);
143
+ const ecosystem = hasEcosystemSignal(userMessage);
144
+ const lower = userMessage.toLowerCase();
107
145
  return servers.filter((s) => {
146
+ // A server named outright in the message ("check the muonroi-docs MCP") is
147
+ // always relevant — never let a category skip override an explicit mention.
148
+ if (s.id && lower.includes(s.id.toLowerCase()))
149
+ return true;
150
+ // muonroi-docs is the AUTHORITATIVE ecosystem source. A question about the
151
+ // Muonroi ecosystem ("hệ sinh thái muonroi", "building-block", "bb rule
152
+ // engine") matches no generic docs/api keyword, so SKIP_WHEN_NO_DOCS would
153
+ // wrongly drop it and the agent falls back to guessing from files (live
154
+ // session dbe408937a3d turn 1). Keep it whenever the turn is ecosystem-about.
155
+ if (ecosystem && /(^|[-_])docs([-_]|$)/.test(s.id) && /muonroi/i.test(s.id))
156
+ return true;
108
157
  if (!browser && SKIP_WHEN_NO_BROWSER.test(s.id))
109
158
  return false;
110
159
  if (!docs && SKIP_WHEN_NO_DOCS.test(s.id))
@@ -86,49 +86,49 @@ describe("MCP smoke test — buildMcpToolSet", () => {
86
86
  it.skipIf(process.platform === "win32" || !!process.env.CI)("discovers tools from stdio MCP echo stub", async () => {
87
87
  // Inline MCP echo server script — handles initialize, notifications/initialized,
88
88
  // and tools/list using Content-Length framing per the MCP JSON-RPC spec.
89
- const echoServerScript = `
90
- const { stdin, stdout } = require('process');
91
- let buf = '';
92
- stdin.setEncoding('utf8');
93
- stdin.on('data', (chunk) => {
94
- buf += chunk;
95
- while (true) {
96
- const headerEnd = buf.indexOf('\\r\\n\\r\\n');
97
- if (headerEnd === -1) break;
98
- const header = buf.slice(0, headerEnd);
99
- const match = header.match(/Content-Length:\\s*(\\d+)/i);
100
- if (!match) { buf = buf.slice(headerEnd + 4); continue; }
101
- const len = parseInt(match[1], 10);
102
- const bodyStart = headerEnd + 4;
103
- if (buf.length < bodyStart + len) break;
104
- const body = buf.slice(bodyStart, bodyStart + len);
105
- buf = buf.slice(bodyStart + len);
106
- handleMessage(JSON.parse(body));
107
- }
108
- });
109
- function send(obj) {
110
- const s = JSON.stringify(obj);
111
- stdout.write('Content-Length: ' + Buffer.byteLength(s) + '\\r\\n\\r\\n' + s);
112
- }
113
- function handleMessage(msg) {
114
- if (msg.method === 'initialize') {
115
- send({ jsonrpc: '2.0', id: msg.id, result: {
116
- protocolVersion: '2024-11-05',
117
- capabilities: { tools: { listChanged: false } },
118
- serverInfo: { name: 'echo-stub', version: '1.0.0' }
119
- }});
120
- } else if (msg.method === 'notifications/initialized') {
121
- // no response needed
122
- } else if (msg.method === 'tools/list') {
123
- send({ jsonrpc: '2.0', id: msg.id, result: {
124
- tools: [{
125
- name: 'echo',
126
- description: 'Echoes input',
127
- inputSchema: { type: 'object', properties: { message: { type: 'string' } }, required: ['message'] }
128
- }]
129
- }});
130
- }
131
- }
89
+ const echoServerScript = `
90
+ const { stdin, stdout } = require('process');
91
+ let buf = '';
92
+ stdin.setEncoding('utf8');
93
+ stdin.on('data', (chunk) => {
94
+ buf += chunk;
95
+ while (true) {
96
+ const headerEnd = buf.indexOf('\\r\\n\\r\\n');
97
+ if (headerEnd === -1) break;
98
+ const header = buf.slice(0, headerEnd);
99
+ const match = header.match(/Content-Length:\\s*(\\d+)/i);
100
+ if (!match) { buf = buf.slice(headerEnd + 4); continue; }
101
+ const len = parseInt(match[1], 10);
102
+ const bodyStart = headerEnd + 4;
103
+ if (buf.length < bodyStart + len) break;
104
+ const body = buf.slice(bodyStart, bodyStart + len);
105
+ buf = buf.slice(bodyStart + len);
106
+ handleMessage(JSON.parse(body));
107
+ }
108
+ });
109
+ function send(obj) {
110
+ const s = JSON.stringify(obj);
111
+ stdout.write('Content-Length: ' + Buffer.byteLength(s) + '\\r\\n\\r\\n' + s);
112
+ }
113
+ function handleMessage(msg) {
114
+ if (msg.method === 'initialize') {
115
+ send({ jsonrpc: '2.0', id: msg.id, result: {
116
+ protocolVersion: '2024-11-05',
117
+ capabilities: { tools: { listChanged: false } },
118
+ serverInfo: { name: 'echo-stub', version: '1.0.0' }
119
+ }});
120
+ } else if (msg.method === 'notifications/initialized') {
121
+ // no response needed
122
+ } else if (msg.method === 'tools/list') {
123
+ send({ jsonrpc: '2.0', id: msg.id, result: {
124
+ tools: [{
125
+ name: 'echo',
126
+ description: 'Echoes input',
127
+ inputSchema: { type: 'object', properties: { message: { type: 'string' } }, required: ['message'] }
128
+ }]
129
+ }});
130
+ }
131
+ }
132
132
  `;
133
133
  const tmpDir = await mkdtemp(join(tmpdir(), "mcp-echo-stub-"));
134
134
  const scriptPath = join(tmpDir, "echo-server.js");
@@ -6,9 +6,16 @@
6
6
  * immediately; poll with selfverify.status; fetch selfverify.result when done.
7
7
  *
8
8
  * Lives at the app layer (NOT agent-harness-core) so it may import src/self-qa.
9
+ *
10
+ * NOTE: this server is for EXTERNAL agents (Claude Code etc.). The CLI's OWN
11
+ * inner agent now exposes the same capabilities as NATIVE in-process builtins
12
+ * (src/tools/native-tools.ts) — it no longer self-spawns this server. The two
13
+ * surfaces share their cores (self-verify-runner.ts, setup-guide-text.ts, the
14
+ * ee/forensics/lsp modules) so behaviour is identical.
9
15
  */
10
16
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
17
  import { JobManager, type Runner } from "./self-verify-jobs.js";
12
18
  export declare function registerSelfVerifyTools(server: McpServer, jm: JobManager): void;
19
+ export declare function registerSetupGuideTool(server: McpServer): void;
13
20
  export declare function createToolsServer(runner?: Runner): McpServer;
14
21
  export declare function runToolsMcpServer(): Promise<void>;
@@ -6,6 +6,12 @@
6
6
  * immediately; poll with selfverify.status; fetch selfverify.result when done.
7
7
  *
8
8
  * Lives at the app layer (NOT agent-harness-core) so it may import src/self-qa.
9
+ *
10
+ * NOTE: this server is for EXTERNAL agents (Claude Code etc.). The CLI's OWN
11
+ * inner agent now exposes the same capabilities as NATIVE in-process builtins
12
+ * (src/tools/native-tools.ts) — it no longer self-spawns this server. The two
13
+ * surfaces share their cores (self-verify-runner.ts, setup-guide-text.ts, the
14
+ * ee/forensics/lsp modules) so behaviour is identical.
9
15
  */
10
16
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
17
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -14,29 +20,9 @@ import { registerEETools } from "./ee-tools.js";
14
20
  import { registerForensicsTools } from "./forensics-tools.js";
15
21
  import { registerLspTools } from "./lsp-tools.js";
16
22
  import { JobManager } from "./self-verify-jobs.js";
23
+ import { defaultRunner } from "./self-verify-runner.js";
24
+ import { SETUP_GUIDE_TEXT } from "./setup-guide-text.js";
17
25
  const LOG_TAIL = 40;
18
- /** Default runner: drives the real self-verify functions in-process. */
19
- const defaultRunner = {
20
- async tier1(opts, log) {
21
- // signal intentionally not forwarded: runSelfVerify/runAgenticLoop do not yet
22
- // accept an AbortSignal. cancel() marks the job and discards the late result.
23
- const { runSelfVerify } = await import("../self-qa/index.js");
24
- return runSelfVerify({
25
- baseRef: opts.since,
26
- maxScenarios: opts.max,
27
- emitSpecs: opts.emit,
28
- specOutDir: opts.out,
29
- log,
30
- });
31
- },
32
- async agentic(opts, log) {
33
- // signal intentionally not forwarded: runSelfVerify/runAgenticLoop do not yet
34
- // accept an AbortSignal. cancel() marks the job and discards the late result.
35
- const { createLLMBrain, runAgenticLoop } = await import("../self-qa/agentic-loop.js");
36
- const brain = await createLLMBrain({ modelId: opts.llm });
37
- return runAgenticLoop({ goal: opts.goal, brain, maxTurns: opts.turns ?? 20, log });
38
- },
39
- };
40
26
  function ok(data) {
41
27
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
42
28
  }
@@ -129,6 +115,16 @@ export function registerSelfVerifyTools(server, jm) {
129
115
  });
130
116
  server.registerTool("selfverify_cancel", { description: "Cancel a running self-verify run (best-effort).", inputSchema: { runId: z.string() } }, async ({ runId }) => ok({ cancelled: jm.cancel(runId) }));
131
117
  }
118
+ export function registerSetupGuideTool(server) {
119
+ server.registerTool("setup_guide", {
120
+ description: "Returns a concise, up-to-date setup, install, first-run, MCP wiring, and verification guide for muonroi-cli. " +
121
+ "Call this directly (setup_guide) when the user asks for setup instructions, onboarding, or 'how do I start' — " +
122
+ "instead of guessing, reading files, or shelling commands. Keeps agents on the happy path.",
123
+ inputSchema: {},
124
+ }, async () => {
125
+ return { content: [{ type: "text", text: SETUP_GUIDE_TEXT }] };
126
+ });
127
+ }
132
128
  export function createToolsServer(runner = defaultRunner) {
133
129
  const server = new McpServer({ name: "muonroi-tools", version: "0.1.0" });
134
130
  const jm = new JobManager(runner);
@@ -136,6 +132,7 @@ export function createToolsServer(runner = defaultRunner) {
136
132
  registerEETools(server);
137
133
  registerForensicsTools(server);
138
134
  registerLspTools(server);
135
+ registerSetupGuideTool(server);
139
136
  return server;
140
137
  }
141
138
  export async function runToolsMcpServer() {