@zokizuan/satori-mcp 6.2.0 → 6.3.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.
- package/README.md +11 -2
- package/dist/core/canonical-symbol-identity.js +33 -5
- package/dist/core/manage-indexing-handlers.js +7 -0
- package/dist/core/manage-types.d.ts +1 -1
- package/dist/core/relationship-backed-call-graph.js +12 -13
- package/dist/core/search-debug-helpers.js +1 -0
- package/dist/core/search-response-helpers.d.ts +3 -2
- package/dist/core/search-response-helpers.js +5 -3
- package/dist/core/search-types.d.ts +1 -0
- package/dist/core/sync.d.ts +13 -0
- package/dist/core/sync.js +122 -51
- package/dist/index.js +31 -4
- package/dist/server/provider-runtime.d.ts +3 -0
- package/dist/server/provider-runtime.js +23 -3
- package/dist/server/shared-runtime-client.d.ts +11 -0
- package/dist/server/shared-runtime-client.js +360 -0
- package/dist/server/shared-runtime-host.d.ts +24 -0
- package/dist/server/shared-runtime-host.js +322 -0
- package/dist/server/shared-runtime-identity.d.ts +51 -0
- package/dist/server/shared-runtime-identity.js +160 -0
- package/dist/server/shared-runtime-lifecycle.d.ts +33 -0
- package/dist/server/shared-runtime-lifecycle.js +259 -0
- package/dist/server/shared-runtime-transport.d.ts +28 -0
- package/dist/server/shared-runtime-transport.js +158 -0
- package/dist/server/shared-runtime.d.ts +83 -0
- package/dist/server/shared-runtime.js +300 -0
- package/dist/server/start-server.d.ts +9 -32
- package/dist/server/start-server.js +15 -142
- package/package.json +2 -2
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { withSourceMeasurementOperation } from "@zokizuan/satori-core";
|
|
5
|
+
import { CapabilityResolver } from "../core/capabilities.js";
|
|
6
|
+
import { CallGraphSidecarManager } from "../core/call-graph.js";
|
|
7
|
+
import { SearchContinuationCoordinator, ToolHandlers, } from "../core/handlers.js";
|
|
8
|
+
import { MutationLeaseCoordinator } from "../core/mutation-lease.js";
|
|
9
|
+
import { RuntimeOwnerRegistry, buildRuntimeOwnerIdentityFromConfig, } from "../core/runtime-owner.js";
|
|
10
|
+
import { SnapshotManager } from "../core/snapshot.js";
|
|
11
|
+
import { SyncManager } from "../core/sync.js";
|
|
12
|
+
import { getMcpToolList, toolRegistry } from "../tools/registry.js";
|
|
13
|
+
import { createLocalOnlyContext, ProviderRuntime } from "./provider-runtime.js";
|
|
14
|
+
import { SHARED_RUNTIME_MAX_PENDING_REQUESTS } from "./shared-runtime-identity.js";
|
|
15
|
+
function errorMessage(error) {
|
|
16
|
+
return error instanceof Error ? error.message : String(error);
|
|
17
|
+
}
|
|
18
|
+
function isMissingProviderConfigIssue(value) {
|
|
19
|
+
return "code" in value && value.code === "MISSING_PROVIDER_CONFIG";
|
|
20
|
+
}
|
|
21
|
+
class SessionProviderRuntime {
|
|
22
|
+
constructor(providerRuntime, continuationCoordinator, mutationLeaseCoordinator, callGraphManager) {
|
|
23
|
+
this.providerRuntime = providerRuntime;
|
|
24
|
+
this.continuationCoordinator = continuationCoordinator;
|
|
25
|
+
this.mutationLeaseCoordinator = mutationLeaseCoordinator;
|
|
26
|
+
this.callGraphManager = callGraphManager;
|
|
27
|
+
this.contexts = new Map();
|
|
28
|
+
this.handlers = new Set();
|
|
29
|
+
}
|
|
30
|
+
async requireToolContext(operation) {
|
|
31
|
+
const shared = await this.providerRuntime.requireToolContext(operation);
|
|
32
|
+
if (isMissingProviderConfigIssue(shared)) {
|
|
33
|
+
return shared;
|
|
34
|
+
}
|
|
35
|
+
const existing = this.contexts.get(shared);
|
|
36
|
+
if (existing) {
|
|
37
|
+
return existing;
|
|
38
|
+
}
|
|
39
|
+
const toolHandlers = new ToolHandlers(shared.context, shared.snapshotManager, shared.syncManager, shared.runtimeFingerprint, shared.capabilities, () => Date.now(), this.callGraphManager, shared.reranker, undefined, undefined, shared.runtimeOwnerGate, this.mutationLeaseCoordinator, this.continuationCoordinator);
|
|
40
|
+
const sessionContext = {
|
|
41
|
+
...shared,
|
|
42
|
+
toolHandlers,
|
|
43
|
+
providerRuntime: this,
|
|
44
|
+
};
|
|
45
|
+
this.contexts.set(shared, sessionContext);
|
|
46
|
+
this.handlers.add(toolHandlers);
|
|
47
|
+
return sessionContext;
|
|
48
|
+
}
|
|
49
|
+
release() {
|
|
50
|
+
for (const handler of this.handlers) {
|
|
51
|
+
handler.releaseSearchContinuationOwnership();
|
|
52
|
+
}
|
|
53
|
+
this.handlers.clear();
|
|
54
|
+
this.contexts.clear();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export class SharedRuntimeHost {
|
|
58
|
+
constructor(config, runtimeFingerprint, runMode) {
|
|
59
|
+
this.config = config;
|
|
60
|
+
this.runtimeFingerprint = runtimeFingerprint;
|
|
61
|
+
this.runMode = runMode;
|
|
62
|
+
this.activeSessions = 0;
|
|
63
|
+
this.activeOperations = 0;
|
|
64
|
+
this.shutdownStarted = false;
|
|
65
|
+
this.activityListeners = new Set();
|
|
66
|
+
this.capabilities = new CapabilityResolver(config);
|
|
67
|
+
this.readFileMaxLines = Math.max(1, config.readFileMaxLines ?? 1000);
|
|
68
|
+
this.watchSyncEnabled = config.watchSyncEnabled === true;
|
|
69
|
+
this.watchDebounceMs = Math.max(1, config.watchDebounceMs ?? 5000);
|
|
70
|
+
console.log(`[FINGERPRINT] Runtime index fingerprint: ${JSON.stringify(runtimeFingerprint)}`);
|
|
71
|
+
this.runtimeOwnerRegistry = new RuntimeOwnerRegistry({
|
|
72
|
+
identity: buildRuntimeOwnerIdentityFromConfig({
|
|
73
|
+
config,
|
|
74
|
+
runtimeFingerprint,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
this.runtimeOwnerRegistry.registerCurrentOwner();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.warn("[RUNTIME-OWNER] Failed to register current Satori runtime owner; "
|
|
82
|
+
+ `index mutations will fail closed until the owner registry is writable: ${errorMessage(error)}`);
|
|
83
|
+
}
|
|
84
|
+
this.mutationLeaseCoordinator = new MutationLeaseCoordinator();
|
|
85
|
+
this.snapshotManager = new SnapshotManager(runtimeFingerprint);
|
|
86
|
+
this.callGraphManager = new CallGraphSidecarManager(runtimeFingerprint);
|
|
87
|
+
this.localContext = createLocalOnlyContext(config, this.mutationLeaseCoordinator);
|
|
88
|
+
this.localSyncManager = new SyncManager(this.localContext, this.snapshotManager, {
|
|
89
|
+
watchEnabled: this.watchSyncEnabled,
|
|
90
|
+
watchDebounceMs: this.watchDebounceMs,
|
|
91
|
+
mutationLeaseCoordinator: this.mutationLeaseCoordinator,
|
|
92
|
+
});
|
|
93
|
+
this.recoveryHandlers = new ToolHandlers(this.localContext, this.snapshotManager, this.localSyncManager, runtimeFingerprint, this.capabilities, () => Date.now(), this.callGraphManager, null, undefined, undefined, this.runtimeOwnerRegistry, this.mutationLeaseCoordinator, new SearchContinuationCoordinator());
|
|
94
|
+
this.providerRuntime = new ProviderRuntime({
|
|
95
|
+
config,
|
|
96
|
+
snapshotManager: this.snapshotManager,
|
|
97
|
+
runtimeFingerprint,
|
|
98
|
+
capabilities: this.capabilities,
|
|
99
|
+
readFileMaxLines: this.readFileMaxLines,
|
|
100
|
+
watchSyncEnabled: this.watchSyncEnabled,
|
|
101
|
+
watchDebounceMs: this.watchDebounceMs,
|
|
102
|
+
startSyncLifecycle: runMode === "mcp" || runMode === "host",
|
|
103
|
+
callGraphManager: this.callGraphManager,
|
|
104
|
+
runtimeOwnerGate: this.runtimeOwnerRegistry,
|
|
105
|
+
mutationLeaseCoordinator: this.mutationLeaseCoordinator,
|
|
106
|
+
onLifecycleActivityChanged: () => this.notifyActivityChanged(),
|
|
107
|
+
});
|
|
108
|
+
this.snapshotManager.loadCodebaseSnapshot();
|
|
109
|
+
}
|
|
110
|
+
createSession() {
|
|
111
|
+
if (this.shutdownStarted) {
|
|
112
|
+
throw new Error("Shared Satori runtime host is shutting down.");
|
|
113
|
+
}
|
|
114
|
+
return new McpSession(this);
|
|
115
|
+
}
|
|
116
|
+
createSessionResources(continuationCoordinator) {
|
|
117
|
+
const localHandlers = new ToolHandlers(this.localContext, this.snapshotManager, this.localSyncManager, this.runtimeFingerprint, this.capabilities, () => Date.now(), this.callGraphManager, null, undefined, undefined, this.runtimeOwnerRegistry, this.mutationLeaseCoordinator, continuationCoordinator);
|
|
118
|
+
const providerRuntime = new SessionProviderRuntime(this.providerRuntime, continuationCoordinator, this.mutationLeaseCoordinator, this.callGraphManager);
|
|
119
|
+
return {
|
|
120
|
+
localHandlers,
|
|
121
|
+
providerRuntime,
|
|
122
|
+
toolContext: {
|
|
123
|
+
context: this.localContext,
|
|
124
|
+
snapshotManager: this.snapshotManager,
|
|
125
|
+
syncManager: this.localSyncManager,
|
|
126
|
+
capabilities: this.capabilities,
|
|
127
|
+
reranker: null,
|
|
128
|
+
runtimeFingerprint: this.runtimeFingerprint,
|
|
129
|
+
toolHandlers: localHandlers,
|
|
130
|
+
readFileMaxLines: this.readFileMaxLines,
|
|
131
|
+
runtimeOwnerGate: this.runtimeOwnerRegistry,
|
|
132
|
+
providerRuntime,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
registerSession() {
|
|
137
|
+
this.activeSessions += 1;
|
|
138
|
+
this.notifyActivityChanged();
|
|
139
|
+
}
|
|
140
|
+
unregisterSession() {
|
|
141
|
+
this.activeSessions = Math.max(0, this.activeSessions - 1);
|
|
142
|
+
this.notifyActivityChanged();
|
|
143
|
+
}
|
|
144
|
+
beginOperation() {
|
|
145
|
+
this.activeOperations += 1;
|
|
146
|
+
this.notifyActivityChanged();
|
|
147
|
+
}
|
|
148
|
+
endOperation() {
|
|
149
|
+
this.activeOperations = Math.max(0, this.activeOperations - 1);
|
|
150
|
+
this.notifyActivityChanged();
|
|
151
|
+
}
|
|
152
|
+
getActivity() {
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
sessions: this.activeSessions,
|
|
155
|
+
operations: this.activeOperations
|
|
156
|
+
+ this.providerRuntime.getActiveLifecycleOperationCount(),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
getProviderRuntime() {
|
|
160
|
+
return this.providerRuntime;
|
|
161
|
+
}
|
|
162
|
+
subscribeActivity(listener) {
|
|
163
|
+
this.activityListeners.add(listener);
|
|
164
|
+
return () => this.activityListeners.delete(listener);
|
|
165
|
+
}
|
|
166
|
+
notifyActivityChanged() {
|
|
167
|
+
for (const listener of this.activityListeners) {
|
|
168
|
+
listener();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async recoverInterruptedIndexingAtStartup() {
|
|
172
|
+
await this.recoveryHandlers.recoverInterruptedIndexingAtStartup();
|
|
173
|
+
}
|
|
174
|
+
async shutdown() {
|
|
175
|
+
if (this.shutdownStarted)
|
|
176
|
+
return;
|
|
177
|
+
this.shutdownStarted = true;
|
|
178
|
+
await this.localSyncManager.stopAndDrainLifecycle();
|
|
179
|
+
await this.providerRuntime.shutdown();
|
|
180
|
+
this.recoveryHandlers.releaseSearchContinuationOwnership();
|
|
181
|
+
this.runtimeOwnerRegistry.unregisterCurrentOwner();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export class McpSession {
|
|
185
|
+
constructor(host) {
|
|
186
|
+
this.host = host;
|
|
187
|
+
this.continuationCoordinator = new SearchContinuationCoordinator();
|
|
188
|
+
this.activeToolCalls = 0;
|
|
189
|
+
this.connected = false;
|
|
190
|
+
this.closed = false;
|
|
191
|
+
this.resourcesReleased = false;
|
|
192
|
+
this.resourceReleasePromise = null;
|
|
193
|
+
this.resolveResourceRelease = null;
|
|
194
|
+
this.keepAliveTimer = null;
|
|
195
|
+
this.server = new Server({
|
|
196
|
+
name: host.config.name,
|
|
197
|
+
version: host.config.version,
|
|
198
|
+
}, {
|
|
199
|
+
capabilities: {
|
|
200
|
+
tools: {},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
this.resources = host.createSessionResources(this.continuationCoordinator);
|
|
204
|
+
this.setupTools();
|
|
205
|
+
}
|
|
206
|
+
setupTools() {
|
|
207
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
208
|
+
tools: getMcpToolList(this.resources.toolContext),
|
|
209
|
+
}));
|
|
210
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
211
|
+
const { name, arguments: args } = request.params;
|
|
212
|
+
const tool = toolRegistry[name];
|
|
213
|
+
if (!tool) {
|
|
214
|
+
return {
|
|
215
|
+
content: [{
|
|
216
|
+
type: "text",
|
|
217
|
+
text: `Unknown tool: ${name}. Supported tools: ${Object.keys(toolRegistry).join(", ")}`,
|
|
218
|
+
}],
|
|
219
|
+
isError: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (this.activeToolCalls >= SHARED_RUNTIME_MAX_PENDING_REQUESTS) {
|
|
223
|
+
return {
|
|
224
|
+
content: [{
|
|
225
|
+
type: "text",
|
|
226
|
+
text: `This Satori session already has ${SHARED_RUNTIME_MAX_PENDING_REQUESTS} active tool calls. Wait for one to finish, then retry.`,
|
|
227
|
+
}],
|
|
228
|
+
isError: true,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
this.activeToolCalls += 1;
|
|
232
|
+
this.host.beginOperation();
|
|
233
|
+
try {
|
|
234
|
+
return await withSourceMeasurementOperation({ operation: name }, () => tool.execute(args || {}, this.resources.toolContext));
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
this.activeToolCalls -= 1;
|
|
238
|
+
this.host.endOperation();
|
|
239
|
+
this.releaseResourcesIfIdle();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async connect(transport, resumeInput) {
|
|
244
|
+
if (this.connected) {
|
|
245
|
+
throw new Error("MCP session is already connected.");
|
|
246
|
+
}
|
|
247
|
+
this.connected = true;
|
|
248
|
+
this.host.registerSession();
|
|
249
|
+
try {
|
|
250
|
+
await this.server.connect(transport);
|
|
251
|
+
resumeInput?.resume();
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
this.host.unregisterSession();
|
|
255
|
+
this.connected = false;
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async connectStdio(input, output) {
|
|
260
|
+
const transportInput = input ?? process.stdin;
|
|
261
|
+
const transportOutput = output ?? process.stdout;
|
|
262
|
+
await this.connect(new StdioServerTransport(transportInput, transportOutput), transportInput);
|
|
263
|
+
this.keepAliveTimer = setInterval(() => {
|
|
264
|
+
// Keep direct stdio sessions alive when provider lifecycle is lazy.
|
|
265
|
+
}, 60 * 60 * 1000);
|
|
266
|
+
}
|
|
267
|
+
async shutdown() {
|
|
268
|
+
if (this.closed)
|
|
269
|
+
return;
|
|
270
|
+
this.closed = true;
|
|
271
|
+
if (this.keepAliveTimer) {
|
|
272
|
+
clearInterval(this.keepAliveTimer);
|
|
273
|
+
this.keepAliveTimer = null;
|
|
274
|
+
}
|
|
275
|
+
if (this.connected) {
|
|
276
|
+
this.connected = false;
|
|
277
|
+
this.host.unregisterSession();
|
|
278
|
+
}
|
|
279
|
+
await this.server.close().catch(() => undefined);
|
|
280
|
+
if (this.activeToolCalls > 0) {
|
|
281
|
+
this.resourceReleasePromise = new Promise((resolve) => {
|
|
282
|
+
this.resolveResourceRelease = resolve;
|
|
283
|
+
});
|
|
284
|
+
await this.resourceReleasePromise;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
this.releaseResourcesIfIdle();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
releaseResourcesIfIdle() {
|
|
291
|
+
if (!this.closed || this.activeToolCalls > 0 || this.resourcesReleased)
|
|
292
|
+
return;
|
|
293
|
+
this.resourcesReleased = true;
|
|
294
|
+
this.resources.localHandlers.releaseSearchContinuationOwnership();
|
|
295
|
+
this.resources.providerRuntime.release();
|
|
296
|
+
this.resolveResourceRelease?.();
|
|
297
|
+
this.resolveResourceRelease = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
//# sourceMappingURL=shared-runtime.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Readable, Writable } from "node:stream";
|
|
2
|
-
import {
|
|
3
|
-
export type ServerRunMode
|
|
2
|
+
import { ServerRunMode, SharedRuntimeHost } from "./shared-runtime.js";
|
|
3
|
+
export type { ServerRunMode } from "./shared-runtime.js";
|
|
4
4
|
export interface StartMcpServerOptions {
|
|
5
5
|
runMode?: ServerRunMode;
|
|
6
6
|
protocolStdin?: Readable;
|
|
@@ -19,38 +19,15 @@ interface StartupLifecycleDependencies {
|
|
|
19
19
|
* pass bumps mutation generation and invalidates warm prepared-read observations.
|
|
20
20
|
*/
|
|
21
21
|
export declare function runPostConnectStartupLifecycle(runMode: ServerRunMode, dependencies: StartupLifecycleDependencies): Promise<void>;
|
|
22
|
-
declare class ContextMcpServer {
|
|
23
|
-
private
|
|
24
|
-
private
|
|
25
|
-
private
|
|
26
|
-
private
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
private runtimeFingerprint;
|
|
30
|
-
private readFileMaxLines;
|
|
31
|
-
private watchSyncEnabled;
|
|
32
|
-
private watchDebounceMs;
|
|
33
|
-
private callGraphManager;
|
|
34
|
-
private providerRuntime;
|
|
35
|
-
private runtimeOwnerRegistry;
|
|
36
|
-
private mutationLeaseCoordinator;
|
|
37
|
-
private searchContinuationCoordinator;
|
|
38
|
-
private runMode;
|
|
39
|
-
private protocolStdin?;
|
|
40
|
-
private protocolStdout?;
|
|
41
|
-
private keepAliveTimer;
|
|
42
|
-
constructor(config: ContextMcpConfig, runtimeFingerprint: IndexFingerprint, runMode: ServerRunMode, protocolStdout?: Writable, protocolStdin?: Readable);
|
|
43
|
-
private getCliTransportStdout;
|
|
44
|
-
private getToolContext;
|
|
45
|
-
/**
|
|
46
|
-
* Verify interrupted indexing snapshots via the fenced recovery path.
|
|
47
|
-
* Must not publish lifecycle transitions without a mutation lease.
|
|
48
|
-
*/
|
|
49
|
-
private verifyCloudState;
|
|
50
|
-
private setupTools;
|
|
22
|
+
export declare class ContextMcpServer {
|
|
23
|
+
private readonly runMode;
|
|
24
|
+
private readonly protocolStdout?;
|
|
25
|
+
private readonly protocolStdin?;
|
|
26
|
+
private readonly host;
|
|
27
|
+
private readonly session;
|
|
28
|
+
constructor(config: ConstructorParameters<typeof SharedRuntimeHost>[0], runtimeFingerprint: ConstructorParameters<typeof SharedRuntimeHost>[1], runMode: ServerRunMode, protocolStdout?: Writable | undefined, protocolStdin?: Readable | undefined);
|
|
51
29
|
start(): Promise<void>;
|
|
52
30
|
shutdown(): Promise<void>;
|
|
53
31
|
}
|
|
54
32
|
export declare function startMcpServerFromEnv(options?: StartMcpServerOptions): Promise<ContextMcpServer | null>;
|
|
55
|
-
export {};
|
|
56
33
|
//# sourceMappingURL=start-server.d.ts.map
|
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
1
|
import fs from "node:fs";
|
|
5
2
|
import os from "node:os";
|
|
6
3
|
import path from "node:path";
|
|
7
|
-
import { withSourceMeasurementOperation } from "@zokizuan/satori-core";
|
|
8
4
|
import { createMcpConfig, logConfigurationSummary, resolveMcpRuntimeBootstrap, showHelpMessage, } from "../config.js";
|
|
9
|
-
import {
|
|
10
|
-
import { SnapshotManager } from "../core/snapshot.js";
|
|
11
|
-
import { SyncManager } from "../core/sync.js";
|
|
12
|
-
import { SearchContinuationCoordinator, ToolHandlers, } from "../core/handlers.js";
|
|
13
|
-
import { CallGraphSidecarManager } from "../core/call-graph.js";
|
|
14
|
-
import { RuntimeOwnerRegistry, buildRuntimeOwnerIdentityFromConfig, } from "../core/runtime-owner.js";
|
|
15
|
-
import { MutationLeaseCoordinator } from "../core/mutation-lease.js";
|
|
16
|
-
import { getMcpToolList, toolRegistry } from "../tools/registry.js";
|
|
17
|
-
import { createLocalOnlyContext, ProviderRuntime } from "./provider-runtime.js";
|
|
5
|
+
import { SharedRuntimeHost, } from "./shared-runtime.js";
|
|
18
6
|
function errorMessage(error) {
|
|
19
7
|
return error instanceof Error ? error.message : String(error);
|
|
20
8
|
}
|
|
@@ -66,134 +54,26 @@ function migrateLegacyStateDir() {
|
|
|
66
54
|
console.error(`[MIGRATION] Failed to migrate '${legacyDir}' -> '${newDir}':`, errorMessage(copyError));
|
|
67
55
|
}
|
|
68
56
|
}
|
|
69
|
-
class ContextMcpServer {
|
|
57
|
+
export class ContextMcpServer {
|
|
70
58
|
constructor(config, runtimeFingerprint, runMode, protocolStdout, protocolStdin) {
|
|
71
|
-
this.keepAliveTimer = null;
|
|
72
59
|
this.runMode = runMode;
|
|
73
|
-
this.protocolStdin = protocolStdin;
|
|
74
60
|
this.protocolStdout = protocolStdout;
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}, {
|
|
79
|
-
capabilities: {
|
|
80
|
-
tools: {},
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
this.capabilities = new CapabilityResolver(config);
|
|
84
|
-
this.runtimeFingerprint = runtimeFingerprint;
|
|
85
|
-
this.readFileMaxLines = Math.max(1, config.readFileMaxLines ?? 1000);
|
|
86
|
-
this.watchSyncEnabled = config.watchSyncEnabled === true;
|
|
87
|
-
this.watchDebounceMs = Math.max(1, config.watchDebounceMs ?? 5000);
|
|
88
|
-
console.log(`[FINGERPRINT] Runtime index fingerprint: ${JSON.stringify(this.runtimeFingerprint)}`);
|
|
89
|
-
this.runtimeOwnerRegistry = new RuntimeOwnerRegistry({
|
|
90
|
-
identity: buildRuntimeOwnerIdentityFromConfig({
|
|
91
|
-
config,
|
|
92
|
-
runtimeFingerprint: this.runtimeFingerprint,
|
|
93
|
-
}),
|
|
94
|
-
});
|
|
95
|
-
try {
|
|
96
|
-
this.runtimeOwnerRegistry.registerCurrentOwner();
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
-
console.warn(`[RUNTIME-OWNER] Failed to register current Satori runtime owner; index mutations will fail closed until the owner registry is writable: ${message}`);
|
|
101
|
-
}
|
|
102
|
-
this.mutationLeaseCoordinator = new MutationLeaseCoordinator();
|
|
103
|
-
this.searchContinuationCoordinator = new SearchContinuationCoordinator();
|
|
104
|
-
this.snapshotManager = new SnapshotManager(this.runtimeFingerprint);
|
|
105
|
-
this.callGraphManager = new CallGraphSidecarManager(this.runtimeFingerprint);
|
|
106
|
-
const localContext = createLocalOnlyContext(config, this.mutationLeaseCoordinator);
|
|
107
|
-
this.syncManager = new SyncManager(localContext, this.snapshotManager, {
|
|
108
|
-
watchEnabled: this.watchSyncEnabled,
|
|
109
|
-
watchDebounceMs: this.watchDebounceMs,
|
|
110
|
-
mutationLeaseCoordinator: this.mutationLeaseCoordinator,
|
|
111
|
-
});
|
|
112
|
-
this.toolHandlers = new ToolHandlers(localContext, this.snapshotManager, this.syncManager, this.runtimeFingerprint, this.capabilities, () => Date.now(), this.callGraphManager, null, undefined, undefined, this.runtimeOwnerRegistry, this.mutationLeaseCoordinator, this.searchContinuationCoordinator);
|
|
113
|
-
this.providerRuntime = new ProviderRuntime({
|
|
114
|
-
config,
|
|
115
|
-
snapshotManager: this.snapshotManager,
|
|
116
|
-
runtimeFingerprint: this.runtimeFingerprint,
|
|
117
|
-
capabilities: this.capabilities,
|
|
118
|
-
readFileMaxLines: this.readFileMaxLines,
|
|
119
|
-
watchSyncEnabled: this.watchSyncEnabled,
|
|
120
|
-
watchDebounceMs: this.watchDebounceMs,
|
|
121
|
-
startSyncLifecycle: this.runMode === "mcp",
|
|
122
|
-
callGraphManager: this.callGraphManager,
|
|
123
|
-
runtimeOwnerGate: this.runtimeOwnerRegistry,
|
|
124
|
-
mutationLeaseCoordinator: this.mutationLeaseCoordinator,
|
|
125
|
-
searchContinuationCoordinator: this.searchContinuationCoordinator,
|
|
126
|
-
});
|
|
127
|
-
this.toolContext = {
|
|
128
|
-
context: localContext,
|
|
129
|
-
snapshotManager: this.snapshotManager,
|
|
130
|
-
syncManager: this.syncManager,
|
|
131
|
-
capabilities: this.capabilities,
|
|
132
|
-
reranker: null,
|
|
133
|
-
runtimeFingerprint: this.runtimeFingerprint,
|
|
134
|
-
toolHandlers: this.toolHandlers,
|
|
135
|
-
readFileMaxLines: this.readFileMaxLines,
|
|
136
|
-
runtimeOwnerGate: this.runtimeOwnerRegistry,
|
|
137
|
-
providerRuntime: this.providerRuntime,
|
|
138
|
-
};
|
|
139
|
-
this.snapshotManager.loadCodebaseSnapshot();
|
|
140
|
-
this.setupTools();
|
|
141
|
-
}
|
|
142
|
-
getCliTransportStdout() {
|
|
143
|
-
if (this.protocolStdout) {
|
|
144
|
-
return this.protocolStdout;
|
|
145
|
-
}
|
|
146
|
-
if (this.runMode !== "cli") {
|
|
147
|
-
return process.stdout;
|
|
148
|
-
}
|
|
149
|
-
throw new Error("E_PROTOCOL_FAILURE Missing protocolStdout for cli mode");
|
|
150
|
-
}
|
|
151
|
-
getToolContext() {
|
|
152
|
-
return this.toolContext;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Verify interrupted indexing snapshots via the fenced recovery path.
|
|
156
|
-
* Must not publish lifecycle transitions without a mutation lease.
|
|
157
|
-
*/
|
|
158
|
-
async verifyCloudState(_toolContext) {
|
|
159
|
-
console.log("[STARTUP] Verifying interrupted indexing state against completion markers...");
|
|
160
|
-
await this.toolHandlers.recoverInterruptedIndexingAtStartup();
|
|
161
|
-
}
|
|
162
|
-
setupTools() {
|
|
163
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
164
|
-
return {
|
|
165
|
-
tools: getMcpToolList(this.getToolContext()),
|
|
166
|
-
};
|
|
167
|
-
});
|
|
168
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
169
|
-
const { name, arguments: args } = request.params;
|
|
170
|
-
const tool = toolRegistry[name];
|
|
171
|
-
if (!tool) {
|
|
172
|
-
return {
|
|
173
|
-
content: [{
|
|
174
|
-
type: "text",
|
|
175
|
-
text: `Unknown tool: ${name}. Supported tools: ${Object.keys(toolRegistry).join(", ")}`,
|
|
176
|
-
}],
|
|
177
|
-
isError: true,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
return withSourceMeasurementOperation({ operation: name }, () => tool.execute(args || {}, this.getToolContext()));
|
|
181
|
-
});
|
|
61
|
+
this.protocolStdin = protocolStdin;
|
|
62
|
+
this.host = new SharedRuntimeHost(config, runtimeFingerprint, runMode);
|
|
63
|
+
this.session = this.host.createSession();
|
|
182
64
|
}
|
|
183
65
|
async start() {
|
|
184
66
|
console.log("Starting Satori MCP server...");
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
await this.server.connect(transport);
|
|
190
|
-
transportStdin.resume();
|
|
191
|
-
this.keepAliveTimer = setInterval(() => {
|
|
192
|
-
// Keep stdio MCP process alive when startup has no background provider lifecycle.
|
|
193
|
-
}, 60 * 60 * 1000);
|
|
67
|
+
if (this.runMode === "cli" && !this.protocolStdout) {
|
|
68
|
+
throw new Error("E_PROTOCOL_FAILURE Missing protocolStdout for cli mode");
|
|
69
|
+
}
|
|
70
|
+
await this.session.connectStdio(this.protocolStdin, this.protocolStdout);
|
|
194
71
|
console.log("MCP server started and listening on stdio.");
|
|
195
72
|
await runPostConnectStartupLifecycle(this.runMode, {
|
|
196
|
-
verifyCloudState: () =>
|
|
73
|
+
verifyCloudState: async () => {
|
|
74
|
+
console.log("[STARTUP] Verifying interrupted indexing state against completion markers...");
|
|
75
|
+
await this.host.recoverInterruptedIndexingAtStartup();
|
|
76
|
+
},
|
|
197
77
|
onVerifyCloudStateError: (error) => {
|
|
198
78
|
console.error("[STARTUP] Error verifying cloud state:", errorMessage(error));
|
|
199
79
|
},
|
|
@@ -201,15 +81,8 @@ class ContextMcpServer {
|
|
|
201
81
|
}
|
|
202
82
|
async shutdown() {
|
|
203
83
|
console.log("Shutting down Satori MCP server...");
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.keepAliveTimer = null;
|
|
207
|
-
}
|
|
208
|
-
this.syncManager.stopBackgroundSync();
|
|
209
|
-
await this.syncManager.stopWatcherMode();
|
|
210
|
-
await this.providerRuntime.shutdown();
|
|
211
|
-
this.toolHandlers.releaseSearchContinuationOwnership();
|
|
212
|
-
this.runtimeOwnerRegistry.unregisterCurrentOwner();
|
|
84
|
+
await this.session.shutdown();
|
|
85
|
+
await this.host.shutdown();
|
|
213
86
|
}
|
|
214
87
|
}
|
|
215
88
|
function isHelpRequested(args) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zokizuan/satori-mcp",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Repo-aware MCP tools for AI coding agents: intent search, symbol navigation, advisory call graphs, exact reads, and freshness-aware recovery.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"jsonc-parser": "^3.3.1",
|
|
16
16
|
"zod": "^3.25.55",
|
|
17
17
|
"zod-to-json-schema": "^3.25.1",
|
|
18
|
-
"@zokizuan/satori-core": "3.
|
|
18
|
+
"@zokizuan/satori-core": "3.2.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^20.0.0",
|