@vinkius-core/mcp-fusion 2.6.0 → 2.7.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/dist/client/FusionClient.d.ts +122 -1
- package/dist/client/FusionClient.d.ts.map +1 -1
- package/dist/client/FusionClient.js +173 -11
- package/dist/client/FusionClient.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/core/StandardSchema.d.ts +178 -0
- package/dist/core/StandardSchema.d.ts.map +1 -0
- package/dist/core/StandardSchema.js +166 -0
- package/dist/core/StandardSchema.js.map +1 -0
- package/dist/core/createGroup.d.ts +140 -0
- package/dist/core/createGroup.d.ts.map +1 -0
- package/dist/core/createGroup.js +133 -0
- package/dist/core/createGroup.js.map +1 -0
- package/dist/core/execution/ExecutionPipeline.d.ts.map +1 -1
- package/dist/core/execution/ExecutionPipeline.js +6 -2
- package/dist/core/execution/ExecutionPipeline.js.map +1 -1
- package/dist/core/index.d.ts +7 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/initFusion.d.ts +201 -0
- package/dist/core/initFusion.d.ts.map +1 -0
- package/dist/core/initFusion.js +134 -0
- package/dist/core/initFusion.js.map +1 -0
- package/dist/core/response.d.ts +49 -2
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +27 -5
- package/dist/core/response.js.map +1 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/presenter/ZodDescriptionExtractor.d.ts +54 -0
- package/dist/presenter/ZodDescriptionExtractor.d.ts.map +1 -0
- package/dist/presenter/ZodDescriptionExtractor.js +131 -0
- package/dist/presenter/ZodDescriptionExtractor.js.map +1 -0
- package/dist/presenter/definePresenter.d.ts +172 -0
- package/dist/presenter/definePresenter.d.ts.map +1 -0
- package/dist/presenter/definePresenter.js +96 -0
- package/dist/presenter/definePresenter.js.map +1 -0
- package/dist/presenter/index.d.ts +3 -0
- package/dist/presenter/index.d.ts.map +1 -1
- package/dist/presenter/index.js +4 -0
- package/dist/presenter/index.js.map +1 -1
- package/dist/server/DevServer.d.ts +96 -0
- package/dist/server/DevServer.d.ts.map +1 -0
- package/dist/server/DevServer.js +187 -0
- package/dist/server/DevServer.js.map +1 -0
- package/dist/server/autoDiscover.d.ts +63 -0
- package/dist/server/autoDiscover.d.ts.map +1 -0
- package/dist/server/autoDiscover.js +157 -0
- package/dist/server/autoDiscover.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -0
- package/dist/server/index.js.map +1 -1
- package/dist/state-sync/PolicyValidator.d.ts +36 -0
- package/dist/state-sync/PolicyValidator.d.ts.map +1 -1
- package/dist/state-sync/PolicyValidator.js +35 -0
- package/dist/state-sync/PolicyValidator.js.map +1 -1
- package/dist/state-sync/ResponseDecorator.d.ts.map +1 -1
- package/dist/state-sync/ResponseDecorator.js +2 -1
- package/dist/state-sync/ResponseDecorator.js.map +1 -1
- package/dist/state-sync/StateSyncLayer.d.ts +5 -4
- package/dist/state-sync/StateSyncLayer.d.ts.map +1 -1
- package/dist/state-sync/StateSyncLayer.js +35 -4
- package/dist/state-sync/StateSyncLayer.js.map +1 -1
- package/dist/state-sync/index.d.ts +3 -1
- package/dist/state-sync/index.d.ts.map +1 -1
- package/dist/state-sync/index.js +1 -0
- package/dist/state-sync/index.js.map +1 -1
- package/dist/state-sync/types.d.ts +62 -0
- package/dist/state-sync/types.d.ts.map +1 -1
- package/package.json +38 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevServer — HMR-enabled MCP Development Server
|
|
3
|
+
*
|
|
4
|
+
* The "killer feature" for MCP DX: `npx mcp-fusion dev` starts a
|
|
5
|
+
* development server that watches for file changes and performs
|
|
6
|
+
* automatic Hot Module Replacement without requiring the LLM client
|
|
7
|
+
* (Claude Desktop, Cursor, etc.) to be restarted.
|
|
8
|
+
*
|
|
9
|
+
* ## How It Works
|
|
10
|
+
*
|
|
11
|
+
* 1. The dev server starts an MCP stdio server with your tools
|
|
12
|
+
* 2. When a `.ts`/`.js` file changes, the module cache is invalidated
|
|
13
|
+
* 3. Tools are re-registered from the updated modules
|
|
14
|
+
* 4. The MCP `notifications/tools/list_changed` notification is sent
|
|
15
|
+
* 5. The LLM client picks up the new tool definitions transparently
|
|
16
|
+
*
|
|
17
|
+
* ## Usage
|
|
18
|
+
*
|
|
19
|
+
* ```bash
|
|
20
|
+
* # Start dev server watching src/tools/ directory
|
|
21
|
+
* npx mcp-fusion dev --dir ./src/tools --entry ./src/server.ts
|
|
22
|
+
*
|
|
23
|
+
* # With specific file pattern
|
|
24
|
+
* npx mcp-fusion dev --dir ./src/tools --pattern "*.tool.ts"
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Using the programmatic API
|
|
30
|
+
* import { createDevServer } from '@vinkius-core/mcp-fusion/dev';
|
|
31
|
+
*
|
|
32
|
+
* const devServer = createDevServer({
|
|
33
|
+
* dir: './src/tools',
|
|
34
|
+
* setup: async (registry) => {
|
|
35
|
+
* // Your setup logic — called on every reload
|
|
36
|
+
* await autoDiscover(registry, './src/tools');
|
|
37
|
+
* },
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* await devServer.start();
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @module
|
|
44
|
+
*/
|
|
45
|
+
import { watch } from 'node:fs';
|
|
46
|
+
import { resolve, join, relative } from 'node:path';
|
|
47
|
+
import { pathToFileURL } from 'node:url';
|
|
48
|
+
// ── Module Cache Invalidation ────────────────────────────
|
|
49
|
+
/**
|
|
50
|
+
* Invalidate Node.js ESM module cache for a given file.
|
|
51
|
+
*
|
|
52
|
+
* ESM modules are cached by URL. We use an import timestamp trick
|
|
53
|
+
* to force re-evaluation on next import.
|
|
54
|
+
*
|
|
55
|
+
* For CJS, we clear `require.cache` if available.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
function invalidateModule(filePath) {
|
|
60
|
+
const absolutePath = resolve(filePath);
|
|
61
|
+
// CJS cache invalidation (when running in CJS mode)
|
|
62
|
+
if (typeof require !== 'undefined' && require.cache) {
|
|
63
|
+
delete require.cache[absolutePath];
|
|
64
|
+
}
|
|
65
|
+
// ESM modules can't be uncached directly — the caller must
|
|
66
|
+
// re-import with a cache-busting query parameter.
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create a cache-busting import URL for ESM modules.
|
|
70
|
+
* Appends a timestamp query to force the module to be re-evaluated.
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
73
|
+
function cacheBustUrl(filePath) {
|
|
74
|
+
const url = pathToFileURL(resolve(filePath));
|
|
75
|
+
url.searchParams.set('t', String(Date.now()));
|
|
76
|
+
return url.href;
|
|
77
|
+
}
|
|
78
|
+
// ── Dev Server Factory ───────────────────────────────────
|
|
79
|
+
/**
|
|
80
|
+
* Create an HMR-enabled MCP development server.
|
|
81
|
+
*
|
|
82
|
+
* Watches a directory for file changes and automatically reloads
|
|
83
|
+
* tools, then notifies the connected MCP client via the native
|
|
84
|
+
* `notifications/tools/list_changed` notification.
|
|
85
|
+
*
|
|
86
|
+
* @param config - Dev server configuration
|
|
87
|
+
* @returns A {@link DevServer} instance with start/stop/reload controls
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* import { createDevServer, autoDiscover, ToolRegistry } from '@vinkius-core/mcp-fusion';
|
|
92
|
+
*
|
|
93
|
+
* const devServer = createDevServer({
|
|
94
|
+
* dir: './src/tools',
|
|
95
|
+
* setup: async (registry) => {
|
|
96
|
+
* await autoDiscover(registry, './src/tools');
|
|
97
|
+
* },
|
|
98
|
+
* onReload: (file) => console.log(`[HMR] Reloaded: ${file}`),
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* await devServer.start();
|
|
102
|
+
* // File changes → auto-reload → LLM client gets notification
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export function createDevServer(config) {
|
|
106
|
+
const { dir, extensions = ['.ts', '.js', '.mjs', '.mts'], debounce = 300, setup, onReload, server, } = config;
|
|
107
|
+
const absoluteDir = resolve(dir);
|
|
108
|
+
let watcher;
|
|
109
|
+
let debounceTimer;
|
|
110
|
+
let reloadCount = 0;
|
|
111
|
+
/**
|
|
112
|
+
* Perform a full reload: clear caches, re-run setup, notify MCP client.
|
|
113
|
+
*/
|
|
114
|
+
async function performReload(changedFile) {
|
|
115
|
+
reloadCount++;
|
|
116
|
+
// Invalidate CJS cache for all watched files
|
|
117
|
+
invalidateModule(changedFile);
|
|
118
|
+
// Re-run the user's setup callback
|
|
119
|
+
// The user is responsible for creating a fresh registry or clearing it
|
|
120
|
+
try {
|
|
121
|
+
await setup({});
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.error(`[mcp-fusion dev] Reload failed: ${message}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// Notify MCP client about tool list changes
|
|
130
|
+
if (server) {
|
|
131
|
+
const notification = { method: 'notifications/tools/list_changed' };
|
|
132
|
+
try {
|
|
133
|
+
if (typeof server.sendNotification === 'function') {
|
|
134
|
+
await server.sendNotification(notification);
|
|
135
|
+
}
|
|
136
|
+
else if (typeof server.notification === 'function') {
|
|
137
|
+
await server.notification(notification);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Connection might not be established yet — ignore
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// User callback
|
|
145
|
+
onReload?.(changedFile);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
async start() {
|
|
149
|
+
// Perform initial load
|
|
150
|
+
await performReload('(initial)');
|
|
151
|
+
// Start watching
|
|
152
|
+
const watchOptions = { recursive: true };
|
|
153
|
+
watcher = watch(absoluteDir, watchOptions, (_eventType, filename) => {
|
|
154
|
+
if (!filename)
|
|
155
|
+
return;
|
|
156
|
+
// Filter by extension
|
|
157
|
+
const ext = '.' + filename.split('.').pop();
|
|
158
|
+
if (!extensions.includes(ext))
|
|
159
|
+
return;
|
|
160
|
+
// Skip test/spec files
|
|
161
|
+
if (/\.(test|spec|d)\./.test(filename))
|
|
162
|
+
return;
|
|
163
|
+
// Debounce rapid changes
|
|
164
|
+
if (debounceTimer)
|
|
165
|
+
clearTimeout(debounceTimer);
|
|
166
|
+
debounceTimer = setTimeout(() => {
|
|
167
|
+
const fullPath = join(absoluteDir, filename);
|
|
168
|
+
void performReload(relative(process.cwd(), fullPath));
|
|
169
|
+
}, debounce);
|
|
170
|
+
});
|
|
171
|
+
// eslint-disable-next-line no-console
|
|
172
|
+
console.log(`[mcp-fusion dev] Watching ${relative(process.cwd(), absoluteDir)} for changes...`);
|
|
173
|
+
},
|
|
174
|
+
stop() {
|
|
175
|
+
if (debounceTimer)
|
|
176
|
+
clearTimeout(debounceTimer);
|
|
177
|
+
watcher?.close();
|
|
178
|
+
watcher = undefined;
|
|
179
|
+
// eslint-disable-next-line no-console
|
|
180
|
+
console.log(`[mcp-fusion dev] Stopped. ${reloadCount} reload(s) performed.`);
|
|
181
|
+
},
|
|
182
|
+
async reload(reason) {
|
|
183
|
+
await performReload(reason ?? '(manual)');
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=DevServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DevServer.js","sourceRoot":"","sources":["../../src/server/DevServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Duck-typed ToolBuilder — avoids circular imports */
|
|
2
|
+
interface ToolBuilderLike {
|
|
3
|
+
getName(): string;
|
|
4
|
+
buildToolDefinition?(): unknown;
|
|
5
|
+
}
|
|
6
|
+
/** Duck-typed ToolRegistry — avoids circular imports */
|
|
7
|
+
interface ToolRegistryLike {
|
|
8
|
+
register(builder: ToolBuilderLike): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Options for `autoDiscover()`.
|
|
12
|
+
*/
|
|
13
|
+
export interface AutoDiscoverOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Regex pattern to filter files. Only files matching this pattern
|
|
16
|
+
* are imported. Default: matches `.ts`, `.js`, `.mjs`, `.mts` files,
|
|
17
|
+
* excluding `.test.`, `.spec.`, and `.d.ts` files.
|
|
18
|
+
*/
|
|
19
|
+
pattern?: RegExp;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to recurse into subdirectories.
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
recursive?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Module resolution style:
|
|
27
|
+
* - `'esm'` — Uses dynamic `import()` (default for ESM projects)
|
|
28
|
+
* - `'cjs'` — Uses `require()` (for CommonJS projects)
|
|
29
|
+
*
|
|
30
|
+
* @default 'esm'
|
|
31
|
+
*/
|
|
32
|
+
loader?: 'esm' | 'cjs';
|
|
33
|
+
/**
|
|
34
|
+
* Custom export resolver. When provided, this function is called
|
|
35
|
+
* with the module's exports and must return the tool builder(s).
|
|
36
|
+
*
|
|
37
|
+
* Default behavior: looks for `default` export or named `tool` export.
|
|
38
|
+
*/
|
|
39
|
+
resolve?: (mod: Record<string, unknown>) => ToolBuilderLike | ToolBuilderLike[] | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Scan a directory and auto-register all discovered tool builders.
|
|
43
|
+
*
|
|
44
|
+
* Eliminates the need for a central `index.ts` that manually imports
|
|
45
|
+
* and registers every tool. New tools are automatically picked up
|
|
46
|
+
* when they are dropped into the scanned directory.
|
|
47
|
+
*
|
|
48
|
+
* @param registry - A ToolRegistry instance to register discovered tools
|
|
49
|
+
* @param dir - Path to the tools directory (absolute or relative to CWD)
|
|
50
|
+
* @param options - Discovery options (pattern, recursive, loader, resolve)
|
|
51
|
+
* @returns Array of discovered file paths (for logging/debugging)
|
|
52
|
+
* @throws If the directory does not exist or is not readable
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const registry = new ToolRegistry<AppContext>();
|
|
57
|
+
* const files = await autoDiscover(registry, './src/tools');
|
|
58
|
+
* console.log(`Discovered ${files.length} tool files`);
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function autoDiscover(registry: ToolRegistryLike, dir: string, options?: AutoDiscoverOptions): Promise<string[]>;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=autoDiscover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoDiscover.d.ts","sourceRoot":"","sources":["../../src/server/autoDiscover.ts"],"names":[],"mappings":"AAkDA,uDAAuD;AACvD,UAAU,eAAe;IACrB,OAAO,IAAI,MAAM,CAAC;IAClB,mBAAmB,CAAC,IAAI,OAAO,CAAC;CACnC;AAED,wDAAwD;AACxD,UAAU,gBAAgB;IACtB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAEvB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,eAAe,GAAG,eAAe,EAAE,GAAG,SAAS,CAAC;CAC/F;AAmED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,YAAY,CAC9B,QAAQ,EAAE,gBAAgB,EAC1B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,mBAAwB,GAClC,OAAO,CAAC,MAAM,EAAE,CAAC,CA6CnB"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-Based Routing — Auto-Discovery of Tools via Directory Structure
|
|
3
|
+
*
|
|
4
|
+
* Scans a directory at startup and auto-registers tools based on the
|
|
5
|
+
* file-system naming convention:
|
|
6
|
+
*
|
|
7
|
+
* ```
|
|
8
|
+
* src/tools/
|
|
9
|
+
* ├── billing/
|
|
10
|
+
* │ ├── get_invoice.ts → billing.get_invoice
|
|
11
|
+
* │ └── pay.ts → billing.pay
|
|
12
|
+
* └── users/
|
|
13
|
+
* ├── list.ts → users.list
|
|
14
|
+
* └── ban.ts → users.ban
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Each file must export a `GroupedToolBuilder` (via `defineTool()`,
|
|
18
|
+
* `createTool()`, `f.tool()`, etc.) as the **default export** or
|
|
19
|
+
* a named export called `tool`.
|
|
20
|
+
*
|
|
21
|
+
* **Benefits:**
|
|
22
|
+
* - Zero code orchestration — no central `index.ts` with 50 imports
|
|
23
|
+
* - Git-friendly — reduced merge conflicts (no shared import file)
|
|
24
|
+
* - Instant onboarding — "drop a file → it's a tool"
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { autoDiscover, ToolRegistry } from '@vinkius-core/mcp-fusion';
|
|
29
|
+
*
|
|
30
|
+
* const registry = new ToolRegistry<AppContext>();
|
|
31
|
+
*
|
|
32
|
+
* // Scan src/tools/ and register everything
|
|
33
|
+
* await autoDiscover(registry, './src/tools');
|
|
34
|
+
*
|
|
35
|
+
* // Or with options
|
|
36
|
+
* await autoDiscover(registry, './src/tools', {
|
|
37
|
+
* pattern: /\.tool\.ts$/, // only files ending in .tool.ts
|
|
38
|
+
* recursive: true, // scan subdirectories (default: true)
|
|
39
|
+
* separator: '.', // directory separator for tool names
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @module
|
|
44
|
+
*/
|
|
45
|
+
import { promises as fs } from 'node:fs';
|
|
46
|
+
import { join, resolve, relative, basename, extname, sep } from 'node:path';
|
|
47
|
+
import { pathToFileURL } from 'node:url';
|
|
48
|
+
// ── Internal ─────────────────────────────────────────────
|
|
49
|
+
const DEFAULT_PATTERN = /\.(ts|js|mjs|mts)$/;
|
|
50
|
+
const EXCLUDED_PATTERN = /\.(test|spec|d)\./;
|
|
51
|
+
/**
|
|
52
|
+
* Walk a directory tree and collect file paths matching the filter.
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
async function walkDir(dir, pattern, recursive) {
|
|
56
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
57
|
+
const files = [];
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const fullPath = join(dir, entry.name);
|
|
60
|
+
if (entry.isDirectory() && recursive) {
|
|
61
|
+
const nested = await walkDir(fullPath, pattern, recursive);
|
|
62
|
+
files.push(...nested);
|
|
63
|
+
}
|
|
64
|
+
else if (entry.isFile() && pattern.test(entry.name) && !EXCLUDED_PATTERN.test(entry.name)) {
|
|
65
|
+
files.push(fullPath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return files;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if a value looks like a ToolBuilder (duck typing).
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
function isToolBuilder(value) {
|
|
75
|
+
return (typeof value === 'object' &&
|
|
76
|
+
value !== null &&
|
|
77
|
+
typeof value.getName === 'function');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Default export resolver: check default → tool → any exported builder.
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
function defaultResolver(mod) {
|
|
84
|
+
// Priority 1: default export
|
|
85
|
+
const defaultExport = mod['default'];
|
|
86
|
+
if (isToolBuilder(defaultExport))
|
|
87
|
+
return defaultExport;
|
|
88
|
+
// Priority 2: named `tool` export
|
|
89
|
+
const toolExport = mod['tool'];
|
|
90
|
+
if (isToolBuilder(toolExport))
|
|
91
|
+
return toolExport;
|
|
92
|
+
// Priority 3: collect all exported builders
|
|
93
|
+
const builders = [];
|
|
94
|
+
for (const value of Object.values(mod)) {
|
|
95
|
+
if (isToolBuilder(value)) {
|
|
96
|
+
builders.push(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return builders.length > 0 ? builders : undefined;
|
|
100
|
+
}
|
|
101
|
+
// ── Public API ───────────────────────────────────────────
|
|
102
|
+
/**
|
|
103
|
+
* Scan a directory and auto-register all discovered tool builders.
|
|
104
|
+
*
|
|
105
|
+
* Eliminates the need for a central `index.ts` that manually imports
|
|
106
|
+
* and registers every tool. New tools are automatically picked up
|
|
107
|
+
* when they are dropped into the scanned directory.
|
|
108
|
+
*
|
|
109
|
+
* @param registry - A ToolRegistry instance to register discovered tools
|
|
110
|
+
* @param dir - Path to the tools directory (absolute or relative to CWD)
|
|
111
|
+
* @param options - Discovery options (pattern, recursive, loader, resolve)
|
|
112
|
+
* @returns Array of discovered file paths (for logging/debugging)
|
|
113
|
+
* @throws If the directory does not exist or is not readable
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const registry = new ToolRegistry<AppContext>();
|
|
118
|
+
* const files = await autoDiscover(registry, './src/tools');
|
|
119
|
+
* console.log(`Discovered ${files.length} tool files`);
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export async function autoDiscover(registry, dir, options = {}) {
|
|
123
|
+
const { pattern = DEFAULT_PATTERN, recursive = true, loader = 'esm', resolve: customResolve, } = options;
|
|
124
|
+
const absoluteDir = resolve(dir);
|
|
125
|
+
const files = await walkDir(absoluteDir, pattern, recursive);
|
|
126
|
+
const discoveredFiles = [];
|
|
127
|
+
for (const filePath of files) {
|
|
128
|
+
try {
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
let mod;
|
|
131
|
+
if (loader === 'cjs') {
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
133
|
+
mod = require(filePath);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
137
|
+
mod = await import(fileUrl);
|
|
138
|
+
}
|
|
139
|
+
const resolver = customResolve ?? defaultResolver;
|
|
140
|
+
const result = resolver(mod);
|
|
141
|
+
if (!result)
|
|
142
|
+
continue;
|
|
143
|
+
const builders = Array.isArray(result) ? result : [result];
|
|
144
|
+
for (const builder of builders) {
|
|
145
|
+
registry.register(builder);
|
|
146
|
+
}
|
|
147
|
+
discoveredFiles.push(filePath);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Skip files that fail to import (syntax errors, missing deps, etc.)
|
|
151
|
+
// In production, this should be logged via the observability layer
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return discoveredFiles;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=autoDiscover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoDiscover.js","sourceRoot":"","sources":["../../src/server/autoDiscover.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -3,4 +3,8 @@ export { resolveServer } from './ServerResolver.js';
|
|
|
3
3
|
export { attachToServer, type AttachOptions, type DetachFn, type RegistryDelegate, } from './ServerAttachment.js';
|
|
4
4
|
export type { ToolExposition, ExpositionConfig } from '../exposition/index.js';
|
|
5
5
|
export { compileExposition, type FlatRoute, type ExpositionResult } from '../exposition/index.js';
|
|
6
|
+
export { autoDiscover } from './autoDiscover.js';
|
|
7
|
+
export type { AutoDiscoverOptions } from './autoDiscover.js';
|
|
8
|
+
export { createDevServer } from './DevServer.js';
|
|
9
|
+
export type { DevServerConfig, DevServer } from './DevServer.js';
|
|
6
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACH,cAAc,EACd,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,GAC3D,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACH,cAAc,EACd,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,GAC3D,MAAM,uBAAuB,CAAC;AAG/B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAGlG,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -2,4 +2,8 @@
|
|
|
2
2
|
export { resolveServer } from './ServerResolver.js';
|
|
3
3
|
export { attachToServer, } from './ServerAttachment.js';
|
|
4
4
|
export { compileExposition } from '../exposition/index.js';
|
|
5
|
+
// ── File-Based Routing ───────────────────────────────────
|
|
6
|
+
export { autoDiscover } from './autoDiscover.js';
|
|
7
|
+
// ── Dev Server (HMR) ────────────────────────────────────
|
|
8
|
+
export { createDevServer } from './DevServer.js';
|
|
5
9
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACH,cAAc,GAEjB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EAAE,iBAAiB,EAAyC,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACH,cAAc,GAEjB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EAAE,iBAAiB,EAAyC,MAAM,wBAAwB,CAAC;AAElG,4DAA4D;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,2DAA2D;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -28,4 +28,40 @@ export declare function validatePolicies(policies: readonly SyncPolicy[]): void;
|
|
|
28
28
|
export declare function validateDefaults(defaults?: {
|
|
29
29
|
readonly cacheControl?: CacheDirective;
|
|
30
30
|
}): void;
|
|
31
|
+
/**
|
|
32
|
+
* Warning produced when two policies potentially overlap.
|
|
33
|
+
*
|
|
34
|
+
* Overlapping policies are not an error (first-match-wins is deterministic),
|
|
35
|
+
* but they can cause subtle configuration bugs when the user expects a
|
|
36
|
+
* more-specific policy to fire but a broader one shadows it.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const warnings = detectOverlaps(policies);
|
|
41
|
+
* warnings.forEach(w => console.warn(`[StateSync] ${w.message}`));
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export interface OverlapWarning {
|
|
45
|
+
/** Human-readable description of the overlap. */
|
|
46
|
+
readonly message: string;
|
|
47
|
+
/** Index of the shadowing (earlier) policy. */
|
|
48
|
+
readonly shadowingIndex: number;
|
|
49
|
+
/** Index of the shadowed (later) policy. */
|
|
50
|
+
readonly shadowedIndex: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Detect potentially overlapping glob policies.
|
|
54
|
+
*
|
|
55
|
+
* Checks if a broader policy at index `i` could shadow a more-specific
|
|
56
|
+
* policy at index `j > i`. Uses GlobMatcher to test if the earlier
|
|
57
|
+
* pattern matches the later pattern's literal segments.
|
|
58
|
+
*
|
|
59
|
+
* This is a heuristic: it only catches cases where the later policy's
|
|
60
|
+
* `match` string (treated as a literal tool name) would match the
|
|
61
|
+
* earlier policy's glob. It does NOT do full set-intersection analysis.
|
|
62
|
+
*
|
|
63
|
+
* @param policies - The policies array to analyze
|
|
64
|
+
* @returns Array of overlap warnings (empty if no overlaps detected)
|
|
65
|
+
*/
|
|
66
|
+
export declare function detectOverlaps(policies: readonly SyncPolicy[]): readonly OverlapWarning[];
|
|
31
67
|
//# sourceMappingURL=PolicyValidator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PolicyValidator.d.ts","sourceRoot":"","sources":["../../src/state-sync/PolicyValidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"PolicyValidator.d.ts","sourceRoot":"","sources":["../../src/state-sync/PolicyValidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK7D,8DAA8D;AAC9D,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAsC,CAAC;AAiExF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,GAAG,IAAI,CAetE;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC5B,QAAQ,CAAC,EAAE;IAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAA;CAAE,GACtD,IAAI,CAON;AAID;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,4CAA4C;IAC5C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,GAAG,SAAS,cAAc,EAAE,CAuBzF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { matchGlob } from './GlobMatcher.js';
|
|
1
2
|
// ── Constants ────────────────────────────────────────────
|
|
2
3
|
/** Valid cache directives — binary vocabulary, no max-age. */
|
|
3
4
|
export const VALID_DIRECTIVES = new Set(['no-store', 'immutable']);
|
|
@@ -84,4 +85,38 @@ export function validateDefaults(defaults) {
|
|
|
84
85
|
`Allowed: "no-store", "immutable".`);
|
|
85
86
|
}
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Detect potentially overlapping glob policies.
|
|
90
|
+
*
|
|
91
|
+
* Checks if a broader policy at index `i` could shadow a more-specific
|
|
92
|
+
* policy at index `j > i`. Uses GlobMatcher to test if the earlier
|
|
93
|
+
* pattern matches the later pattern's literal segments.
|
|
94
|
+
*
|
|
95
|
+
* This is a heuristic: it only catches cases where the later policy's
|
|
96
|
+
* `match` string (treated as a literal tool name) would match the
|
|
97
|
+
* earlier policy's glob. It does NOT do full set-intersection analysis.
|
|
98
|
+
*
|
|
99
|
+
* @param policies - The policies array to analyze
|
|
100
|
+
* @returns Array of overlap warnings (empty if no overlaps detected)
|
|
101
|
+
*/
|
|
102
|
+
export function detectOverlaps(policies) {
|
|
103
|
+
const warnings = [];
|
|
104
|
+
for (let i = 0; i < policies.length; i++) {
|
|
105
|
+
for (let j = i + 1; j < policies.length; j++) {
|
|
106
|
+
const earlier = policies[i].match;
|
|
107
|
+
const later = policies[j].match;
|
|
108
|
+
// If the later policy's match pattern (as a literal name)
|
|
109
|
+
// would be caught by the earlier policy's glob, it's shadowed.
|
|
110
|
+
if (matchGlob(earlier, later)) {
|
|
111
|
+
warnings.push({
|
|
112
|
+
message: `policy[${i}] (match: "${earlier}") shadows policy[${j}] (match: "${later}"). ` +
|
|
113
|
+
`The later policy will never match because first-match-wins applies.`,
|
|
114
|
+
shadowingIndex: i,
|
|
115
|
+
shadowedIndex: j,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return warnings;
|
|
121
|
+
}
|
|
87
122
|
//# sourceMappingURL=PolicyValidator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PolicyValidator.js","sourceRoot":"","sources":["../../src/state-sync/PolicyValidator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PolicyValidator.js","sourceRoot":"","sources":["../../src/state-sync/PolicyValidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResponseDecorator.d.ts","sourceRoot":"","sources":["../../src/state-sync/ResponseDecorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"ResponseDecorator.d.ts","sourceRoot":"","sources":["../../src/state-sync/ResponseDecorator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC5B,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,EAAE,MAAM,GACjB,YAAY,CAUd"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { escapeXmlAttr } from '../core/response.js';
|
|
1
2
|
/**
|
|
2
3
|
* Prepend a System invalidation content block to a tool call response.
|
|
3
4
|
*
|
|
@@ -11,7 +12,7 @@ export function decorateResponse(result, patterns, causedBy) {
|
|
|
11
12
|
return {
|
|
12
13
|
...result,
|
|
13
14
|
content: [
|
|
14
|
-
{ type: 'text', text: `<cache_invalidation cause="${causedBy}" domains="${domains}" />` },
|
|
15
|
+
{ type: 'text', text: `<cache_invalidation cause="${escapeXmlAttr(causedBy)}" domains="${escapeXmlAttr(domains)}" />` },
|
|
15
16
|
...result.content,
|
|
16
17
|
],
|
|
17
18
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResponseDecorator.js","sourceRoot":"","sources":["../../src/state-sync/ResponseDecorator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ResponseDecorator.js","sourceRoot":"","sources":["../../src/state-sync/ResponseDecorator.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC5B,MAAoB,EACpB,QAA2B,EAC3B,QAAgB;IAEhB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEpC,OAAO;QACH,GAAG,MAAM;QACT,OAAO,EAAE;YACL,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,8BAA8B,aAAa,CAAC,QAAQ,CAAC,cAAc,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE;YAChI,GAAG,MAAM,CAAC,OAAO;SACpB;KACJ,CAAC;AACN,CAAC"}
|
|
@@ -40,6 +40,8 @@ import type { ToolResponse } from '../core/response.js';
|
|
|
40
40
|
import type { StateSyncConfig } from './types.js';
|
|
41
41
|
export declare class StateSyncLayer {
|
|
42
42
|
private readonly _engine;
|
|
43
|
+
private readonly _onInvalidation?;
|
|
44
|
+
private readonly _notificationSink?;
|
|
43
45
|
/**
|
|
44
46
|
* Per-tool-name cache of decorated McpTool objects.
|
|
45
47
|
*
|
|
@@ -49,10 +51,9 @@ export declare class StateSyncLayer {
|
|
|
49
51
|
* on every `tools/list` request — which is the hottest path since
|
|
50
52
|
* it runs at the start of every LLM conversation.
|
|
51
53
|
*
|
|
52
|
-
* The cache key is the tool name
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* hit rate approaches 100%.
|
|
54
|
+
* The cache key is the tool name — tool definitions are immutable
|
|
55
|
+
* after registration, so the name alone is sufficient.
|
|
56
|
+
* Cache hit rate approaches 100%.
|
|
56
57
|
*/
|
|
57
58
|
private readonly _decoratedToolCache;
|
|
58
59
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StateSyncLayer.d.ts","sourceRoot":"","sources":["../../src/state-sync/StateSyncLayer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,KAAK,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"StateSyncLayer.d.ts","sourceRoot":"","sources":["../../src/state-sync/StateSyncLayer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,KAAK,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAA2C,MAAM,YAAY,CAAC;AAM3F,qBAAa,cAAc;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAmD;IACpF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAA6E;IAEhH;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA8B;IAElE;;;;;;;;;OASG;gBACS,MAAM,EAAE,eAAe;IAMnC;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE;IAI1C;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,YAAY;IAuCpE;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;CAQ9B"}
|