@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 +150 -132
- package/package.json +1 -1
- package/src/security/registry-pubkey.generated.ts +1 -1
- package/src/server-dispatch.ts +225 -0
- package/src/server.ts +9 -215
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
|
-
|
|
27764
|
-
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
27768
|
-
|
|
27769
|
-
|
|
27770
|
-
|
|
27771
|
-
|
|
27772
|
-
|
|
27773
|
-
|
|
27774
|
-
|
|
27775
|
-
|
|
27776
|
-
|
|
27777
|
-
|
|
27778
|
-
|
|
27779
|
-
|
|
27780
|
-
|
|
27781
|
-
|
|
27782
|
-
|
|
27783
|
-
|
|
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
|
-
}
|
|
27823
|
-
|
|
27824
|
-
|
|
27825
|
-
|
|
27826
|
-
|
|
27827
|
-
|
|
27828
|
-
|
|
27829
|
-
|
|
27830
|
-
|
|
27831
|
-
|
|
27832
|
-
|
|
27833
|
-
|
|
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
|
-
|
|
27839
|
-
|
|
27840
|
-
|
|
27841
|
-
|
|
27842
|
-
|
|
27843
|
-
|
|
27844
|
-
|
|
27845
|
-
|
|
27846
|
-
|
|
27847
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27856
|
-
|
|
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
|
-
|
|
27875
|
+
try {
|
|
27876
|
+
const response = await handleRequest(request);
|
|
27877
|
+
return { response, emit: request.id !== void 0 };
|
|
27878
|
+
} catch (error) {
|
|
27859
27879
|
return {
|
|
27860
|
-
|
|
27861
|
-
|
|
27862
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
27975
|
+
const line = buffer.slice(0, newlineIndex);
|
|
27928
27976
|
buffer = buffer.slice(newlineIndex + 1);
|
|
27929
|
-
|
|
27930
|
-
|
|
27931
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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 {
|
|
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
|
-
|
|
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)
|
|
83
|
+
const line = buffer.slice(0, newlineIndex);
|
|
248
84
|
buffer = buffer.slice(newlineIndex + 1);
|
|
249
85
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
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
|
|