@massu/core 1.6.2 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -27759,111 +27759,163 @@ var init_tool_db_needs = __esm({
27759
27759
  }
27760
27760
  });
27761
27761
 
27762
- // src/server.ts
27763
- var server_exports = {};
27764
- import { readFileSync as readFileSync39 } from "fs";
27765
- import { resolve as resolve32, dirname as dirname16 } from "path";
27766
- import { fileURLToPath as fileURLToPath4 } from "url";
27767
- function resolveDbsForTool(toolName) {
27768
- const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
27769
- let dataDbResolved;
27770
- let codegraphDbResolved;
27771
- if (needs.includes("data")) {
27772
- if (!dataDbCache) dataDbCache = getDataDb();
27773
- dataDbResolved = dataDbCache;
27774
- }
27775
- if (needs.includes("codegraph")) {
27776
- if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb();
27777
- codegraphDbResolved = codegraphDbCache;
27778
- }
27779
- return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
27780
- }
27781
- async function handleRequest(request) {
27782
- const { method, params, id } = request;
27783
- switch (method) {
27784
- case "initialize": {
27785
- return {
27786
- jsonrpc: "2.0",
27787
- id: id ?? null,
27788
- result: {
27789
- protocolVersion: "2024-11-05",
27790
- capabilities: {
27791
- tools: {}
27792
- },
27793
- serverInfo: {
27794
- name: getConfig().toolPrefix || "massu",
27795
- version: PKG_VERSION
27796
- }
27797
- }
27798
- };
27799
- }
27800
- case "notifications/initialized": {
27801
- return { jsonrpc: "2.0", id: id ?? null, result: {} };
27802
- }
27803
- case "tools/list": {
27804
- const tools = getToolDefinitions();
27805
- return {
27806
- jsonrpc: "2.0",
27807
- id: id ?? null,
27808
- result: { tools }
27809
- };
27810
- }
27811
- case "tools/call": {
27812
- const toolName = params?.name;
27813
- const toolArgs = params?.arguments ?? {};
27814
- try {
27815
- const { dataDb: lDb, codegraphDb: cgDb } = resolveDbsForTool(toolName);
27816
- const result = await handleToolCall(toolName, toolArgs, lDb, cgDb);
27762
+ // src/server-dispatch.ts
27763
+ function createDispatcher(options) {
27764
+ let codegraphDbCache = null;
27765
+ let dataDbCache = null;
27766
+ function resolveDbsForTool(toolName) {
27767
+ const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
27768
+ let dataDbResolved;
27769
+ let codegraphDbResolved;
27770
+ if (needs.includes("data")) {
27771
+ if (!dataDbCache) dataDbCache = getDataDb();
27772
+ dataDbResolved = dataDbCache;
27773
+ }
27774
+ if (needs.includes("codegraph")) {
27775
+ if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb();
27776
+ codegraphDbResolved = codegraphDbCache;
27777
+ }
27778
+ return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
27779
+ }
27780
+ async function handleRequest(request) {
27781
+ const { method, params, id } = request;
27782
+ switch (method) {
27783
+ case "initialize": {
27817
27784
  return {
27818
27785
  jsonrpc: "2.0",
27819
27786
  id: id ?? null,
27820
- result
27787
+ result: {
27788
+ protocolVersion: "2024-11-05",
27789
+ capabilities: { tools: {} },
27790
+ serverInfo: {
27791
+ name: getConfig().toolPrefix || "massu",
27792
+ version: options.serverInfoVersion
27793
+ }
27794
+ }
27821
27795
  };
27822
- } catch (err) {
27823
- if (err instanceof CodegraphDbNotInitializedError) {
27824
- return {
27825
- jsonrpc: "2.0",
27826
- id: id ?? null,
27827
- error: {
27828
- code: -32001,
27829
- message: `Tool requires CodeGraph database which is not initialized for this repo`,
27830
- data: {
27831
- remedy: "npx @colbymchenry/codegraph@0.7.4 init . && npx @colbymchenry/codegraph@0.7.4 index .",
27832
- codegraphDbPath: err.dbPath,
27833
- tool: toolName
27796
+ }
27797
+ case "notifications/initialized": {
27798
+ return { jsonrpc: "2.0", id: id ?? null, result: {} };
27799
+ }
27800
+ case "tools/list": {
27801
+ const tools = getToolDefinitions();
27802
+ return { jsonrpc: "2.0", id: id ?? null, result: { tools } };
27803
+ }
27804
+ case "tools/call": {
27805
+ const toolName = params?.name ?? "";
27806
+ const toolArgs = params?.arguments ?? {};
27807
+ try {
27808
+ const { dataDb, codegraphDb } = resolveDbsForTool(toolName);
27809
+ const result = await handleToolCall(toolName, toolArgs, dataDb, codegraphDb);
27810
+ return { jsonrpc: "2.0", id: id ?? null, result };
27811
+ } catch (err) {
27812
+ if (err instanceof CodegraphDbNotInitializedError) {
27813
+ return {
27814
+ jsonrpc: "2.0",
27815
+ id: id ?? null,
27816
+ error: {
27817
+ code: -32001,
27818
+ message: "Tool requires CodeGraph database which is not initialized for this repo",
27819
+ data: {
27820
+ remedy: "npx @colbymchenry/codegraph@0.7.4 init . && npx @colbymchenry/codegraph@0.7.4 index .",
27821
+ codegraphDbPath: err.dbPath,
27822
+ tool: toolName
27823
+ }
27834
27824
  }
27835
- }
27836
- };
27837
- }
27838
- if (err instanceof UnknownToolError) {
27839
- return {
27840
- jsonrpc: "2.0",
27841
- id: id ?? null,
27842
- error: {
27843
- code: -32602,
27844
- message: `Unknown tool: ${err.toolName}`,
27845
- data: {
27846
- remedy: "Tool not registered in TOOL_DB_NEEDS manifest. See packages/core/src/tool-db-needs.ts.",
27847
- tool: toolName
27825
+ };
27826
+ }
27827
+ if (err instanceof UnknownToolError) {
27828
+ return {
27829
+ jsonrpc: "2.0",
27830
+ id: id ?? null,
27831
+ error: {
27832
+ code: -32602,
27833
+ message: `Unknown tool: ${err.toolName}`,
27834
+ data: {
27835
+ remedy: "Tool not registered in TOOL_DB_NEEDS manifest. See packages/core/src/tool-db-needs.ts.",
27836
+ tool: toolName
27837
+ }
27848
27838
  }
27849
- }
27850
- };
27839
+ };
27840
+ }
27841
+ throw err;
27851
27842
  }
27852
- throw err;
27843
+ }
27844
+ case "ping": {
27845
+ return { jsonrpc: "2.0", id: id ?? null, result: {} };
27846
+ }
27847
+ default: {
27848
+ return {
27849
+ jsonrpc: "2.0",
27850
+ id: id ?? null,
27851
+ error: { code: -32601, message: `Method not found: ${method}` }
27852
+ };
27853
27853
  }
27854
27854
  }
27855
- case "ping": {
27856
- return { jsonrpc: "2.0", id: id ?? null, result: {} };
27855
+ }
27856
+ async function processLine(line) {
27857
+ const trimmed = line.trim();
27858
+ if (!trimmed) return null;
27859
+ let request;
27860
+ try {
27861
+ request = JSON.parse(trimmed);
27862
+ } catch (parseError) {
27863
+ return {
27864
+ response: {
27865
+ jsonrpc: "2.0",
27866
+ id: null,
27867
+ error: {
27868
+ code: -32700,
27869
+ message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`
27870
+ }
27871
+ },
27872
+ emit: true
27873
+ };
27857
27874
  }
27858
- default: {
27875
+ try {
27876
+ const response = await handleRequest(request);
27877
+ return { response, emit: request.id !== void 0 };
27878
+ } catch (error) {
27859
27879
  return {
27860
- jsonrpc: "2.0",
27861
- id: id ?? null,
27862
- error: { code: -32601, message: `Method not found: ${method}` }
27880
+ response: {
27881
+ jsonrpc: "2.0",
27882
+ id: request.id ?? null,
27883
+ error: {
27884
+ code: -32603,
27885
+ message: `Internal error: ${error instanceof Error ? error.message : String(error)}`
27886
+ }
27887
+ },
27888
+ emit: true
27863
27889
  };
27864
27890
  }
27865
27891
  }
27892
+ function closeCachedDbs() {
27893
+ if (codegraphDbCache) {
27894
+ codegraphDbCache.close();
27895
+ codegraphDbCache = null;
27896
+ }
27897
+ if (dataDbCache) {
27898
+ dataDbCache.close();
27899
+ dataDbCache = null;
27900
+ }
27901
+ }
27902
+ return { handleRequest, processLine, closeCachedDbs };
27866
27903
  }
27904
+ var init_server_dispatch = __esm({
27905
+ "src/server-dispatch.ts"() {
27906
+ "use strict";
27907
+ init_db();
27908
+ init_config();
27909
+ init_tools();
27910
+ init_tool_db_needs();
27911
+ }
27912
+ });
27913
+
27914
+ // src/server.ts
27915
+ var server_exports = {};
27916
+ import { readFileSync as readFileSync39 } from "fs";
27917
+ import { resolve as resolve32, dirname as dirname16 } from "path";
27918
+ import { fileURLToPath as fileURLToPath4 } from "url";
27867
27919
  function pruneMemoryOnStartup() {
27868
27920
  try {
27869
27921
  const memDb = getMemoryDb();
@@ -27887,16 +27939,13 @@ function pruneMemoryOnStartup() {
27887
27939
  );
27888
27940
  }
27889
27941
  }
27890
- var __dirname4, PKG_VERSION, codegraphDbCache, dataDbCache, buffer;
27942
+ var __dirname4, PKG_VERSION, dispatcher, buffer;
27891
27943
  var init_server = __esm({
27892
27944
  "src/server.ts"() {
27893
27945
  "use strict";
27894
- init_db();
27895
- init_config();
27896
- init_tools();
27897
27946
  init_memory_db();
27898
27947
  init_license();
27899
- init_tool_db_needs();
27948
+ init_server_dispatch();
27900
27949
  __dirname4 = dirname16(fileURLToPath4(import.meta.url));
27901
27950
  PKG_VERSION = (() => {
27902
27951
  try {
@@ -27906,8 +27955,7 @@ var init_server = __esm({
27906
27955
  return "0.0.0";
27907
27956
  }
27908
27957
  })();
27909
- codegraphDbCache = null;
27910
- dataDbCache = null;
27958
+ dispatcher = createDispatcher({ serverInfoVersion: PKG_VERSION });
27911
27959
  pruneMemoryOnStartup();
27912
27960
  getCurrentTier().then((tier) => {
27913
27961
  process.stderr.write(`massu: License tier: ${tier}
@@ -27924,46 +27972,16 @@ var init_server = __esm({
27924
27972
  buffer += chunk;
27925
27973
  let newlineIndex;
27926
27974
  while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
27927
- const line = buffer.slice(0, newlineIndex).trim();
27975
+ const line = buffer.slice(0, newlineIndex);
27928
27976
  buffer = buffer.slice(newlineIndex + 1);
27929
- if (!line) continue;
27930
- let request = null;
27931
- try {
27932
- request = JSON.parse(line);
27933
- } catch (parseError) {
27934
- const errorResponse = {
27935
- jsonrpc: "2.0",
27936
- id: null,
27937
- error: {
27938
- code: -32700,
27939
- message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`
27940
- }
27941
- };
27942
- process.stdout.write(JSON.stringify(errorResponse) + "\n");
27943
- continue;
27944
- }
27945
- try {
27946
- const response = await handleRequest(request);
27947
- if (request.id !== void 0) {
27948
- const responseStr = JSON.stringify(response);
27949
- process.stdout.write(responseStr + "\n");
27950
- }
27951
- } catch (error) {
27952
- const errorResponse = {
27953
- jsonrpc: "2.0",
27954
- id: request.id ?? null,
27955
- error: {
27956
- code: -32603,
27957
- message: `Internal error: ${error instanceof Error ? error.message : String(error)}`
27958
- }
27959
- };
27960
- process.stdout.write(JSON.stringify(errorResponse) + "\n");
27977
+ const result = await dispatcher.processLine(line);
27978
+ if (result && result.emit) {
27979
+ process.stdout.write(JSON.stringify(result.response) + "\n");
27961
27980
  }
27962
27981
  }
27963
27982
  });
27964
27983
  process.stdin.on("end", () => {
27965
- if (codegraphDbCache) codegraphDbCache.close();
27966
- if (dataDbCache) dataDbCache.close();
27984
+ dispatcher.closeCachedDbs();
27967
27985
  process.exit(0);
27968
27986
  });
27969
27987
  process.on("uncaughtException", (error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massu/core",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "type": "module",
5
5
  "description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 72 total), 55+ workflow commands, 11 agents, 20+ patterns",
6
6
  "main": "src/server.ts",
@@ -1,4 +1,4 @@
1
- // AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-11T05:57:55.925Z.
1
+ // AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-11T22:54:34.471Z.
2
2
  // Source pem: packages/core/security/registry-pubkey.pem
3
3
  // RAW-bytes sha256: 3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4c
4
4
  // DO NOT EDIT — regenerate via `node scripts/bundle-pubkey.mjs` or
@@ -0,0 +1,225 @@
1
+ // Copyright (c) 2026 Massu. All rights reserved.
2
+ // Licensed under BSL 1.1 - see LICENSE file for details.
3
+
4
+ /**
5
+ * MCP server dispatch logic — pure, factory-based, no module-level mutable state.
6
+ *
7
+ * Production (`server.ts`) calls `createDispatcher()` once at startup and wires
8
+ * its `processLine` into stdin. Tests call `createDispatcher()` per test for
9
+ * fresh DB cache state (no test bleed).
10
+ *
11
+ * Three error envelopes live here (CR-12 / plan-1.6.2-server-lazy-db-deps):
12
+ * -32001 Tool needs CodeGraph but `.codegraph/codegraph.db` is missing
13
+ * (CodegraphDbNotInitializedError → structured remedy data)
14
+ * -32602 Tool not registered in `TOOL_DB_NEEDS` manifest
15
+ * (UnknownToolError → points at tool-db-needs.ts)
16
+ * -32603 Other internal errors raised by handleToolCall — request id
17
+ * is preserved (NOT id:null, which is reserved for -32700 parse
18
+ * failures per JSON-RPC §5.1).
19
+ */
20
+
21
+ import type Database from 'better-sqlite3';
22
+ import { getCodeGraphDb, getDataDb, CodegraphDbNotInitializedError } from './db.ts';
23
+ import { getConfig } from './config.ts';
24
+ import { getToolDefinitions, handleToolCall } from './tools.ts';
25
+ import { getToolDbNeeds, UnknownToolError, type DbNeed } from './tool-db-needs.ts';
26
+
27
+ export interface JsonRpcRequest {
28
+ jsonrpc: '2.0';
29
+ id?: number | string;
30
+ method: string;
31
+ params?: Record<string, unknown>;
32
+ }
33
+
34
+ export interface JsonRpcResponse {
35
+ jsonrpc: '2.0';
36
+ id: number | string | null;
37
+ result?: unknown;
38
+ error?: { code: number; message: string; data?: unknown };
39
+ }
40
+
41
+ /** Per-line dispatch result. `emit=false` when the request was a notification (no id). */
42
+ export interface ProcessLineResult {
43
+ response: JsonRpcResponse;
44
+ emit: boolean;
45
+ }
46
+
47
+ export interface DispatcherOptions {
48
+ /** Version string surfaced in `initialize.result.serverInfo.version`. */
49
+ serverInfoVersion: string;
50
+ }
51
+
52
+ export interface Dispatcher {
53
+ handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse>;
54
+ processLine(line: string): Promise<ProcessLineResult | null>;
55
+ closeCachedDbs(): void;
56
+ }
57
+
58
+ export function createDispatcher(options: DispatcherOptions): Dispatcher {
59
+ let codegraphDbCache: Database.Database | null = null;
60
+ let dataDbCache: Database.Database | null = null;
61
+
62
+ function resolveDbsForTool(toolName: string): {
63
+ needs: readonly DbNeed[];
64
+ dataDb?: Database.Database;
65
+ codegraphDb?: Database.Database;
66
+ } {
67
+ const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
68
+
69
+ let dataDbResolved: Database.Database | undefined;
70
+ let codegraphDbResolved: Database.Database | undefined;
71
+
72
+ if (needs.includes('data')) {
73
+ if (!dataDbCache) dataDbCache = getDataDb();
74
+ dataDbResolved = dataDbCache;
75
+ }
76
+
77
+ if (needs.includes('codegraph')) {
78
+ // Throws CodegraphDbNotInitializedError when .codegraph/codegraph.db is missing.
79
+ if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb();
80
+ codegraphDbResolved = codegraphDbCache;
81
+ }
82
+
83
+ return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
84
+ }
85
+
86
+ async function handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
87
+ const { method, params, id } = request;
88
+
89
+ switch (method) {
90
+ case 'initialize': {
91
+ return {
92
+ jsonrpc: '2.0',
93
+ id: id ?? null,
94
+ result: {
95
+ protocolVersion: '2024-11-05',
96
+ capabilities: { tools: {} },
97
+ serverInfo: {
98
+ name: getConfig().toolPrefix || 'massu',
99
+ version: options.serverInfoVersion,
100
+ },
101
+ },
102
+ };
103
+ }
104
+
105
+ case 'notifications/initialized': {
106
+ return { jsonrpc: '2.0', id: id ?? null, result: {} };
107
+ }
108
+
109
+ case 'tools/list': {
110
+ const tools = getToolDefinitions();
111
+ return { jsonrpc: '2.0', id: id ?? null, result: { tools } };
112
+ }
113
+
114
+ case 'tools/call': {
115
+ const toolName = (params as { name?: string })?.name ?? '';
116
+ const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
117
+
118
+ try {
119
+ const { dataDb, codegraphDb } = resolveDbsForTool(toolName);
120
+ const result = await handleToolCall(toolName, toolArgs, dataDb, codegraphDb);
121
+ return { jsonrpc: '2.0', id: id ?? null, result };
122
+ } catch (err) {
123
+ if (err instanceof CodegraphDbNotInitializedError) {
124
+ return {
125
+ jsonrpc: '2.0',
126
+ id: id ?? null,
127
+ error: {
128
+ code: -32001,
129
+ message: 'Tool requires CodeGraph database which is not initialized for this repo',
130
+ data: {
131
+ remedy: 'npx @colbymchenry/codegraph@0.7.4 init . && npx @colbymchenry/codegraph@0.7.4 index .',
132
+ codegraphDbPath: err.dbPath,
133
+ tool: toolName,
134
+ },
135
+ },
136
+ };
137
+ }
138
+ if (err instanceof UnknownToolError) {
139
+ return {
140
+ jsonrpc: '2.0',
141
+ id: id ?? null,
142
+ error: {
143
+ code: -32602,
144
+ message: `Unknown tool: ${err.toolName}`,
145
+ data: {
146
+ remedy: 'Tool not registered in TOOL_DB_NEEDS manifest. See packages/core/src/tool-db-needs.ts.',
147
+ tool: toolName,
148
+ },
149
+ },
150
+ };
151
+ }
152
+ throw err;
153
+ }
154
+ }
155
+
156
+ case 'ping': {
157
+ return { jsonrpc: '2.0', id: id ?? null, result: {} };
158
+ }
159
+
160
+ default: {
161
+ return {
162
+ jsonrpc: '2.0',
163
+ id: id ?? null,
164
+ error: { code: -32601, message: `Method not found: ${method}` },
165
+ };
166
+ }
167
+ }
168
+ }
169
+
170
+ async function processLine(line: string): Promise<ProcessLineResult | null> {
171
+ const trimmed = line.trim();
172
+ if (!trimmed) return null;
173
+
174
+ let request: JsonRpcRequest;
175
+ try {
176
+ request = JSON.parse(trimmed) as JsonRpcRequest;
177
+ } catch (parseError) {
178
+ // JSON-RPC §5.1: parse failure → -32700 + id:null (no id is extractable).
179
+ return {
180
+ response: {
181
+ jsonrpc: '2.0',
182
+ id: null,
183
+ error: {
184
+ code: -32700,
185
+ message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
186
+ },
187
+ },
188
+ emit: true,
189
+ };
190
+ }
191
+
192
+ try {
193
+ const response = await handleRequest(request);
194
+ // Notifications (no id) MUST NOT receive a response per JSON-RPC §4.1.
195
+ return { response, emit: request.id !== undefined };
196
+ } catch (error) {
197
+ // Request-processing failure: -32603 with the request id preserved.
198
+ // Specific subclasses (-32001/-32602) are handled inside tools/call.
199
+ return {
200
+ response: {
201
+ jsonrpc: '2.0',
202
+ id: request.id ?? null,
203
+ error: {
204
+ code: -32603,
205
+ message: `Internal error: ${error instanceof Error ? error.message : String(error)}`,
206
+ },
207
+ },
208
+ emit: true,
209
+ };
210
+ }
211
+ }
212
+
213
+ function closeCachedDbs(): void {
214
+ if (codegraphDbCache) {
215
+ codegraphDbCache.close();
216
+ codegraphDbCache = null;
217
+ }
218
+ if (dataDbCache) {
219
+ dataDbCache.close();
220
+ dataDbCache = null;
221
+ }
222
+ }
223
+
224
+ return { handleRequest, processLine, closeCachedDbs };
225
+ }
package/src/server.ts CHANGED
@@ -14,13 +14,9 @@
14
14
  import { readFileSync } from 'fs';
15
15
  import { resolve, dirname } from 'path';
16
16
  import { fileURLToPath } from 'url';
17
- import type Database from 'better-sqlite3';
18
- import { getCodeGraphDb, getDataDb, CodegraphDbNotInitializedError } from './db.ts';
19
- import { getConfig, getResolvedPaths } from './config.ts';
20
- import { getToolDefinitions, handleToolCall } from './tools.ts';
21
17
  import { getMemoryDb, pruneOldConversationTurns, pruneOldObservations } from './memory-db.ts';
22
18
  import { getCurrentTier } from './license.ts';
23
- import { getToolDbNeeds, UnknownToolError, type DbNeed } from './tool-db-needs.ts';
19
+ import { createDispatcher } from './server-dispatch.ts';
24
20
 
25
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
26
22
  const PKG_VERSION = (() => {
@@ -32,167 +28,7 @@ const PKG_VERSION = (() => {
32
28
  }
33
29
  })();
34
30
 
35
- interface JsonRpcRequest {
36
- jsonrpc: '2.0';
37
- id?: number | string;
38
- method: string;
39
- params?: Record<string, unknown>;
40
- }
41
-
42
- interface JsonRpcResponse {
43
- jsonrpc: '2.0';
44
- id: number | string | null;
45
- result?: unknown;
46
- error?: { code: number; message: string; data?: unknown };
47
- }
48
-
49
- // === Server state: lazy per-tool DB resolution ===
50
- //
51
- // Per plan-1.6.2-server-lazy-db-deps: DBs are opened ONLY when the
52
- // currently-dispatched tool declares it needs them in `TOOL_DB_NEEDS`.
53
- // Connections are cached at module scope so subsequent tool calls reuse
54
- // the open handle without re-opening (CodeGraph is read-only — safe to
55
- // share; Data DB has WAL journal — single-writer is fine).
56
- //
57
- // PRIOR DESIGN (eliminated 2026-05-10): `getDb()` eagerly opened BOTH
58
- // CodeGraph + Data on every `tools/call`, even for memory/audit/knowledge
59
- // tools that don't need codegraph. Missing `.codegraph/codegraph.db`
60
- // broke ALL tools. See `docs/plans/2026-05-10-server-lazy-db-deps.md`.
61
-
62
- let codegraphDbCache: Database.Database | null = null;
63
- let dataDbCache: Database.Database | null = null;
64
-
65
- /**
66
- * Resolve the SQLite connections a tool needs, opening cached singletons
67
- * lazily. Memory DB and Knowledge DB are opened per-call by their routed
68
- * handlers (existing pattern in tools.ts) — only CodeGraph + Data are
69
- * cached here.
70
- *
71
- * @throws {CodegraphDbNotInitializedError} when tool needs codegraph but
72
- * `.codegraph/codegraph.db` is missing. Caller (handleRequest) catches
73
- * and translates to a structured `-32001` JSON-RPC error.
74
- */
75
- function resolveDbsForTool(toolName: string): {
76
- needs: readonly DbNeed[];
77
- dataDb?: Database.Database;
78
- codegraphDb?: Database.Database;
79
- } {
80
- const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
81
-
82
- let dataDbResolved: Database.Database | undefined;
83
- let codegraphDbResolved: Database.Database | undefined;
84
-
85
- if (needs.includes('data')) {
86
- if (!dataDbCache) dataDbCache = getDataDb();
87
- dataDbResolved = dataDbCache;
88
- }
89
-
90
- if (needs.includes('codegraph')) {
91
- if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb(); // throws CodegraphDbNotInitializedError on missing
92
- codegraphDbResolved = codegraphDbCache;
93
- }
94
-
95
- return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
96
- }
97
-
98
- async function handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
99
- const { method, params, id } = request;
100
-
101
- switch (method) {
102
- case 'initialize': {
103
- return {
104
- jsonrpc: '2.0',
105
- id: id ?? null,
106
- result: {
107
- protocolVersion: '2024-11-05',
108
- capabilities: {
109
- tools: {},
110
- },
111
- serverInfo: {
112
- name: getConfig().toolPrefix || 'massu',
113
- version: PKG_VERSION,
114
- },
115
- },
116
- };
117
- }
118
-
119
- case 'notifications/initialized': {
120
- // Client acknowledges initialization - no response needed for notifications
121
- return { jsonrpc: '2.0', id: id ?? null, result: {} };
122
- }
123
-
124
- case 'tools/list': {
125
- const tools = getToolDefinitions();
126
- return {
127
- jsonrpc: '2.0',
128
- id: id ?? null,
129
- result: { tools },
130
- };
131
- }
132
-
133
- case 'tools/call': {
134
- const toolName = (params as { name: string })?.name;
135
- const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
136
-
137
- // Lazy per-tool DB resolution. Throws if tool needs codegraph and
138
- // .codegraph/codegraph.db is missing; caught below and translated
139
- // to a structured -32001 error preserving the request id.
140
- try {
141
- const { dataDb: lDb, codegraphDb: cgDb } = resolveDbsForTool(toolName);
142
- const result = await handleToolCall(toolName, toolArgs, lDb, cgDb);
143
- return {
144
- jsonrpc: '2.0',
145
- id: id ?? null,
146
- result,
147
- };
148
- } catch (err) {
149
- if (err instanceof CodegraphDbNotInitializedError) {
150
- return {
151
- jsonrpc: '2.0',
152
- id: id ?? null,
153
- error: {
154
- code: -32001,
155
- message: `Tool requires CodeGraph database which is not initialized for this repo`,
156
- data: {
157
- remedy: 'npx @colbymchenry/codegraph@0.7.4 init . && npx @colbymchenry/codegraph@0.7.4 index .',
158
- codegraphDbPath: err.dbPath,
159
- tool: toolName,
160
- },
161
- },
162
- };
163
- }
164
- if (err instanceof UnknownToolError) {
165
- return {
166
- jsonrpc: '2.0',
167
- id: id ?? null,
168
- error: {
169
- code: -32602,
170
- message: `Unknown tool: ${err.toolName}`,
171
- data: {
172
- remedy: 'Tool not registered in TOOL_DB_NEEDS manifest. See packages/core/src/tool-db-needs.ts.',
173
- tool: toolName,
174
- },
175
- },
176
- };
177
- }
178
- // Other errors propagate to the outer catch in the stdio handler
179
- throw err;
180
- }
181
- }
182
-
183
- case 'ping': {
184
- return { jsonrpc: '2.0', id: id ?? null, result: {} };
185
- }
186
-
187
- default: {
188
- return {
189
- jsonrpc: '2.0',
190
- id: id ?? null,
191
- error: { code: -32601, message: `Method not found: ${method}` },
192
- };
193
- }
194
- }
195
- }
31
+ const dispatcher = createDispatcher({ serverInfoVersion: PKG_VERSION });
196
32
 
197
33
  // === Startup: prune stale memory data (non-blocking) ===
198
34
 
@@ -244,62 +80,20 @@ process.stdin.on('data', async (chunk: string) => {
244
80
  // Process complete messages (newline-delimited JSON-RPC)
245
81
  let newlineIndex: number;
246
82
  while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
247
- const line = buffer.slice(0, newlineIndex).trim();
83
+ const line = buffer.slice(0, newlineIndex);
248
84
  buffer = buffer.slice(newlineIndex + 1);
249
85
 
250
- if (!line) continue;
251
-
252
- // Two-phase error handling: separate JSON-parse failures (genuine
253
- // -32700) from request-processing failures (-32603 Internal error,
254
- // preserving the request id when parseable).
255
- let request: JsonRpcRequest | null = null;
256
- try {
257
- request = JSON.parse(line) as JsonRpcRequest;
258
- } catch (parseError) {
259
- // Real JSON parse failure — -32700 per JSON-RPC §5.1, id MUST be null
260
- // because we couldn't extract one.
261
- const errorResponse: JsonRpcResponse = {
262
- jsonrpc: '2.0',
263
- id: null,
264
- error: {
265
- code: -32700,
266
- message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
267
- },
268
- };
269
- process.stdout.write(JSON.stringify(errorResponse) + '\n');
270
- continue;
271
- }
272
-
273
- try {
274
- const response = await handleRequest(request);
275
- // Don't send responses for notifications (no id)
276
- if (request.id !== undefined) {
277
- const responseStr = JSON.stringify(response);
278
- process.stdout.write(responseStr + '\n');
279
- }
280
- } catch (error) {
281
- // Request-processing failure — propagate the request id (not null).
282
- // -32603 Internal error per JSON-RPC §5.1. Specific subclasses
283
- // (codegraph-not-init, unknown-tool) are caught earlier in the
284
- // tools/call handler and translated to structured -32001/-32602.
285
- const errorResponse: JsonRpcResponse = {
286
- jsonrpc: '2.0',
287
- id: request.id ?? null,
288
- error: {
289
- code: -32603,
290
- message: `Internal error: ${error instanceof Error ? error.message : String(error)}`,
291
- },
292
- };
293
- process.stdout.write(JSON.stringify(errorResponse) + '\n');
86
+ const result = await dispatcher.processLine(line);
87
+ if (result && result.emit) {
88
+ process.stdout.write(JSON.stringify(result.response) + '\n');
294
89
  }
295
90
  }
296
91
  });
297
92
 
298
93
  process.stdin.on('end', () => {
299
- // Clean up cached DB connections (Memory + Knowledge are per-call,
300
- // already closed in their routing branches).
301
- if (codegraphDbCache) codegraphDbCache.close();
302
- if (dataDbCache) dataDbCache.close();
94
+ // Close cached CodeGraph + Data connections. Memory + Knowledge are
95
+ // per-call (closed inside their routing branches in tools.ts).
96
+ dispatcher.closeCachedDbs();
303
97
  process.exit(0);
304
98
  });
305
99