@j0hanz/filesystem-mcp 1.19.0 → 2.0.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 +333 -838
- package/dist/cli-help.d.ts +3 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +169 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +168 -123
- package/dist/cli.js.map +1 -0
- package/dist/core/concurrency.d.ts +70 -0
- package/dist/core/concurrency.d.ts.map +1 -0
- package/dist/core/concurrency.js +173 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +11 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cursor.d.ts +23 -0
- package/dist/core/cursor.d.ts.map +1 -0
- package/dist/core/cursor.js +62 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/errors.d.ts +96 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +356 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/file-uri.d.ts +24 -0
- package/dist/core/file-uri.d.ts.map +1 -0
- package/dist/core/file-uri.js +52 -0
- package/dist/core/file-uri.js.map +1 -0
- package/dist/core/fmt.d.ts +38 -0
- package/dist/core/fmt.d.ts.map +1 -0
- package/dist/core/fmt.js +109 -0
- package/dist/core/fmt.js.map +1 -0
- package/dist/core/fs.d.ts +108 -0
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/fs.js +243 -0
- package/dist/core/fs.js.map +1 -0
- package/dist/core/glob.d.ts +41 -0
- package/dist/core/glob.d.ts.map +1 -0
- package/dist/core/glob.js +399 -0
- package/dist/core/glob.js.map +1 -0
- package/dist/core/input-required.d.ts +147 -0
- package/dist/core/input-required.d.ts.map +1 -0
- package/dist/core/input-required.js +261 -0
- package/dist/core/input-required.js.map +1 -0
- package/dist/core/mime.d.ts +15 -0
- package/dist/core/mime.d.ts.map +1 -0
- package/dist/core/mime.js +188 -0
- package/dist/core/mime.js.map +1 -0
- package/dist/core/observability.d.ts +10 -0
- package/dist/core/observability.d.ts.map +1 -0
- package/dist/core/observability.js +76 -0
- package/dist/core/observability.js.map +1 -0
- package/dist/core/page-store.d.ts +41 -0
- package/dist/core/page-store.d.ts.map +1 -0
- package/dist/core/page-store.js +74 -0
- package/dist/core/page-store.js.map +1 -0
- package/dist/core/path-completer.d.ts +10 -0
- package/dist/core/path-completer.d.ts.map +1 -0
- package/dist/core/path-completer.js +227 -0
- package/dist/core/path-completer.js.map +1 -0
- package/dist/core/path-discovery.d.ts +8 -0
- package/dist/core/path-discovery.d.ts.map +1 -0
- package/dist/core/path-discovery.js +105 -0
- package/dist/core/path-discovery.js.map +1 -0
- package/dist/core/path-utils.d.ts +9 -0
- package/dist/core/path-utils.d.ts.map +1 -0
- package/dist/core/path-utils.js +121 -0
- package/dist/core/path-utils.js.map +1 -0
- package/dist/core/path.d.ts +148 -0
- package/dist/core/path.d.ts.map +1 -0
- package/dist/core/path.js +674 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/primitives.d.ts +23 -0
- package/dist/core/primitives.d.ts.map +1 -0
- package/dist/core/primitives.js +54 -0
- package/dist/core/primitives.js.map +1 -0
- package/dist/core/read.d.ts +74 -0
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +562 -0
- package/dist/core/read.js.map +1 -0
- package/dist/core/schema.d.ts +139 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +345 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/search.d.ts +100 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +248 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/sensitive.d.ts +6 -0
- package/dist/core/sensitive.d.ts.map +1 -0
- package/dist/core/sensitive.js +142 -0
- package/dist/core/sensitive.js.map +1 -0
- package/dist/core/store.d.ts +57 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +162 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/util.d.ts +26 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/core/util.js +66 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/watcher-registry.d.ts +68 -0
- package/dist/core/watcher-registry.d.ts.map +1 -0
- package/dist/core/watcher-registry.js +312 -0
- package/dist/core/watcher-registry.js.map +1 -0
- package/dist/http-policy.d.ts +121 -0
- package/dist/http-policy.d.ts.map +1 -0
- package/dist/http-policy.js +340 -0
- package/dist/http-policy.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -41
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +71 -0
- package/dist/instructions.js.map +1 -0
- package/dist/prompts.d.ts +8 -6
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +74 -168
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +73 -9
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +392 -163
- package/dist/resources.js.map +1 -0
- package/dist/server.d.ts +62 -1
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +139 -1
- package/dist/server.js.map +1 -0
- package/dist/tools/batch.d.ts +89 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +157 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/create.d.ts +2 -0
- package/dist/tools/create.d.ts.map +1 -0
- package/dist/tools/create.js +142 -0
- package/dist/tools/create.js.map +1 -0
- package/dist/tools/define.d.ts +110 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +394 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/delete-file.d.ts +2 -4
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +330 -87
- package/dist/tools/delete-file.js.map +1 -0
- package/dist/tools/diff.d.ts +2 -0
- package/dist/tools/diff.d.ts.map +1 -0
- package/dist/tools/diff.js +94 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +442 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/index.d.ts +26 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list.d.ts +2 -0
- package/dist/tools/list.d.ts.map +1 -0
- package/dist/tools/list.js +291 -0
- package/dist/tools/list.js.map +1 -0
- package/dist/tools/move.d.ts +2 -0
- package/dist/tools/move.d.ts.map +1 -0
- package/dist/tools/move.js +321 -0
- package/dist/tools/move.js.map +1 -0
- package/dist/tools/patch.d.ts +2 -0
- package/dist/tools/patch.d.ts.map +1 -0
- package/dist/tools/patch.js +155 -0
- package/dist/tools/patch.js.map +1 -0
- package/dist/tools/progress.d.ts +52 -0
- package/dist/tools/progress.d.ts.map +1 -0
- package/dist/tools/progress.js +172 -0
- package/dist/tools/progress.js.map +1 -0
- package/dist/tools/read.d.ts +2 -4
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +398 -152
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/replace-in-files.d.ts +2 -4
- package/dist/tools/replace-in-files.d.ts.map +1 -0
- package/dist/tools/replace-in-files.js +435 -264
- package/dist/tools/replace-in-files.js.map +1 -0
- package/dist/tools/roots.d.ts +2 -4
- package/dist/tools/roots.d.ts.map +1 -0
- package/dist/tools/roots.js +29 -52
- package/dist/tools/roots.js.map +1 -0
- package/dist/tools/search-content.d.ts +2 -4
- package/dist/tools/search-content.d.ts.map +1 -0
- package/dist/tools/search-content.js +262 -228
- package/dist/tools/search-content.js.map +1 -0
- package/dist/tools/search-files.d.ts +2 -4
- package/dist/tools/search-files.d.ts.map +1 -0
- package/dist/tools/search-files.js +162 -161
- package/dist/tools/search-files.js.map +1 -0
- package/dist/tools/stat.d.ts +2 -4
- package/dist/tools/stat.d.ts.map +1 -0
- package/dist/tools/stat.js +202 -62
- package/dist/tools/stat.js.map +1 -0
- package/dist/transport/http.d.ts +5 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +276 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/shared.d.ts +45 -0
- package/dist/transport/shared.d.ts.map +1 -0
- package/dist/transport/shared.js +65 -0
- package/dist/transport/shared.js.map +1 -0
- package/dist/transport/stdio.d.ts +32 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +279 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport.d.ts +5 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +4 -0
- package/dist/transport.js.map +1 -0
- package/package.json +37 -46
- package/dist/assets/logo.svg +0 -35
- package/dist/completions.d.ts +0 -2
- package/dist/completions.js +0 -572
- package/dist/config.d.ts +0 -119
- package/dist/config.js +0 -31
- package/dist/lib/abort.d.ts +0 -7
- package/dist/lib/abort.js +0 -81
- package/dist/lib/constants.d.ts +0 -42
- package/dist/lib/constants.js +0 -313
- package/dist/lib/errors.d.ts +0 -28
- package/dist/lib/errors.js +0 -301
- package/dist/lib/file-operations/core.d.ts +0 -53
- package/dist/lib/file-operations/core.js +0 -144
- package/dist/lib/file-operations/metadata.d.ts +0 -77
- package/dist/lib/file-operations/metadata.js +0 -878
- package/dist/lib/file-operations/search.d.ts +0 -46
- package/dist/lib/file-operations/search.js +0 -1194
- package/dist/lib/file-operations/traversal.d.ts +0 -40
- package/dist/lib/file-operations/traversal.js +0 -310
- package/dist/lib/fs-helpers.d.ts +0 -46
- package/dist/lib/fs-helpers.js +0 -606
- package/dist/lib/globs.d.ts +0 -2
- package/dist/lib/globs.js +0 -19
- package/dist/lib/logger.d.ts +0 -27
- package/dist/lib/logger.js +0 -91
- package/dist/lib/observability.d.ts +0 -33
- package/dist/lib/observability.js +0 -373
- package/dist/lib/paths.d.ts +0 -35
- package/dist/lib/paths.js +0 -634
- package/dist/lib/resource-store.d.ts +0 -28
- package/dist/lib/resource-store.js +0 -195
- package/dist/lib/utils.d.ts +0 -7
- package/dist/lib/utils.js +0 -37
- package/dist/lib/zod-codecs.d.ts +0 -2
- package/dist/lib/zod-codecs.js +0 -18
- package/dist/pkg-info.d.ts +0 -7
- package/dist/pkg-info.js +0 -9
- package/dist/resources/generated-instructions.d.ts +0 -1
- package/dist/resources/generated-instructions.js +0 -105
- package/dist/resources/tool-catalog.d.ts +0 -2
- package/dist/resources/tool-catalog.js +0 -78
- package/dist/resources/tool-info.d.ts +0 -9
- package/dist/resources/tool-info.js +0 -206
- package/dist/resources/workflows.d.ts +0 -1
- package/dist/resources/workflows.js +0 -72
- package/dist/schemas.d.ts +0 -505
- package/dist/schemas.js +0 -676
- package/dist/server/bootstrap.d.ts +0 -6
- package/dist/server/bootstrap.js +0 -563
- package/dist/server/event-store.d.ts +0 -18
- package/dist/server/event-store.js +0 -71
- package/dist/server/roots-manager.d.ts +0 -28
- package/dist/server/roots-manager.js +0 -227
- package/dist/server/task-store.d.ts +0 -10
- package/dist/server/task-store.js +0 -91
- package/dist/tools/apply-patch.d.ts +0 -4
- package/dist/tools/apply-patch.js +0 -235
- package/dist/tools/calculate-hash.d.ts +0 -4
- package/dist/tools/calculate-hash.js +0 -187
- package/dist/tools/contract.d.ts +0 -50
- package/dist/tools/contract.js +0 -1
- package/dist/tools/create-directory.d.ts +0 -4
- package/dist/tools/create-directory.js +0 -68
- package/dist/tools/diff-files.d.ts +0 -4
- package/dist/tools/diff-files.js +0 -136
- package/dist/tools/edit-file.d.ts +0 -4
- package/dist/tools/edit-file.js +0 -256
- package/dist/tools/icons.d.ts +0 -15
- package/dist/tools/icons.js +0 -24
- package/dist/tools/list-directory.d.ts +0 -4
- package/dist/tools/list-directory.js +0 -218
- package/dist/tools/move-file.d.ts +0 -4
- package/dist/tools/move-file.js +0 -178
- package/dist/tools/read-multiple.d.ts +0 -4
- package/dist/tools/read-multiple.js +0 -207
- package/dist/tools/shared.d.ts +0 -172
- package/dist/tools/shared.js +0 -599
- package/dist/tools/stat-many.d.ts +0 -4
- package/dist/tools/stat-many.js +0 -104
- package/dist/tools/task-support.d.ts +0 -29
- package/dist/tools/task-support.js +0 -472
- package/dist/tools/tree.d.ts +0 -4
- package/dist/tools/tree.js +0 -102
- package/dist/tools/write-file.d.ts +0 -4
- package/dist/tools/write-file.js +0 -60
- package/dist/tools.d.ts +0 -5
- package/dist/tools.js +0 -54
package/dist/server/bootstrap.js
DELETED
|
@@ -1,563 +0,0 @@
|
|
|
1
|
-
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
|
|
2
|
-
import { InMemoryTaskMessageQueue, isInitializeRequest, localhostAllowedHostnames, McpServer, ProtocolErrorCode, StdioServerTransport, validateHostHeader, } from '@modelcontextprotocol/server';
|
|
3
|
-
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
4
|
-
import { channel } from 'node:diagnostics_channel';
|
|
5
|
-
import { readFile } from 'node:fs/promises';
|
|
6
|
-
import { createServer as createHttpServer, } from 'node:http';
|
|
7
|
-
import { inspect } from 'node:util';
|
|
8
|
-
import { DEFAULT_LOG_LEVEL, getInitHandshakeTimeoutMs, INIT_TIMEOUT_CLOSE, parseEnvInt, } from '../lib/constants.js';
|
|
9
|
-
import { formatUnknownErrorMessage } from '../lib/errors.js';
|
|
10
|
-
import { createLoggingState, Logger, logToMcp, SessionContext, } from '../lib/logger.js';
|
|
11
|
-
import { withAllowedDirectoriesState } from '../lib/paths.js';
|
|
12
|
-
import { createInMemoryResourceStore } from '../lib/resource-store.js';
|
|
13
|
-
import { registerCompletions } from '../completions.js';
|
|
14
|
-
import { pkgInfo } from '../pkg-info.js';
|
|
15
|
-
import { registerAnalyzePathPrompt, registerCompareFilesPrompt, registerGetHelpPrompt, registerGetToolHelpPrompt, } from '../prompts.js';
|
|
16
|
-
import { registerInstructionResource, registerMetricsResource, registerResultResources, registerToolCatalogResource, registerToolInfoResource, registerWorkflowGuideResource, } from '../resources.js';
|
|
17
|
-
import { buildServerInstructions } from '../resources/generated-instructions.js';
|
|
18
|
-
import { registerAllTools } from '../tools.js';
|
|
19
|
-
import { withDefaultIcons } from '../tools/shared.js';
|
|
20
|
-
import { InMemoryEventStore } from './event-store.js';
|
|
21
|
-
import { RootsManager } from './roots-manager.js';
|
|
22
|
-
import { createTaskStore } from './task-store.js';
|
|
23
|
-
function buildServerCapabilities(options = {}) {
|
|
24
|
-
const capabilities = {
|
|
25
|
-
logging: {},
|
|
26
|
-
resources: {},
|
|
27
|
-
tools: {},
|
|
28
|
-
prompts: options.enablePromptListChanged ? { listChanged: true } : {},
|
|
29
|
-
completions: {},
|
|
30
|
-
extensions: {},
|
|
31
|
-
};
|
|
32
|
-
if (options.enableTaskToolRequests) {
|
|
33
|
-
// NOTE: enabling task tool requests requires the caller to configure
|
|
34
|
-
// an InMemoryTaskStore and InMemoryTaskMessageQueue on the McpServer.
|
|
35
|
-
// InMemoryTaskStore auto-evicts tasks after TTL via setTimeout.
|
|
36
|
-
capabilities.tasks = {
|
|
37
|
-
list: {},
|
|
38
|
-
cancel: {},
|
|
39
|
-
requests: { tools: { call: {} } },
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
return capabilities;
|
|
43
|
-
}
|
|
44
|
-
// Global map of all active servers by sessionId for routing logs
|
|
45
|
-
const activeServers = new Map();
|
|
46
|
-
// For stdio (single session without a specific ID)
|
|
47
|
-
let stdioServer;
|
|
48
|
-
function stringifyData(data) {
|
|
49
|
-
if (data === undefined)
|
|
50
|
-
return '';
|
|
51
|
-
if (typeof data === 'string')
|
|
52
|
-
return ` ${data}`;
|
|
53
|
-
if (data === null ||
|
|
54
|
-
typeof data === 'number' ||
|
|
55
|
-
typeof data === 'boolean' ||
|
|
56
|
-
typeof data === 'bigint') {
|
|
57
|
-
return ` ${String(data)}`;
|
|
58
|
-
}
|
|
59
|
-
return ` ${inspect(data, { depth: 4, colors: false, compact: 3 })}`;
|
|
60
|
-
}
|
|
61
|
-
channel('filesystem-mcp:log').subscribe((message) => {
|
|
62
|
-
const event = message;
|
|
63
|
-
const target = event.sessionId
|
|
64
|
-
? activeServers.get(event.sessionId)
|
|
65
|
-
: stdioServer;
|
|
66
|
-
const dataStr = stringifyData(event.data);
|
|
67
|
-
if (target) {
|
|
68
|
-
logToMcp(target.server, event.level, `${event.message}${dataStr}`, target.loggingState.minimumLevel);
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
// Fallback if no server
|
|
72
|
-
const fullMsg = `${event.message}${dataStr}`;
|
|
73
|
-
console.error(`[${event.level.toUpperCase()}] ${fullMsg}`);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
const { version: SERVER_VERSION, description: SERVER_DESCRIPTION, homepage: SERVER_HOMEPAGE, } = pkgInfo;
|
|
77
|
-
const rootsManagers = new WeakMap();
|
|
78
|
-
function getRootsManager(server) {
|
|
79
|
-
const manager = rootsManagers.get(server);
|
|
80
|
-
if (!manager) {
|
|
81
|
-
throw new Error('Roots manager not initialized for server instance');
|
|
82
|
-
}
|
|
83
|
-
return manager;
|
|
84
|
-
}
|
|
85
|
-
async function getLocalIconInfo() {
|
|
86
|
-
const name = 'logo.svg';
|
|
87
|
-
const mime = 'image/svg+xml';
|
|
88
|
-
const candidates = [`../assets/${name}`, `../../assets/${name}`];
|
|
89
|
-
for (const candidate of candidates) {
|
|
90
|
-
try {
|
|
91
|
-
const iconPath = new URL(candidate, import.meta.url);
|
|
92
|
-
const buffer = await readFile(iconPath);
|
|
93
|
-
return {
|
|
94
|
-
src: `data:${mime};base64,${buffer.toString('base64')}`,
|
|
95
|
-
mimeType: mime,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// Try next candidate.
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return undefined;
|
|
103
|
-
}
|
|
104
|
-
export async function createServer(options = {}) {
|
|
105
|
-
const resourceStore = createInMemoryResourceStore();
|
|
106
|
-
const serverInstructions = buildServerInstructions();
|
|
107
|
-
const localIcon = await getLocalIconInfo();
|
|
108
|
-
const capabilities = buildServerCapabilities({
|
|
109
|
-
enablePromptListChanged: false,
|
|
110
|
-
enableTaskToolRequests: true,
|
|
111
|
-
});
|
|
112
|
-
if (capabilities.tasks) {
|
|
113
|
-
capabilities.tasks = {
|
|
114
|
-
...capabilities.tasks,
|
|
115
|
-
taskStore: createTaskStore(),
|
|
116
|
-
taskMessageQueue: new InMemoryTaskMessageQueue(),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
const serverConfig = { capabilities };
|
|
120
|
-
if (serverInstructions) {
|
|
121
|
-
serverConfig.instructions =
|
|
122
|
-
'filesystem-mcp: Secure local filesystem MCP server. ' +
|
|
123
|
-
'Start with: roots -> ls/find -> stat -> read. Never guess paths. ' +
|
|
124
|
-
'For full guidance, read internal://instructions or run the get-help prompt.';
|
|
125
|
-
}
|
|
126
|
-
const server = new McpServer(withDefaultIcons({
|
|
127
|
-
name: 'filesystem-mcp',
|
|
128
|
-
title: 'Filesystem MCP',
|
|
129
|
-
version: SERVER_VERSION,
|
|
130
|
-
...(SERVER_DESCRIPTION ? { description: SERVER_DESCRIPTION } : {}),
|
|
131
|
-
...(SERVER_HOMEPAGE ? { websiteUrl: SERVER_HOMEPAGE } : {}),
|
|
132
|
-
}, localIcon), serverConfig);
|
|
133
|
-
const loggingState = createLoggingState(DEFAULT_LOG_LEVEL);
|
|
134
|
-
const rootsManager = new RootsManager(options, loggingState);
|
|
135
|
-
rootsManagers.set(server, rootsManager);
|
|
136
|
-
// Subscribe to Logger channel if not already done, but we need to route based on session or fallback to this server if it's stdio.
|
|
137
|
-
// Wait, in stdio there's only one server. In HTTP there are multiple.
|
|
138
|
-
server.server.setRequestHandler('logging/setLevel', (req) => {
|
|
139
|
-
loggingState.minimumLevel = req.params.level;
|
|
140
|
-
Logger.notice(`Log level set to ${req.params.level}`);
|
|
141
|
-
return {};
|
|
142
|
-
});
|
|
143
|
-
// Track stdio server by default, or it will be overwritten per HTTP session later
|
|
144
|
-
stdioServer ??= { server, loggingState };
|
|
145
|
-
registerInstructionResource(server, serverInstructions, localIcon);
|
|
146
|
-
registerToolCatalogResource(server, localIcon);
|
|
147
|
-
registerWorkflowGuideResource(server, localIcon);
|
|
148
|
-
registerToolInfoResource(server, localIcon);
|
|
149
|
-
registerGetHelpPrompt(server, serverInstructions, localIcon);
|
|
150
|
-
registerCompareFilesPrompt(server, localIcon);
|
|
151
|
-
registerAnalyzePathPrompt(server, localIcon);
|
|
152
|
-
registerGetToolHelpPrompt(server, localIcon);
|
|
153
|
-
registerResultResources(server, resourceStore, localIcon);
|
|
154
|
-
registerMetricsResource(server, localIcon);
|
|
155
|
-
registerCompletions(server, serverInstructions);
|
|
156
|
-
registerAllTools(server, {
|
|
157
|
-
resourceStore,
|
|
158
|
-
isInitialized: () => rootsManager.isInitialized(),
|
|
159
|
-
...(localIcon ? { iconInfo: localIcon } : {}),
|
|
160
|
-
});
|
|
161
|
-
return server;
|
|
162
|
-
}
|
|
163
|
-
export async function startServer(server) {
|
|
164
|
-
const transport = new StdioServerTransport();
|
|
165
|
-
const rootsManager = getRootsManager(server);
|
|
166
|
-
rootsManager.registerHandlers(server, INIT_TIMEOUT_CLOSE
|
|
167
|
-
? () => {
|
|
168
|
-
void server.close();
|
|
169
|
-
}
|
|
170
|
-
: undefined);
|
|
171
|
-
await rootsManager.recomputeAllowedDirectories();
|
|
172
|
-
await server.connect(transport);
|
|
173
|
-
const sdkOnClose = transport.onclose;
|
|
174
|
-
transport.onclose = () => {
|
|
175
|
-
rootsManager.destroy();
|
|
176
|
-
sdkOnClose?.();
|
|
177
|
-
};
|
|
178
|
-
rootsManager.logMissingDirectoriesIfNeeded(server);
|
|
179
|
-
}
|
|
180
|
-
const MAX_REQUEST_BODY_BYTES = parseEnvInt('FS_CONTEXT_MAX_REQUEST_BYTES', 4 * 1024 * 1024, 1024, 256 * 1024 * 1024);
|
|
181
|
-
class RequestBodyError extends Error {
|
|
182
|
-
statusCode;
|
|
183
|
-
constructor(message, statusCode) {
|
|
184
|
-
super(message);
|
|
185
|
-
this.statusCode = statusCode;
|
|
186
|
-
this.name = 'RequestBodyError';
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
async function readRequestBody(req) {
|
|
190
|
-
return new Promise((resolve, reject) => {
|
|
191
|
-
const chunks = [];
|
|
192
|
-
let totalBytes = 0;
|
|
193
|
-
let tooBig = false;
|
|
194
|
-
req.on('data', (chunk) => {
|
|
195
|
-
totalBytes += chunk.length;
|
|
196
|
-
if (totalBytes > MAX_REQUEST_BODY_BYTES) {
|
|
197
|
-
if (!tooBig) {
|
|
198
|
-
tooBig = true;
|
|
199
|
-
chunks.length = 0; // free accumulated memory
|
|
200
|
-
req.pause(); // stop emitting data events; TCP window fills naturally
|
|
201
|
-
reject(new RequestBodyError('Request body too large', 413));
|
|
202
|
-
}
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
chunks.push(chunk);
|
|
206
|
-
});
|
|
207
|
-
req.on('end', () => {
|
|
208
|
-
if (tooBig)
|
|
209
|
-
return; // already rejected in 'data' handler
|
|
210
|
-
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
211
|
-
if (!raw) {
|
|
212
|
-
resolve(undefined);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
try {
|
|
216
|
-
resolve(JSON.parse(raw));
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
reject(new RequestBodyError('Invalid JSON in request body', 400));
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
req.on('error', reject);
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
async function createHttpSession(options, sessions, eventStore) {
|
|
226
|
-
const mcpServer = await createServer(options);
|
|
227
|
-
const rootsManager = getRootsManager(mcpServer);
|
|
228
|
-
rootsManager.registerHandlers(mcpServer);
|
|
229
|
-
await rootsManager.recomputeAllowedDirectories();
|
|
230
|
-
let cleanedUp = false;
|
|
231
|
-
const cleanup = () => {
|
|
232
|
-
if (cleanedUp)
|
|
233
|
-
return;
|
|
234
|
-
cleanedUp = true;
|
|
235
|
-
const { sessionId } = transport;
|
|
236
|
-
if (sessionId) {
|
|
237
|
-
sessions.delete(sessionId);
|
|
238
|
-
activeServers.delete(sessionId);
|
|
239
|
-
eventStore.delete(sessionId);
|
|
240
|
-
}
|
|
241
|
-
rootsManager.destroy();
|
|
242
|
-
};
|
|
243
|
-
const close = async () => {
|
|
244
|
-
cleanup();
|
|
245
|
-
await mcpServer.close();
|
|
246
|
-
};
|
|
247
|
-
const transport = new NodeStreamableHTTPServerTransport({
|
|
248
|
-
sessionIdGenerator: () => randomUUID(),
|
|
249
|
-
eventStore,
|
|
250
|
-
onsessioninitialized: (sessionId) => {
|
|
251
|
-
sessions.set(sessionId, {
|
|
252
|
-
server: mcpServer,
|
|
253
|
-
rootsManager,
|
|
254
|
-
transport,
|
|
255
|
-
createdAt: Date.now(),
|
|
256
|
-
cleanup,
|
|
257
|
-
close,
|
|
258
|
-
});
|
|
259
|
-
activeServers.set(sessionId, {
|
|
260
|
-
server: mcpServer,
|
|
261
|
-
loggingState: rootsManager['loggingState'],
|
|
262
|
-
});
|
|
263
|
-
rootsManager.logMissingDirectoriesIfNeeded(mcpServer);
|
|
264
|
-
},
|
|
265
|
-
});
|
|
266
|
-
transport.onclose = cleanup;
|
|
267
|
-
await mcpServer.connect(transport);
|
|
268
|
-
return {
|
|
269
|
-
server: mcpServer,
|
|
270
|
-
rootsManager,
|
|
271
|
-
transport,
|
|
272
|
-
createdAt: Date.now(),
|
|
273
|
-
cleanup,
|
|
274
|
-
close,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
function sendJsonRpcError(res, status, code, message) {
|
|
278
|
-
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
279
|
-
res.end(JSON.stringify({
|
|
280
|
-
jsonrpc: '2.0',
|
|
281
|
-
error: { code, message },
|
|
282
|
-
id: null,
|
|
283
|
-
}));
|
|
284
|
-
}
|
|
285
|
-
const LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/u;
|
|
286
|
-
const MAX_SESSION_ID_LENGTH = 256;
|
|
287
|
-
const MAX_BEARER_TOKEN_LENGTH = 4096;
|
|
288
|
-
const JSON_RPC_SERVER_ERROR = -32000;
|
|
289
|
-
const JSON_RPC_INVALID_REQUEST = ProtocolErrorCode.InvalidRequest;
|
|
290
|
-
const JSON_RPC_PARSE_ERROR = ProtocolErrorCode.ParseError;
|
|
291
|
-
const JSON_RPC_INTERNAL_ERROR = ProtocolErrorCode.InternalError;
|
|
292
|
-
function isAllowedOrigin(origin) {
|
|
293
|
-
if (origin === undefined)
|
|
294
|
-
return true; // Non-browser clients omit Origin.
|
|
295
|
-
return LOCALHOST_ORIGIN_RE.test(origin);
|
|
296
|
-
}
|
|
297
|
-
function normalizeAllowedHostname(host) {
|
|
298
|
-
const trimmed = host.trim().toLowerCase();
|
|
299
|
-
if (trimmed === '::1')
|
|
300
|
-
return '[::1]';
|
|
301
|
-
return trimmed;
|
|
302
|
-
}
|
|
303
|
-
function getAllowedHostnames(httpHost) {
|
|
304
|
-
if (isLoopbackHttpHost(httpHost)) {
|
|
305
|
-
return localhostAllowedHostnames().map(normalizeAllowedHostname);
|
|
306
|
-
}
|
|
307
|
-
const normalizedHost = normalizeAllowedHostname(httpHost);
|
|
308
|
-
if (normalizedHost === '0.0.0.0' ||
|
|
309
|
-
normalizedHost === '::' ||
|
|
310
|
-
normalizedHost === '[::]') {
|
|
311
|
-
return undefined;
|
|
312
|
-
}
|
|
313
|
-
return [normalizedHost];
|
|
314
|
-
}
|
|
315
|
-
function getSessionId(req) {
|
|
316
|
-
const rawSessionId = req.headers['mcp-session-id'];
|
|
317
|
-
return typeof rawSessionId === 'string' &&
|
|
318
|
-
rawSessionId.length <= MAX_SESSION_ID_LENGTH
|
|
319
|
-
? rawSessionId
|
|
320
|
-
: undefined;
|
|
321
|
-
}
|
|
322
|
-
function isAuthorizedBearer(apiKey, authHeader) {
|
|
323
|
-
const bearerPrefix = 'Bearer ';
|
|
324
|
-
if (typeof authHeader !== 'string' || !authHeader.startsWith(bearerPrefix)) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
const userKey = authHeader.slice(bearerPrefix.length);
|
|
328
|
-
if (userKey.length > MAX_BEARER_TOKEN_LENGTH) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
const expectedHash = createHash('sha256').update(apiKey).digest();
|
|
332
|
-
const actualHash = createHash('sha256').update(userKey).digest();
|
|
333
|
-
return timingSafeEqual(expectedHash, actualHash);
|
|
334
|
-
}
|
|
335
|
-
function writeUnauthorizedResponse(res) {
|
|
336
|
-
res.writeHead(401, {
|
|
337
|
-
'Content-Type': 'application/json',
|
|
338
|
-
'WWW-Authenticate': 'Bearer',
|
|
339
|
-
});
|
|
340
|
-
res.end(JSON.stringify({
|
|
341
|
-
jsonrpc: '2.0',
|
|
342
|
-
error: { code: JSON_RPC_SERVER_ERROR, message: 'Unauthorized' },
|
|
343
|
-
id: null,
|
|
344
|
-
}));
|
|
345
|
-
}
|
|
346
|
-
function ensureAuthorizedRequest(req, res) {
|
|
347
|
-
const apiKey = process.env['FILESYSTEM_MCP_API_KEY'];
|
|
348
|
-
if (!apiKey)
|
|
349
|
-
return true;
|
|
350
|
-
if (isAuthorizedBearer(apiKey, req.headers['authorization']))
|
|
351
|
-
return true;
|
|
352
|
-
writeUnauthorizedResponse(res);
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
function ensureAllowedOrigin(req, res) {
|
|
356
|
-
const { origin } = req.headers;
|
|
357
|
-
if (isAllowedOrigin(origin)) {
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
sendJsonRpcError(res, 403, JSON_RPC_SERVER_ERROR, 'Forbidden: disallowed origin');
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
function ensureAllowedHostHeader(req, res, httpHost) {
|
|
364
|
-
const allowedHostnames = getAllowedHostnames(httpHost);
|
|
365
|
-
if (!allowedHostnames || allowedHostnames.length === 0) {
|
|
366
|
-
return true;
|
|
367
|
-
}
|
|
368
|
-
const hostHeader = typeof req.headers['host'] === 'string' ? req.headers['host'] : undefined;
|
|
369
|
-
const result = validateHostHeader(hostHeader, allowedHostnames);
|
|
370
|
-
if (result.ok)
|
|
371
|
-
return true;
|
|
372
|
-
sendJsonRpcError(res, 403, JSON_RPC_SERVER_ERROR, `Forbidden: ${result.message}`);
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
375
|
-
function discardRequestBody(req) {
|
|
376
|
-
req.on('error', () => {
|
|
377
|
-
// Best effort drain to avoid corrupting keep-alive pipelines.
|
|
378
|
-
});
|
|
379
|
-
req.resume();
|
|
380
|
-
}
|
|
381
|
-
async function handleSessionTransportRequest(session, req, res, body) {
|
|
382
|
-
const store = session.transport.sessionId
|
|
383
|
-
? { sessionId: session.transport.sessionId }
|
|
384
|
-
: {};
|
|
385
|
-
await SessionContext.run(store, async () => {
|
|
386
|
-
await withAllowedDirectoriesState(session.rootsManager.getAllowedDirectoriesState(), () => session.transport.handleRequest(req, res, body));
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
function getSessionOrRespondNotFound(sessions, sessionId, res) {
|
|
390
|
-
const session = sessions.get(sessionId);
|
|
391
|
-
if (!session) {
|
|
392
|
-
sendJsonRpcError(res, 404, JSON_RPC_SERVER_ERROR, 'Session not found');
|
|
393
|
-
return undefined;
|
|
394
|
-
}
|
|
395
|
-
return session;
|
|
396
|
-
}
|
|
397
|
-
function isLoopbackHttpHost(host) {
|
|
398
|
-
const normalized = host.trim().toLowerCase();
|
|
399
|
-
return (normalized === '127.0.0.1' ||
|
|
400
|
-
normalized === 'localhost' ||
|
|
401
|
-
normalized === '::1' ||
|
|
402
|
-
normalized === '[::1]');
|
|
403
|
-
}
|
|
404
|
-
function assertHttpBindingSecurity(host) {
|
|
405
|
-
if (isLoopbackHttpHost(host))
|
|
406
|
-
return;
|
|
407
|
-
if (process.env['FILESYSTEM_MCP_API_KEY'])
|
|
408
|
-
return;
|
|
409
|
-
throw new Error(`Refusing to bind HTTP server to non-loopback host '${host}' without FILESYSTEM_MCP_API_KEY.`);
|
|
410
|
-
}
|
|
411
|
-
function writeMethodNotAllowedResponse(res) {
|
|
412
|
-
res.writeHead(405, {
|
|
413
|
-
Allow: 'GET, POST, DELETE',
|
|
414
|
-
'Content-Type': 'application/json',
|
|
415
|
-
});
|
|
416
|
-
res.end(JSON.stringify({
|
|
417
|
-
jsonrpc: '2.0',
|
|
418
|
-
error: {
|
|
419
|
-
code: JSON_RPC_SERVER_ERROR,
|
|
420
|
-
message: 'Method Not Allowed',
|
|
421
|
-
},
|
|
422
|
-
id: null,
|
|
423
|
-
}));
|
|
424
|
-
}
|
|
425
|
-
function handleHttpRequestError(error, res) {
|
|
426
|
-
if (error instanceof RequestBodyError && !res.headersSent) {
|
|
427
|
-
const rpcCode = error.statusCode === 413
|
|
428
|
-
? JSON_RPC_INVALID_REQUEST
|
|
429
|
-
: JSON_RPC_PARSE_ERROR;
|
|
430
|
-
res.setHeader('Connection', 'close');
|
|
431
|
-
sendJsonRpcError(res, error.statusCode, rpcCode, error.message);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
Logger.error('[HTTP] Error handling request:', formatUnknownErrorMessage(error));
|
|
435
|
-
if (!res.headersSent) {
|
|
436
|
-
sendJsonRpcError(res, 500, JSON_RPC_INTERNAL_ERROR, 'Internal Server Error');
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
export async function startHttpServer(port, options) {
|
|
440
|
-
const sessions = new Map();
|
|
441
|
-
const eventStore = new InMemoryEventStore();
|
|
442
|
-
const httpHost = process.env['FILESYSTEM_MCP_HTTP_HOST'] ?? '127.0.0.1';
|
|
443
|
-
assertHttpBindingSecurity(httpHost);
|
|
444
|
-
let closingSessions;
|
|
445
|
-
async function closeAllSessions() {
|
|
446
|
-
if (closingSessions)
|
|
447
|
-
return closingSessions;
|
|
448
|
-
closingSessions = (async () => {
|
|
449
|
-
const activeSessions = [...sessions.values()];
|
|
450
|
-
sessions.clear();
|
|
451
|
-
eventStore.clear();
|
|
452
|
-
await Promise.allSettled(activeSessions.map((session) => session.close()));
|
|
453
|
-
})();
|
|
454
|
-
await closingSessions;
|
|
455
|
-
}
|
|
456
|
-
async function handlePostRequest(req, res, sessionId) {
|
|
457
|
-
if (sessionId) {
|
|
458
|
-
const session = getSessionOrRespondNotFound(sessions, sessionId, res);
|
|
459
|
-
if (!session) {
|
|
460
|
-
discardRequestBody(req);
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
const body = await readRequestBody(req);
|
|
464
|
-
await handleSessionTransportRequest(session, req, res, body);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
const body = await readRequestBody(req);
|
|
468
|
-
if (isInitializeRequest(body)) {
|
|
469
|
-
const maxSessions = parseEnvInt('FILESYSTEM_MCP_MAX_HTTP_SESSIONS', 100, 1, 10_000);
|
|
470
|
-
if (sessions.size >= maxSessions) {
|
|
471
|
-
sendJsonRpcError(res, 503, JSON_RPC_SERVER_ERROR, 'Too many sessions');
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
const session = await createHttpSession(options, sessions, eventStore);
|
|
475
|
-
await handleSessionTransportRequest(session, req, res, body);
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
sendJsonRpcError(res, 400, JSON_RPC_SERVER_ERROR, 'Bad Request: No valid session ID provided');
|
|
479
|
-
discardRequestBody(req);
|
|
480
|
-
}
|
|
481
|
-
async function handleGetDeleteRequest(req, res, sessionId) {
|
|
482
|
-
if (!sessionId) {
|
|
483
|
-
sendJsonRpcError(res, 400, JSON_RPC_SERVER_ERROR, 'Bad Request: Missing session ID');
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
const session = getSessionOrRespondNotFound(sessions, sessionId, res);
|
|
487
|
-
if (!session)
|
|
488
|
-
return;
|
|
489
|
-
await handleSessionTransportRequest(session, req, res);
|
|
490
|
-
}
|
|
491
|
-
async function dispatchMcpMethod(method, req, res, sessionId) {
|
|
492
|
-
switch (method) {
|
|
493
|
-
case 'POST':
|
|
494
|
-
await handlePostRequest(req, res, sessionId);
|
|
495
|
-
return;
|
|
496
|
-
case 'GET':
|
|
497
|
-
case 'DELETE':
|
|
498
|
-
await handleGetDeleteRequest(req, res, sessionId);
|
|
499
|
-
return;
|
|
500
|
-
default:
|
|
501
|
-
writeMethodNotAllowedResponse(res);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
async function handleMcpRequest(req, res) {
|
|
505
|
-
const sessionId = getSessionId(req);
|
|
506
|
-
if (!ensureAllowedOrigin(req, res))
|
|
507
|
-
return;
|
|
508
|
-
if (!ensureAllowedHostHeader(req, res, httpHost))
|
|
509
|
-
return;
|
|
510
|
-
if (!ensureAuthorizedRequest(req, res))
|
|
511
|
-
return;
|
|
512
|
-
try {
|
|
513
|
-
await dispatchMcpMethod(req.method, req, res, sessionId);
|
|
514
|
-
}
|
|
515
|
-
catch (error) {
|
|
516
|
-
handleHttpRequestError(error, res);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
const initHandshakeTimeoutMs = getInitHandshakeTimeoutMs();
|
|
520
|
-
const SWEEP_INTERVAL_MS = initHandshakeTimeoutMs * 2;
|
|
521
|
-
const sweepTimer = setInterval(() => {
|
|
522
|
-
const now = Date.now();
|
|
523
|
-
for (const [sessionId, session] of sessions) {
|
|
524
|
-
if (!session.rootsManager.isInitialized() &&
|
|
525
|
-
now - session.createdAt > initHandshakeTimeoutMs) {
|
|
526
|
-
Logger.warn(`[HTTP] Evicting stale session ${sessionId}: client never sent notifications/initialized`);
|
|
527
|
-
session.close().catch((err) => {
|
|
528
|
-
Logger.error(`[HTTP] Error closing stale session ${sessionId}:`, formatUnknownErrorMessage(err));
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}, SWEEP_INTERVAL_MS);
|
|
533
|
-
sweepTimer.unref();
|
|
534
|
-
const httpServer = createHttpServer((req, res) => {
|
|
535
|
-
const urlPath = (req.url ?? '/').split('?')[0];
|
|
536
|
-
if (urlPath === '/mcp') {
|
|
537
|
-
handleMcpRequest(req, res).catch((err) => {
|
|
538
|
-
Logger.error('[HTTP] Unhandled error in request handler:', formatUnknownErrorMessage(err));
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
else {
|
|
542
|
-
res.writeHead(404);
|
|
543
|
-
res.end('Not Found');
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
httpServer.once('close', () => {
|
|
547
|
-
clearInterval(sweepTimer);
|
|
548
|
-
});
|
|
549
|
-
const originalClose = httpServer.close.bind(httpServer);
|
|
550
|
-
httpServer.close = ((callback) => {
|
|
551
|
-
void closeAllSessions().catch((error) => {
|
|
552
|
-
Logger.error('[HTTP] Error closing sessions before server shutdown:', formatUnknownErrorMessage(error));
|
|
553
|
-
});
|
|
554
|
-
return originalClose(callback);
|
|
555
|
-
});
|
|
556
|
-
return new Promise((resolve, reject) => {
|
|
557
|
-
httpServer.once('error', reject);
|
|
558
|
-
httpServer.listen(port, httpHost, () => {
|
|
559
|
-
Logger.info(`MCP HTTP server listening on ${httpHost}:${port}`);
|
|
560
|
-
resolve(httpServer);
|
|
561
|
-
});
|
|
562
|
-
});
|
|
563
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { JSONRPCMessage } from '@modelcontextprotocol/server';
|
|
2
|
-
export declare class InMemoryEventStore {
|
|
3
|
-
private streams;
|
|
4
|
-
private eventIdToStreamId;
|
|
5
|
-
storeEvent(streamId: string, message: JSONRPCMessage): Promise<string>;
|
|
6
|
-
getStreamIdForEventId(eventId: string): Promise<string | undefined>;
|
|
7
|
-
replayEventsAfter(lastEventId: string, callbacks: {
|
|
8
|
-
send: (eventId: string, message: JSONRPCMessage) => Promise<void>;
|
|
9
|
-
}): Promise<string>;
|
|
10
|
-
/**
|
|
11
|
-
* Cleans up all events for a given streamId.
|
|
12
|
-
*/
|
|
13
|
-
delete(streamId: string): void;
|
|
14
|
-
/**
|
|
15
|
-
* Cleans up all streams.
|
|
16
|
-
*/
|
|
17
|
-
clear(): void;
|
|
18
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
const MAX_EVENTS_PER_STREAM = 1000;
|
|
3
|
-
export class InMemoryEventStore {
|
|
4
|
-
// Map of streamId -> StoredEvent[]
|
|
5
|
-
streams = new Map();
|
|
6
|
-
// Map of eventId -> streamId for fast lookup
|
|
7
|
-
eventIdToStreamId = new Map();
|
|
8
|
-
storeEvent(streamId, message) {
|
|
9
|
-
const eventId = randomUUID();
|
|
10
|
-
let stream = this.streams.get(streamId);
|
|
11
|
-
if (!stream) {
|
|
12
|
-
stream = [];
|
|
13
|
-
this.streams.set(streamId, stream);
|
|
14
|
-
}
|
|
15
|
-
// Add new event
|
|
16
|
-
stream.push({ id: eventId, message });
|
|
17
|
-
this.eventIdToStreamId.set(eventId, streamId);
|
|
18
|
-
// Enforce limits
|
|
19
|
-
if (stream.length > MAX_EVENTS_PER_STREAM) {
|
|
20
|
-
const removed = stream.shift();
|
|
21
|
-
if (removed) {
|
|
22
|
-
this.eventIdToStreamId.delete(removed.id);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return Promise.resolve(eventId);
|
|
26
|
-
}
|
|
27
|
-
getStreamIdForEventId(eventId) {
|
|
28
|
-
return Promise.resolve(this.eventIdToStreamId.get(eventId));
|
|
29
|
-
}
|
|
30
|
-
async replayEventsAfter(lastEventId, callbacks) {
|
|
31
|
-
const streamId = this.eventIdToStreamId.get(lastEventId);
|
|
32
|
-
if (!streamId) {
|
|
33
|
-
throw new Error(`Event ID ${lastEventId} not found or expired`);
|
|
34
|
-
}
|
|
35
|
-
const stream = this.streams.get(streamId);
|
|
36
|
-
if (!stream) {
|
|
37
|
-
throw new Error(`Stream ${streamId} not found`);
|
|
38
|
-
}
|
|
39
|
-
const eventIndex = stream.findIndex((e) => e.id === lastEventId);
|
|
40
|
-
if (eventIndex === -1) {
|
|
41
|
-
throw new Error(`Event ID ${lastEventId} not found in stream ${streamId}`);
|
|
42
|
-
}
|
|
43
|
-
// Replay all events after the found index
|
|
44
|
-
for (let i = eventIndex + 1; i < stream.length; i++) {
|
|
45
|
-
const event = stream[i];
|
|
46
|
-
if (event) {
|
|
47
|
-
await callbacks.send(event.id, event.message);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return streamId;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Cleans up all events for a given streamId.
|
|
54
|
-
*/
|
|
55
|
-
delete(streamId) {
|
|
56
|
-
const stream = this.streams.get(streamId);
|
|
57
|
-
if (stream) {
|
|
58
|
-
for (const event of stream) {
|
|
59
|
-
this.eventIdToStreamId.delete(event.id);
|
|
60
|
-
}
|
|
61
|
-
this.streams.delete(streamId);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Cleans up all streams.
|
|
66
|
-
*/
|
|
67
|
-
clear() {
|
|
68
|
-
this.streams.clear();
|
|
69
|
-
this.eventIdToStreamId.clear();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type McpServer } from '@modelcontextprotocol/server';
|
|
2
|
-
import { type LoggingState } from '../lib/logger.js';
|
|
3
|
-
import { type AllowedDirectoriesState } from '../lib/paths.js';
|
|
4
|
-
export interface ServerOptions {
|
|
5
|
-
allowCwd?: boolean;
|
|
6
|
-
cliAllowedDirs?: string[];
|
|
7
|
-
}
|
|
8
|
-
export declare class RootsManager {
|
|
9
|
-
private _debouncedUpdate;
|
|
10
|
-
private rootDirectories;
|
|
11
|
-
private allowedDirectoriesState;
|
|
12
|
-
private clientInitialized;
|
|
13
|
-
private initTimer;
|
|
14
|
-
private updatingRoots;
|
|
15
|
-
private pendingRootsUpdate;
|
|
16
|
-
private readonly options;
|
|
17
|
-
readonly loggingState: LoggingState;
|
|
18
|
-
constructor(options: ServerOptions, loggingState: LoggingState);
|
|
19
|
-
isInitialized(): boolean;
|
|
20
|
-
destroy(): void;
|
|
21
|
-
getAllowedDirectoriesState(): AllowedDirectoriesState;
|
|
22
|
-
logMissingDirectoriesIfNeeded(server: McpServer): void;
|
|
23
|
-
registerHandlers(server: McpServer, onInitTimeout?: () => void): void;
|
|
24
|
-
recomputeAllowedDirectories(): Promise<void>;
|
|
25
|
-
private scheduleRootsUpdate;
|
|
26
|
-
private logMissingDirectories;
|
|
27
|
-
private updateRootsFromClient;
|
|
28
|
-
}
|