@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.
@@ -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 { ContextMcpConfig, IndexFingerprint } from "../config.js";
3
- export type ServerRunMode = "mcp" | "cli" | "postflight";
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 server;
24
- private toolContext;
25
- private snapshotManager;
26
- private syncManager;
27
- private toolHandlers;
28
- private capabilities;
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 { CapabilityResolver } from "../core/capabilities.js";
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.server = new Server({
76
- name: config.name,
77
- version: config.version,
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
- const transportStdin = this.protocolStdin ?? process.stdin;
186
- const transport = this.runMode === "cli" || this.protocolStdin || this.protocolStdout
187
- ? new StdioServerTransport(transportStdin, this.getCliTransportStdout())
188
- : new StdioServerTransport();
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: () => this.verifyCloudState(this.getToolContext()),
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
- if (this.keepAliveTimer) {
205
- clearInterval(this.keepAliveTimer);
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.2.0",
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.1.0"
18
+ "@zokizuan/satori-core": "3.2.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.0.0",