codebase-onboarder 0.1.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 (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +275 -0
  3. package/bin/onboarder.js +14 -0
  4. package/bin/postinstall.js +6 -0
  5. package/cli/commands.js +436 -0
  6. package/cli/main.js +129 -0
  7. package/cli/prompt.js +101 -0
  8. package/cli/ui.js +55 -0
  9. package/cli/wizard.js +325 -0
  10. package/package.json +48 -0
  11. package/public/app.js +1339 -0
  12. package/public/index.html +455 -0
  13. package/public/js/about.js +284 -0
  14. package/public/js/aiDraft.js +163 -0
  15. package/public/js/analysisPanel.js +270 -0
  16. package/public/js/analysisReport.js +154 -0
  17. package/public/js/api.js +264 -0
  18. package/public/js/atlas.js +98 -0
  19. package/public/js/blameView.js +41 -0
  20. package/public/js/codeTab.js +256 -0
  21. package/public/js/codeViewer.js +184 -0
  22. package/public/js/components/FileChip.js +102 -0
  23. package/public/js/components/MetricSparkline.js +89 -0
  24. package/public/js/components/RiskBadge.js +105 -0
  25. package/public/js/deepAnalysisView.js +496 -0
  26. package/public/js/diagramPane.js +154 -0
  27. package/public/js/diffView.js +352 -0
  28. package/public/js/docsView.js +566 -0
  29. package/public/js/fileSourceBrowser.js +47 -0
  30. package/public/js/flameGraph.js +75 -0
  31. package/public/js/forceGraph.js +592 -0
  32. package/public/js/heatmap.js +185 -0
  33. package/public/js/highlight.js +122 -0
  34. package/public/js/html.js +50 -0
  35. package/public/js/insightsView.js +236 -0
  36. package/public/js/inspector.js +535 -0
  37. package/public/js/llm.js +345 -0
  38. package/public/js/mapView.js +159 -0
  39. package/public/js/markdown.js +119 -0
  40. package/public/js/mindmap.js +289 -0
  41. package/public/js/repoFiles.js +44 -0
  42. package/public/js/sbomView.js +142 -0
  43. package/public/js/scanCache.js +76 -0
  44. package/public/js/search.js +874 -0
  45. package/public/js/serverSettings.js +169 -0
  46. package/public/js/state.js +192 -0
  47. package/public/js/tour.js +17 -0
  48. package/public/js/transitions.js +15 -0
  49. package/public/js/tree.js +200 -0
  50. package/public/js/workflowsView.js +95 -0
  51. package/public/styles.css +2767 -0
  52. package/public/vendor/mermaid.min.js +3587 -0
  53. package/public/vendor/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  54. package/public/vendor/monaco/vs/base/worker/workerMain.js +31 -0
  55. package/public/vendor/monaco/vs/basic-languages/abap/abap.js +10 -0
  56. package/public/vendor/monaco/vs/basic-languages/apex/apex.js +10 -0
  57. package/public/vendor/monaco/vs/basic-languages/azcli/azcli.js +10 -0
  58. package/public/vendor/monaco/vs/basic-languages/bat/bat.js +10 -0
  59. package/public/vendor/monaco/vs/basic-languages/bicep/bicep.js +11 -0
  60. package/public/vendor/monaco/vs/basic-languages/cameligo/cameligo.js +10 -0
  61. package/public/vendor/monaco/vs/basic-languages/clojure/clojure.js +10 -0
  62. package/public/vendor/monaco/vs/basic-languages/coffee/coffee.js +10 -0
  63. package/public/vendor/monaco/vs/basic-languages/cpp/cpp.js +10 -0
  64. package/public/vendor/monaco/vs/basic-languages/csharp/csharp.js +10 -0
  65. package/public/vendor/monaco/vs/basic-languages/csp/csp.js +10 -0
  66. package/public/vendor/monaco/vs/basic-languages/css/css.js +12 -0
  67. package/public/vendor/monaco/vs/basic-languages/cypher/cypher.js +10 -0
  68. package/public/vendor/monaco/vs/basic-languages/dart/dart.js +10 -0
  69. package/public/vendor/monaco/vs/basic-languages/dockerfile/dockerfile.js +10 -0
  70. package/public/vendor/monaco/vs/basic-languages/ecl/ecl.js +10 -0
  71. package/public/vendor/monaco/vs/basic-languages/elixir/elixir.js +10 -0
  72. package/public/vendor/monaco/vs/basic-languages/flow9/flow9.js +10 -0
  73. package/public/vendor/monaco/vs/basic-languages/freemarker2/freemarker2.js +12 -0
  74. package/public/vendor/monaco/vs/basic-languages/fsharp/fsharp.js +10 -0
  75. package/public/vendor/monaco/vs/basic-languages/go/go.js +10 -0
  76. package/public/vendor/monaco/vs/basic-languages/graphql/graphql.js +10 -0
  77. package/public/vendor/monaco/vs/basic-languages/handlebars/handlebars.js +10 -0
  78. package/public/vendor/monaco/vs/basic-languages/hcl/hcl.js +10 -0
  79. package/public/vendor/monaco/vs/basic-languages/html/html.js +10 -0
  80. package/public/vendor/monaco/vs/basic-languages/ini/ini.js +10 -0
  81. package/public/vendor/monaco/vs/basic-languages/java/java.js +10 -0
  82. package/public/vendor/monaco/vs/basic-languages/javascript/javascript.js +10 -0
  83. package/public/vendor/monaco/vs/basic-languages/julia/julia.js +10 -0
  84. package/public/vendor/monaco/vs/basic-languages/kotlin/kotlin.js +10 -0
  85. package/public/vendor/monaco/vs/basic-languages/less/less.js +11 -0
  86. package/public/vendor/monaco/vs/basic-languages/lexon/lexon.js +10 -0
  87. package/public/vendor/monaco/vs/basic-languages/liquid/liquid.js +10 -0
  88. package/public/vendor/monaco/vs/basic-languages/lua/lua.js +10 -0
  89. package/public/vendor/monaco/vs/basic-languages/m3/m3.js +10 -0
  90. package/public/vendor/monaco/vs/basic-languages/markdown/markdown.js +10 -0
  91. package/public/vendor/monaco/vs/basic-languages/mdx/mdx.js +10 -0
  92. package/public/vendor/monaco/vs/basic-languages/mips/mips.js +10 -0
  93. package/public/vendor/monaco/vs/basic-languages/msdax/msdax.js +10 -0
  94. package/public/vendor/monaco/vs/basic-languages/mysql/mysql.js +10 -0
  95. package/public/vendor/monaco/vs/basic-languages/objective-c/objective-c.js +10 -0
  96. package/public/vendor/monaco/vs/basic-languages/pascal/pascal.js +10 -0
  97. package/public/vendor/monaco/vs/basic-languages/pascaligo/pascaligo.js +10 -0
  98. package/public/vendor/monaco/vs/basic-languages/perl/perl.js +10 -0
  99. package/public/vendor/monaco/vs/basic-languages/pgsql/pgsql.js +10 -0
  100. package/public/vendor/monaco/vs/basic-languages/php/php.js +10 -0
  101. package/public/vendor/monaco/vs/basic-languages/pla/pla.js +10 -0
  102. package/public/vendor/monaco/vs/basic-languages/postiats/postiats.js +10 -0
  103. package/public/vendor/monaco/vs/basic-languages/powerquery/powerquery.js +10 -0
  104. package/public/vendor/monaco/vs/basic-languages/powershell/powershell.js +10 -0
  105. package/public/vendor/monaco/vs/basic-languages/protobuf/protobuf.js +11 -0
  106. package/public/vendor/monaco/vs/basic-languages/pug/pug.js +10 -0
  107. package/public/vendor/monaco/vs/basic-languages/python/python.js +10 -0
  108. package/public/vendor/monaco/vs/basic-languages/qsharp/qsharp.js +10 -0
  109. package/public/vendor/monaco/vs/basic-languages/r/r.js +10 -0
  110. package/public/vendor/monaco/vs/basic-languages/razor/razor.js +10 -0
  111. package/public/vendor/monaco/vs/basic-languages/redis/redis.js +10 -0
  112. package/public/vendor/monaco/vs/basic-languages/redshift/redshift.js +10 -0
  113. package/public/vendor/monaco/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  114. package/public/vendor/monaco/vs/basic-languages/ruby/ruby.js +10 -0
  115. package/public/vendor/monaco/vs/basic-languages/rust/rust.js +10 -0
  116. package/public/vendor/monaco/vs/basic-languages/sb/sb.js +10 -0
  117. package/public/vendor/monaco/vs/basic-languages/scala/scala.js +10 -0
  118. package/public/vendor/monaco/vs/basic-languages/scheme/scheme.js +10 -0
  119. package/public/vendor/monaco/vs/basic-languages/scss/scss.js +12 -0
  120. package/public/vendor/monaco/vs/basic-languages/shell/shell.js +10 -0
  121. package/public/vendor/monaco/vs/basic-languages/solidity/solidity.js +10 -0
  122. package/public/vendor/monaco/vs/basic-languages/sophia/sophia.js +10 -0
  123. package/public/vendor/monaco/vs/basic-languages/sparql/sparql.js +10 -0
  124. package/public/vendor/monaco/vs/basic-languages/sql/sql.js +10 -0
  125. package/public/vendor/monaco/vs/basic-languages/st/st.js +10 -0
  126. package/public/vendor/monaco/vs/basic-languages/swift/swift.js +13 -0
  127. package/public/vendor/monaco/vs/basic-languages/systemverilog/systemverilog.js +10 -0
  128. package/public/vendor/monaco/vs/basic-languages/tcl/tcl.js +10 -0
  129. package/public/vendor/monaco/vs/basic-languages/twig/twig.js +10 -0
  130. package/public/vendor/monaco/vs/basic-languages/typescript/typescript.js +10 -0
  131. package/public/vendor/monaco/vs/basic-languages/typespec/typespec.js +10 -0
  132. package/public/vendor/monaco/vs/basic-languages/vb/vb.js +10 -0
  133. package/public/vendor/monaco/vs/basic-languages/wgsl/wgsl.js +307 -0
  134. package/public/vendor/monaco/vs/basic-languages/xml/xml.js +10 -0
  135. package/public/vendor/monaco/vs/basic-languages/yaml/yaml.js +10 -0
  136. package/public/vendor/monaco/vs/editor/editor.main.css +8 -0
  137. package/public/vendor/monaco/vs/editor/editor.main.js +798 -0
  138. package/public/vendor/monaco/vs/language/css/cssMode.js +13 -0
  139. package/public/vendor/monaco/vs/language/css/cssWorker.js +77 -0
  140. package/public/vendor/monaco/vs/language/html/htmlMode.js +13 -0
  141. package/public/vendor/monaco/vs/language/html/htmlWorker.js +454 -0
  142. package/public/vendor/monaco/vs/language/json/jsonMode.js +19 -0
  143. package/public/vendor/monaco/vs/language/json/jsonWorker.js +42 -0
  144. package/public/vendor/monaco/vs/language/typescript/tsMode.js +20 -0
  145. package/public/vendor/monaco/vs/language/typescript/tsWorker.js +51328 -0
  146. package/public/vendor/monaco/vs/loader.js +11 -0
  147. package/public/vendor/monaco/worker-boot.js +9 -0
  148. package/server/.fuse_hidden0000000800000001 +36 -0
  149. package/server/apiDiff.js +25 -0
  150. package/server/apiDocs.js +67 -0
  151. package/server/apiFile.js +36 -0
  152. package/server/apiGitBlame.js +63 -0
  153. package/server/apiMcp.js +110 -0
  154. package/server/apiScan.js +119 -0
  155. package/server/apiSearch.js +253 -0
  156. package/server/apiSettings.js +118 -0
  157. package/server/apiTools.js +90 -0
  158. package/server/config.js +227 -0
  159. package/server/fileSourceNode.js +44 -0
  160. package/server/gitClone.js +95 -0
  161. package/server/gitDiff.js +184 -0
  162. package/server/gitHistory.js +110 -0
  163. package/server/htmlText.js +44 -0
  164. package/server/http.js +55 -0
  165. package/server/httpGuards.js +87 -0
  166. package/server/index.js +156 -0
  167. package/server/llmProxy.js +162 -0
  168. package/server/logger.js +29 -0
  169. package/server/mcp/analysis.js +209 -0
  170. package/server/mcp/http.js +213 -0
  171. package/server/mcp/runner.js +278 -0
  172. package/server/mcp/server.js +241 -0
  173. package/server/mcp/standalone.js +42 -0
  174. package/server/mcp/tools.js +683 -0
  175. package/server/paths.js +39 -0
  176. package/server/router.js +218 -0
  177. package/server/searchIndex.js +118 -0
  178. package/server/sessions.js +163 -0
  179. package/server/static.js +59 -0
  180. package/server/tools/install.js +246 -0
  181. package/server/tools/parse.js +170 -0
  182. package/server/tools/platform.js +91 -0
  183. package/server/tools/registry.js +212 -0
  184. package/server/tools/scan.js +136 -0
  185. package/server/tools.js +212 -0
  186. package/server/tunnel.js +92 -0
  187. package/shared/analyzer/docs.js +135 -0
  188. package/shared/analyzer/explainLocal.js +163 -0
  189. package/shared/analyzer/graph.js +461 -0
  190. package/shared/analyzer/health.js +215 -0
  191. package/shared/analyzer/history.js +146 -0
  192. package/shared/analyzer/languages/csharp.js +39 -0
  193. package/shared/analyzer/languages/generic.js +131 -0
  194. package/shared/analyzer/languages/go.js +70 -0
  195. package/shared/analyzer/languages/index.js +43 -0
  196. package/shared/analyzer/languages/java.js +39 -0
  197. package/shared/analyzer/languages/javascript.js +240 -0
  198. package/shared/analyzer/languages/python.js +120 -0
  199. package/shared/analyzer/languages/rust.js +42 -0
  200. package/shared/analyzer/languages/typescript.js +138 -0
  201. package/shared/analyzer/licenses.js +151 -0
  202. package/shared/analyzer/metrics.js +120 -0
  203. package/shared/analyzer/pathUtil.js +69 -0
  204. package/shared/analyzer/patterns.js +272 -0
  205. package/shared/analyzer/scan.js +473 -0
  206. package/shared/analyzer/security.js +187 -0
  207. package/shared/analyzer/services.js +157 -0
  208. package/shared/analyzer/stack.js +173 -0
  209. package/shared/analyzer/tour.js +69 -0
  210. package/shared/analyzer/util.js +123 -0
  211. package/shared/analyzer/workflows.js +143 -0
  212. package/shared/diagram/aiFacts.js +145 -0
  213. package/shared/diagram/aiMermaid.js +59 -0
  214. package/shared/diagram/atlas.js +93 -0
  215. package/shared/diagram/mermaid.js +459 -0
  216. package/shared/search/query.js +518 -0
@@ -0,0 +1,241 @@
1
+ // The MCP server: JSON-RPC 2.0 over stdio, one JSON object per line.
2
+ //
3
+ // Why no SDK: the project has no dependencies, and `package.json` says so. MCP's
4
+ // stdio transport is a framing convention (one line-delimited JSON-RPC message
5
+ // per message, on the child's stdin/stdout), not a wire protocol with magic — so
6
+ // implementing it directly is a few dozen lines and keeps the promise that this
7
+ // repo installs with nothing. If the SDK ever becomes worth it, this file is the
8
+ // only thing that changes; `tools.js` and `analysis.js` know nothing about JSON.
9
+ //
10
+ // The rules that matter for correctness:
11
+ // * stdout belongs to the protocol. Anything else printed there corrupts the
12
+ // stream, so the logger is redirected to stderr for the lifetime of the
13
+ // child (see `runner.js`).
14
+ // * Requests are answered in order. MCP clients pipeline, and answering a
15
+ // `tools/call` out of order makes some clients discard the later result.
16
+ // * A tool that throws is answered with a *result* carrying `isError: true`,
17
+ // not a JSON-RPC error. That is what puts the message in front of the model
18
+ // so it can correct itself; a protocol error just fails the turn.
19
+
20
+ import { toolDefinitions, callTool, findTool } from './tools.js';
21
+ import { ToolError } from './analysis.js';
22
+
23
+ export const PROTOCOL_VERSION = '2025-06-18';
24
+ export const SERVER_INFO = {
25
+ name: 'onboarder',
26
+ version: '1.0.0',
27
+ title: 'Onboarder',
28
+ instructions:
29
+ 'Onboarder explains a codebase. Start with onboarder_scan on a repository path; '
30
+ + 'it returns a written overview, the entry points, the most depended-upon files, and '
31
+ + 'any dependency cycles. Then use onboarder_tour for a reading order, onboarder_search '
32
+ + 'to find things by name, onboarder_explain_file for one file, and onboarder_read_file '
33
+ + 'for raw source. Every tool also takes `path`; omit it to keep working on the last '
34
+ + 'repository scanned. The repository is read, never written.',
35
+ };
36
+
37
+ // JSON-RPC error codes. Only the first two are protocol-level; the rest are
38
+ // conventional in MCP and keep a misbehaving client from parsing a server bug as
39
+ // a server bug it caused.
40
+ const PARSE_ERROR = -32700;
41
+ const INVALID_REQUEST = -32600;
42
+ const METHOD_NOT_FOUND = -32601;
43
+ const INVALID_PARAMS = -32602;
44
+
45
+ function result(id, value) {
46
+ return { jsonrpc: '2.0', id, result: value };
47
+ }
48
+
49
+ function failure(id, code, message, data) {
50
+ const error = { code, message };
51
+ if (data !== undefined) error.data = data;
52
+ return { jsonrpc: '2.0', id, error };
53
+ }
54
+
55
+ // A tool error and an internal error are different things. A `ToolError` is a
56
+ // sentence the model can act on — "that path is outside the repo" — so it becomes
57
+ // an isError result with that sentence intact. Anything else is a bug, and saying
58
+ // so plainly beats pretending it was the caller's fault.
59
+ async function callOne(name, args) {
60
+ const tool = findTool(name);
61
+ if (!tool) {
62
+ return {
63
+ isError: true,
64
+ content: [{ type: 'text', text: `No tool called "${name}". Call tools/list to see what is available.` }],
65
+ };
66
+ }
67
+ try {
68
+ const value = await tool.run(args || {});
69
+ // MCP content is a list of blocks. A tool that returns structured data sends
70
+ // it as JSON text plus the native `structuredContent` field, which is what
71
+ // typed clients read; the text block is what everything else falls back to.
72
+ return {
73
+ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
74
+ structuredContent: value,
75
+ isError: false,
76
+ };
77
+ } catch (err) {
78
+ if (err instanceof ToolError) {
79
+ return { isError: true, content: [{ type: 'text', text: err.message }] };
80
+ }
81
+ // Unexpected. Report the message, not the stack: the caller is a model on the
82
+ // other end of a pipe, and a stack trace is noise. The full error is on the
83
+ // child process's stderr, which is where a human debugging this will look.
84
+ console.error('[onboarder-mcp] tool failed:', err);
85
+ return {
86
+ isError: true,
87
+ content: [{ type: 'text', text: `The tool failed unexpectedly: ${err?.message || String(err)}` }],
88
+ };
89
+ }
90
+ }
91
+
92
+ // The dispatcher is exported because the Streamable HTTP transport in `./http.js`
93
+ // needs to answer the same messages without going through a line of stdio. The
94
+ // protocol logic lives in exactly one place; a second transport must not become a
95
+ // second implementation of `tools/list`.
96
+ export { handle };
97
+
98
+ async function handle(msg) {
99
+ // A request must name a method and must declare the protocol version. Anything
100
+ // else is not a request at all, and replying to it would invent an id to answer
101
+ // under — so this returns nothing and the line is dropped.
102
+ //
103
+ // `null` rather than `undefined` is the contract with the loop below: both mean
104
+ // "say nothing", but only one of them survives being awaited and compared.
105
+ if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0' || typeof msg.method !== 'string') {
106
+ return null;
107
+ }
108
+ const { method, id, params } = msg;
109
+
110
+ // A notification has no id and expects no answer. Returning `null` is how the
111
+ // loop knows to stay silent, which matters: replying to a notification is
112
+ // itself a protocol violation.
113
+ const isNotification = id === undefined || id === null;
114
+
115
+ switch (method) {
116
+ case 'initialize':
117
+ return isNotification ? undefined : result(id, {
118
+ protocolVersion: PROTOCOL_VERSION,
119
+ capabilities: { tools: { listChanged: false } },
120
+ serverInfo: SERVER_INFO,
121
+ });
122
+
123
+ // The client's post-handshake ping. Answering it is what tells the client the
124
+ // handshake completed and the tool loop is live.
125
+ case 'notifications/initialized':
126
+ return null;
127
+
128
+ case 'ping':
129
+ return isNotification ? undefined : result(id, {});
130
+
131
+ case 'tools/list':
132
+ return isNotification ? undefined : result(id, { tools: toolDefinitions() });
133
+
134
+ case 'tools/call':
135
+ if (isNotification) return null;
136
+ if (!params?.name || typeof params.name !== 'string') {
137
+ return failure(id, INVALID_PARAMS, 'tools/call needs a tool name.');
138
+ }
139
+ return result(id, await callOne(params.name, params.arguments));
140
+
141
+ // A client asking for resources or prompts is not an error — this server has
142
+ // none, and saying so is more useful than METHOD_NOT_FOUND. A harness that
143
+ // probes both at startup gets a clean answer instead of a warning.
144
+ case 'resources/list':
145
+ return result(id, { resources: [] });
146
+
147
+ case 'prompts/list':
148
+ return result(id, { prompts: [] });
149
+
150
+ case 'resources/templates/list':
151
+ return result(id, { resourceTemplates: [] });
152
+
153
+ default:
154
+ if (isNotification) return null;
155
+ return failure(id, METHOD_NOT_FOUND, `No method called "${method}".`);
156
+ }
157
+ }
158
+
159
+ // One line in, one line out. A message that will not parse gets a parse error
160
+ // with `id: null`, which is what the spec requires — the client cannot know which
161
+ // request the bad line belonged to.
162
+ function parseLine(line) {
163
+ try {
164
+ const msg = JSON.parse(line);
165
+ if (!msg || typeof msg !== 'object' || Array.isArray(msg)) {
166
+ return { bad: failure(null, INVALID_REQUEST, 'A message must be a JSON-RPC object.') };
167
+ }
168
+ return { msg };
169
+ } catch (err) {
170
+ return { bad: failure(null, PARSE_ERROR, `Could not parse the message: ${err.message}`) };
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Run the server until stdin closes. `input` and `output` are parameters so tests
176
+ * can drive a whole session through in-memory streams.
177
+ */
178
+ export function serveMcp({ input = process.stdin, output = process.stdout } = {}) {
179
+ let buffer = '';
180
+ // Messages are handled strictly in order, but not awaited inline: a long scan
181
+ // must not block the event loop from reading the next line, or a client that
182
+ // pipelines would sit with a full buffer. The promise chain is what preserves
183
+ // the ordering while letting reads continue.
184
+ let chain = Promise.resolve();
185
+ let closed = false;
186
+
187
+ const send = (message) => {
188
+ if (closed) return;
189
+ output.write(JSON.stringify(message) + '\n');
190
+ };
191
+
192
+ const enqueue = (line) => {
193
+ chain = chain.then(async () => {
194
+ const { msg, bad } = parseLine(line);
195
+ if (bad) { send(bad); return; }
196
+ const response = await handle(msg);
197
+ if (response) send(response);
198
+ }).catch((err) => {
199
+ // `handle` already turns tool failures into results. Reaching here means
200
+ // something threw outside that, so the only honest move is to log it and
201
+ // keep serving — a dead server helps nobody recover.
202
+ console.error('[onboarder-mcp] dispatch failed:', err);
203
+ });
204
+ };
205
+
206
+ input.setEncoding?.('utf8');
207
+ input.on('data', (chunk) => {
208
+ buffer += chunk;
209
+ let nl;
210
+ // A partial line at the end of a chunk stays in the buffer for the next one.
211
+ while ((nl = buffer.indexOf('\n')) !== -1) {
212
+ const line = buffer.slice(0, nl).trim();
213
+ buffer = buffer.slice(nl + 1);
214
+ if (line) enqueue(line);
215
+ }
216
+ });
217
+
218
+ input.on('end', () => {
219
+ // A last line without a trailing newline is still a message. Dropping it
220
+ // would lose the final request of a session — which is often the only one.
221
+ const line = buffer.trim();
222
+ buffer = '';
223
+ if (line) enqueue(line);
224
+ // Let the queue finish before the process exits, so a reply written just
225
+ // after stdin closed still reaches the client.
226
+ chain.finally(() => {
227
+ if (output === process.stdout) process.exitCode = 0;
228
+ });
229
+ });
230
+
231
+ input.on('error', (err) => {
232
+ console.error('[onboarder-mcp] stdin error:', err);
233
+ });
234
+
235
+ return {
236
+ stop() { closed = true; },
237
+ // Awaiting the queue is how a test knows every reply has been written, and
238
+ // how a shutdown can drain in-flight work instead of cutting it off.
239
+ settled() { return chain; },
240
+ };
241
+ }
@@ -0,0 +1,42 @@
1
+ // The MCP server as its own process: `node server/mcp/standalone.js`.
2
+ //
3
+ // This is the command an agent harness is configured with. It is a separate entry
4
+ // point rather than a flag on the web server because the two have incompatible
5
+ // requirements for stdout — the web server logs to it, the MCP server speaks
6
+ // JSON-RPC over it and must own it completely.
7
+ //
8
+ // It also guards stdout for the lifetime of the process. Anything that writes
9
+ // there — a `console.log` from a shared analyzer, a logger the tools reach for —
10
+ // would be read by the client as a malformed protocol frame, and the failure
11
+ // would look like a server bug rather than a logging bug. Redirecting is better
12
+ // than trusting: the guard costs one comparison per write and cannot be forgotten
13
+ // by a future contributor three files deep.
14
+
15
+ import { serveMcp, SERVER_INFO } from './server.js';
16
+
17
+ // stdout is a shared pipe with two kinds of writer: the protocol, which must be
18
+ // the only thing a client ever sees, and everything else — a stray `console.log`
19
+ // in a shared analyzer, a logger — which would be read as a malformed frame and
20
+ // make the server look broken when it is not.
21
+ //
22
+ // Rather than hoping nothing else writes, this builds the protocol's writer first
23
+ // and hands it to `serveMcp`, then points `process.stdout.write` at stderr. A
24
+ // later `console.log` cannot corrupt the stream, and the protocol does not depend
25
+ // on being the first thing to run.
26
+ const realStdoutWrite = process.stdout.write.bind(process.stdout);
27
+ const protocolStream = { write: (chunk) => realStdoutWrite(chunk) };
28
+
29
+ process.stdout.write = function toStderr(chunk, encoding, callback) {
30
+ process.stderr.write(chunk);
31
+ if (typeof encoding === 'function') encoding();
32
+ else if (typeof callback === 'function') callback();
33
+ return true;
34
+ };
35
+
36
+ // Diagnostics are opt-in: a client reading stdout is reading a protocol stream
37
+ // and nothing else, so anything else has to be asked for.
38
+ if (process.env.ONBOARDER_MCP_LOG === '1') {
39
+ console.error(`[onboarder-mcp] ${SERVER_INFO.name} ${SERVER_INFO.version} starting on stdio`);
40
+ }
41
+
42
+ serveMcp({ output: protocolStream });