cwtools-mcp 0.1.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 +279 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +76 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +212 -0
- package/dist/cwtools-mcp.cjs +49480 -0
- package/dist/hosts/lspProcessHost.d.ts +49 -0
- package/dist/hosts/lspProcessHost.js +412 -0
- package/dist/hosts/nodeHostServices.d.ts +3 -0
- package/dist/hosts/nodeHostServices.js +506 -0
- package/dist/hosts/projectDetect.d.ts +7 -0
- package/dist/hosts/projectDetect.js +151 -0
- package/dist/hosts/projectSettings.d.ts +11 -0
- package/dist/hosts/projectSettings.js +265 -0
- package/dist/hosts/revalidation.d.ts +12 -0
- package/dist/hosts/revalidation.js +157 -0
- package/dist/hosts/vscodeCache.d.ts +5 -0
- package/dist/hosts/vscodeCache.js +153 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +25 -0
- package/dist/mcp/bridgeProxy.d.ts +4 -0
- package/dist/mcp/bridgeProxy.js +281 -0
- package/dist/mcp/resources.d.ts +15 -0
- package/dist/mcp/resources.js +151 -0
- package/dist/mcp/toolHandlers.d.ts +9 -0
- package/dist/mcp/toolHandlers.js +58 -0
- package/dist/mcp/toolRegistrar.d.ts +2 -0
- package/dist/mcp/toolRegistrar.js +7 -0
- package/dist/mcp/transportHttp.d.ts +7 -0
- package/dist/mcp/transportHttp.js +101 -0
- package/dist/mcp/transportStdio.d.ts +2 -0
- package/dist/mcp/transportStdio.js +8 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +85 -0
- package/package.json +46 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createBridgeProxyMcpServer = createBridgeProxyMcpServer;
|
|
37
|
+
exports.assertBridgeWorkspaceMatches = assertBridgeWorkspaceMatches;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const url_1 = require("url");
|
|
41
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
42
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
43
|
+
const vscodeCache_1 = require("../hosts/vscodeCache");
|
|
44
|
+
const resources_1 = require("./resources");
|
|
45
|
+
const toolRegistrar_1 = require("./toolRegistrar");
|
|
46
|
+
const toolHandlers_1 = require("./toolHandlers");
|
|
47
|
+
const BRIDGE_MANIFEST_FILE = 'bridge-manifest.json';
|
|
48
|
+
const BRIDGE_PROTOCOL_VERSION = 1;
|
|
49
|
+
function createBridgeProxyMcpServer(config) {
|
|
50
|
+
const server = new index_js_1.Server({
|
|
51
|
+
name: 'cwtools-mcp',
|
|
52
|
+
version: '0.1.0',
|
|
53
|
+
}, {
|
|
54
|
+
capabilities: {
|
|
55
|
+
resources: {},
|
|
56
|
+
tools: {},
|
|
57
|
+
},
|
|
58
|
+
instructions: [
|
|
59
|
+
'CWTools MCP is served by the active VS Code-compatible extension host.',
|
|
60
|
+
'This proxy does not start its own CWTools language server. If calls report',
|
|
61
|
+
'bridge_unavailable, open the project in the compatible host where the extension',
|
|
62
|
+
'is installed and active, or rerun with --standalone to use the legacy isolated mode.',
|
|
63
|
+
].join('\n'),
|
|
64
|
+
});
|
|
65
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
66
|
+
try {
|
|
67
|
+
return await bridgeRpc(config, server, 'tools/list', {});
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { tools: (0, toolRegistrar_1.listRegisteredTools)() };
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
|
74
|
+
try {
|
|
75
|
+
return await bridgeRpc(config, server, 'resources/list', {});
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { resources: (0, resources_1.listResources)() };
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
|
|
82
|
+
try {
|
|
83
|
+
return await bridgeRpc(config, server, 'resources/read', {
|
|
84
|
+
uri: request.params.uri,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
contents: [
|
|
90
|
+
{
|
|
91
|
+
uri: request.params.uri,
|
|
92
|
+
mimeType: 'application/json',
|
|
93
|
+
text: `${JSON.stringify(unavailableResult(error), null, 2)}\n`,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
100
|
+
try {
|
|
101
|
+
const args = request.params.arguments && typeof request.params.arguments === 'object'
|
|
102
|
+
? request.params.arguments
|
|
103
|
+
: {};
|
|
104
|
+
const result = await bridgeRpc(config, server, 'tools/call', {
|
|
105
|
+
name: request.params.name,
|
|
106
|
+
arguments: args,
|
|
107
|
+
});
|
|
108
|
+
return normalizeCallToolResult(result);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return (0, toolHandlers_1.toMcpCallToolResult)(unavailableResult(error));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return server;
|
|
115
|
+
}
|
|
116
|
+
async function bridgeRpc(config, server, method, params) {
|
|
117
|
+
const manifest = await readBridgeManifest(config, server);
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
const timer = setTimeout(() => controller.abort(), 30000);
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetch(manifest.rpcUrl, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: {
|
|
124
|
+
'authorization': `Bearer ${manifest.token}`,
|
|
125
|
+
'content-type': 'application/json',
|
|
126
|
+
},
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
jsonrpc: '2.0',
|
|
129
|
+
id: `${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
130
|
+
method,
|
|
131
|
+
params,
|
|
132
|
+
}),
|
|
133
|
+
signal: controller.signal,
|
|
134
|
+
});
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
throw new Error(`Bridge HTTP ${response.status}: ${await response.text()}`);
|
|
137
|
+
}
|
|
138
|
+
const payload = await response.json();
|
|
139
|
+
if (payload.error) {
|
|
140
|
+
throw new Error(payload.error.message ?? `Bridge JSON-RPC error ${payload.error.code ?? ''}`.trim());
|
|
141
|
+
}
|
|
142
|
+
return payload.result;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function readBridgeManifest(config, server) {
|
|
149
|
+
const manifestPath = resolveBridgeManifestPath(config);
|
|
150
|
+
let parsed;
|
|
151
|
+
try {
|
|
152
|
+
parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw new Error(`CWTools MCP bridge is unavailable: could not read ${manifestPath}. ` +
|
|
156
|
+
`Open this project in a VS Code-compatible host with the extension active, or pass --standalone to start a separate CWTools server. ` +
|
|
157
|
+
`Details: ${error instanceof Error ? error.message : String(error)}`);
|
|
158
|
+
}
|
|
159
|
+
if (parsed.kind !== 'cwtools-mcp-extension-bridge' || parsed.protocolVersion !== BRIDGE_PROTOCOL_VERSION) {
|
|
160
|
+
throw new Error(`CWTools MCP bridge manifest at ${manifestPath} is not compatible with this proxy. ` +
|
|
161
|
+
`Open the project in the updated extension host, or pass --standalone for legacy isolated mode.`);
|
|
162
|
+
}
|
|
163
|
+
if (!parsed.rpcUrl || !parsed.token) {
|
|
164
|
+
throw new Error(`CWTools MCP bridge manifest at ${manifestPath} is missing rpcUrl/token.`);
|
|
165
|
+
}
|
|
166
|
+
const expected = await resolveExpectedWorkspace(config, server);
|
|
167
|
+
assertBridgeWorkspaceMatches(expected.roots, parsed.workspaceRoot, expected.source);
|
|
168
|
+
return parsed;
|
|
169
|
+
}
|
|
170
|
+
async function resolveExpectedWorkspace(config, server) {
|
|
171
|
+
if (config.workspaceRootExplicit) {
|
|
172
|
+
return { roots: [config.workspaceRoot], source: '--workspace' };
|
|
173
|
+
}
|
|
174
|
+
const clientRoots = await readClientWorkspaceRoots(server);
|
|
175
|
+
if (clientRoots.length > 0) {
|
|
176
|
+
return { roots: clientRoots, source: 'MCP client roots' };
|
|
177
|
+
}
|
|
178
|
+
const envRoot = process.env.CLAUDE_PROJECT_DIR || process.env.CWTOOLS_MCP_WORKSPACE;
|
|
179
|
+
if (envRoot) {
|
|
180
|
+
return { roots: [envRoot], source: 'environment workspace' };
|
|
181
|
+
}
|
|
182
|
+
return { roots: [config.workspaceRoot], source: 'MCP process cwd' };
|
|
183
|
+
}
|
|
184
|
+
async function readClientWorkspaceRoots(server) {
|
|
185
|
+
if (!server.getClientCapabilities()?.roots)
|
|
186
|
+
return [];
|
|
187
|
+
try {
|
|
188
|
+
const result = await server.listRoots(undefined, { timeout: 1000 });
|
|
189
|
+
const roots = result.roots
|
|
190
|
+
.map(root => rootUriToPath(root.uri))
|
|
191
|
+
.filter((root) => !!root);
|
|
192
|
+
return uniquePaths(roots);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function rootUriToPath(uri) {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = new URL(uri);
|
|
201
|
+
if (parsed.protocol !== 'file:')
|
|
202
|
+
return undefined;
|
|
203
|
+
return (0, url_1.fileURLToPath)(parsed);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function uniquePaths(roots) {
|
|
210
|
+
const result = [];
|
|
211
|
+
for (const root of roots) {
|
|
212
|
+
if (!result.some(existing => samePath(existing, root))) {
|
|
213
|
+
result.push(root);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
function assertBridgeWorkspaceMatches(expectedWorkspaceRoot, servedWorkspaceRoot, source = 'MCP process workspace') {
|
|
219
|
+
const expectedRoots = Array.isArray(expectedWorkspaceRoot) ? expectedWorkspaceRoot : [expectedWorkspaceRoot];
|
|
220
|
+
if (expectedRoots.some(expected => samePath(expected, servedWorkspaceRoot)))
|
|
221
|
+
return;
|
|
222
|
+
const expected = expectedRoots.map(root => path.resolve(root)).join(', ');
|
|
223
|
+
throw new Error(`CWTools MCP bridge workspace mismatch. ${source} expects ${expected}, ` +
|
|
224
|
+
`but the active extension bridge serves ${servedWorkspaceRoot}. ` +
|
|
225
|
+
'Open the same project in the compatible host, or make sure the MCP client exposes its current workspace roots/cwd.');
|
|
226
|
+
}
|
|
227
|
+
function resolveBridgeManifestPath(config) {
|
|
228
|
+
if (config.bridgeManifestPath)
|
|
229
|
+
return config.bridgeManifestPath;
|
|
230
|
+
const envPath = process.env.CWTOOLS_MCP_BRIDGE_MANIFEST;
|
|
231
|
+
if (envPath)
|
|
232
|
+
return path.resolve(envPath);
|
|
233
|
+
const scriptPath = process.argv[1] ? path.resolve(process.argv[1]) : __filename;
|
|
234
|
+
const scriptSibling = path.join(path.dirname(scriptPath), BRIDGE_MANIFEST_FILE);
|
|
235
|
+
if (fs.existsSync(scriptSibling))
|
|
236
|
+
return scriptSibling;
|
|
237
|
+
// Independently installed (npx / npm -g) proxies live nowhere near the
|
|
238
|
+
// extension's globalStorage — fall back to the manifest the active
|
|
239
|
+
// VS Code-compatible host writes there.
|
|
240
|
+
const detected = (0, vscodeCache_1.detectExtensionBridgeManifestPath)();
|
|
241
|
+
return detected ?? scriptSibling;
|
|
242
|
+
}
|
|
243
|
+
function normalizeCallToolResult(result) {
|
|
244
|
+
if (result
|
|
245
|
+
&& Array.isArray(result.content)
|
|
246
|
+
&& result.content.every(item => item?.type === 'text' && typeof item.text === 'string')) {
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
return (0, toolHandlers_1.toMcpCallToolResult)({
|
|
250
|
+
ok: false,
|
|
251
|
+
status: 'error',
|
|
252
|
+
source: 'cwtools-mcp-bridge-proxy',
|
|
253
|
+
error: {
|
|
254
|
+
code: 'invalid_bridge_response',
|
|
255
|
+
message: 'The extension MCP bridge returned an invalid tool result.',
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function unavailableResult(error) {
|
|
260
|
+
return {
|
|
261
|
+
ok: false,
|
|
262
|
+
status: 'unavailable',
|
|
263
|
+
source: 'cwtools-mcp-bridge-proxy',
|
|
264
|
+
error: {
|
|
265
|
+
code: 'bridge_unavailable',
|
|
266
|
+
message: error instanceof Error ? error.message : String(error),
|
|
267
|
+
},
|
|
268
|
+
nextSteps: [
|
|
269
|
+
'Open the workspace in a VS Code-compatible host with the CWTools extension installed and active.',
|
|
270
|
+
'Point the MCP client at cwtools-mcp (npx -y cwtools-mcp) or pass --bridge-manifest to the host globalStorage/mcp/bridge-manifest.json.',
|
|
271
|
+
'Use --standalone only if you intentionally want a separate CWTools Server process.',
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function samePath(a, b) {
|
|
276
|
+
const ra = path.resolve(a);
|
|
277
|
+
const rb = path.resolve(b);
|
|
278
|
+
return process.platform === 'win32'
|
|
279
|
+
? ra.toLowerCase() === rb.toLowerCase()
|
|
280
|
+
: ra === rb;
|
|
281
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HostServices } from 'cwtools-shared';
|
|
2
|
+
export declare const RESOURCE_URIS: readonly ["cwtools://knowledge/game", "cwtools://knowledge/diagnostic-routing", "cwtools://knowledge/workflow-hints", "cwtools://project/profile", "cwtools://project/knowledge-manifest"];
|
|
3
|
+
export declare function listResources(): {
|
|
4
|
+
uri: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
mimeType: string;
|
|
8
|
+
}[];
|
|
9
|
+
export declare function readResource(host: HostServices, uri: string): Promise<{
|
|
10
|
+
contents: {
|
|
11
|
+
uri: string;
|
|
12
|
+
mimeType: string;
|
|
13
|
+
text: string;
|
|
14
|
+
}[];
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RESOURCE_URIS = void 0;
|
|
37
|
+
exports.listResources = listResources;
|
|
38
|
+
exports.readResource = readResource;
|
|
39
|
+
const cwtools_shared_1 = require("cwtools-shared");
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
exports.RESOURCE_URIS = [
|
|
42
|
+
'cwtools://knowledge/game',
|
|
43
|
+
'cwtools://knowledge/diagnostic-routing',
|
|
44
|
+
'cwtools://knowledge/workflow-hints',
|
|
45
|
+
'cwtools://project/profile',
|
|
46
|
+
'cwtools://project/knowledge-manifest',
|
|
47
|
+
];
|
|
48
|
+
function listResources() {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
uri: 'cwtools://knowledge/game',
|
|
52
|
+
name: 'CWTools game knowledge',
|
|
53
|
+
description: 'Current-game semantic catalog summary and stable evidence-routing guidance.',
|
|
54
|
+
mimeType: 'application/json',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
uri: 'cwtools://knowledge/diagnostic-routing',
|
|
58
|
+
name: 'CWTools diagnostic routing',
|
|
59
|
+
description: 'Structured diagnostic routing guidance and suggested tools.',
|
|
60
|
+
mimeType: 'application/json',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
uri: 'cwtools://knowledge/workflow-hints',
|
|
64
|
+
name: 'CWTools workflow hints',
|
|
65
|
+
description: 'Reusable workflow hints for diagnostics, localisation, and entity lookup.',
|
|
66
|
+
mimeType: 'application/json',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
uri: 'cwtools://project/profile',
|
|
70
|
+
name: 'CWTools project profile',
|
|
71
|
+
description: 'The generated .cwtools/project/profile.json if available.',
|
|
72
|
+
mimeType: 'application/json',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
uri: 'cwtools://project/knowledge-manifest',
|
|
76
|
+
name: 'CWTools project knowledge manifest',
|
|
77
|
+
description: 'Freshness, domains, counts, and fingerprints for the /init-generated semantic knowledge pack.',
|
|
78
|
+
mimeType: 'application/json',
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
async function readResource(host, uri) {
|
|
83
|
+
const data = await readResourceData(host, uri);
|
|
84
|
+
return {
|
|
85
|
+
contents: [
|
|
86
|
+
{
|
|
87
|
+
uri,
|
|
88
|
+
mimeType: 'application/json',
|
|
89
|
+
text: `${JSON.stringify(data, null, 2)}\n`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async function readResourceData(host, uri) {
|
|
95
|
+
switch (uri) {
|
|
96
|
+
case 'cwtools://knowledge/game': {
|
|
97
|
+
const catalog = await host.lsp.executeCommand('cwtools.ai.getSemanticCatalog', [[], []], { timeoutMs: 10000 }).catch(() => undefined);
|
|
98
|
+
return (0, cwtools_shared_1.queryGameKnowledge)('paradox', catalog);
|
|
99
|
+
}
|
|
100
|
+
case 'cwtools://knowledge/diagnostic-routing':
|
|
101
|
+
return {
|
|
102
|
+
status: 'ready',
|
|
103
|
+
routes: [
|
|
104
|
+
(0, cwtools_shared_1.analyzeDiagnosticKnowledge)({ message: 'syntax error' }),
|
|
105
|
+
(0, cwtools_shared_1.analyzeDiagnosticKnowledge)({ message: 'missing localisation key' }),
|
|
106
|
+
(0, cwtools_shared_1.analyzeDiagnosticKnowledge)({ message: 'scope mismatch' }),
|
|
107
|
+
(0, cwtools_shared_1.analyzeDiagnosticKnowledge)({ message: 'unknown' }),
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
case 'cwtools://knowledge/workflow-hints':
|
|
111
|
+
return (0, cwtools_shared_1.queryWorkflowHints)();
|
|
112
|
+
case 'cwtools://project/profile': {
|
|
113
|
+
const profilePath = (0, cwtools_shared_1.getProjectProfilePath)(host.workspaceRoot);
|
|
114
|
+
const read = await host.filesystem.readTextFile(profilePath);
|
|
115
|
+
return read.exists
|
|
116
|
+
? JSON.parse(read.content)
|
|
117
|
+
: {
|
|
118
|
+
status: 'missing',
|
|
119
|
+
profilePath,
|
|
120
|
+
_hint: 'Run /init in the VS Code extension or create .cwtools/project/profile.json.',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
case 'cwtools://project/knowledge-manifest': {
|
|
124
|
+
let manifestPath = path.join(host.workspaceRoot, '.cwtools', 'project', 'knowledge', 'manifest.json');
|
|
125
|
+
let read = await host.filesystem.readTextFile(manifestPath);
|
|
126
|
+
if (!read.exists) {
|
|
127
|
+
const legacyPath = path.join(host.workspaceRoot, '.cwtools-ai', 'project', 'knowledge', 'manifest.json');
|
|
128
|
+
const legacyRead = await host.filesystem.readTextFile(legacyPath);
|
|
129
|
+
if (legacyRead.exists) {
|
|
130
|
+
manifestPath = legacyPath;
|
|
131
|
+
read = legacyRead;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return read.exists
|
|
135
|
+
? JSON.parse(read.content)
|
|
136
|
+
: {
|
|
137
|
+
status: 'missing',
|
|
138
|
+
manifestPath,
|
|
139
|
+
_hint: 'Run /init in the VS Code extension and wait for the deep semantic phase.',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
return {
|
|
144
|
+
status: 'error',
|
|
145
|
+
error: {
|
|
146
|
+
code: 'resource_not_found',
|
|
147
|
+
message: `Unknown CWTools MCP resource: ${uri}`,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type HostServices, type SharedToolDispatcher, type SharedToolResult } from 'cwtools-shared';
|
|
2
|
+
export declare function createToolCallHandler(host: HostServices, dispatcher?: SharedToolDispatcher): (name: string, args?: Record<string, unknown>) => Promise<SharedToolResult>;
|
|
3
|
+
export declare function toMcpCallToolResult(result: SharedToolResult): {
|
|
4
|
+
content: Array<{
|
|
5
|
+
type: 'text';
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
isError?: boolean;
|
|
9
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createToolCallHandler = createToolCallHandler;
|
|
4
|
+
exports.toMcpCallToolResult = toMcpCallToolResult;
|
|
5
|
+
const cwtools_shared_1 = require("cwtools-shared");
|
|
6
|
+
function createToolCallHandler(host, dispatcher = cwtools_shared_1.defaultSharedToolDispatcher) {
|
|
7
|
+
// The game stays loaded once ready, so probe readiness only until it flips true.
|
|
8
|
+
let ready = false;
|
|
9
|
+
const probeReadiness = async () => {
|
|
10
|
+
if (ready)
|
|
11
|
+
return { ready: true };
|
|
12
|
+
try {
|
|
13
|
+
const status = await host.lsp.executeCommand('cwtools.ai.getValidationStatus', [], { timeoutMs: 8000 });
|
|
14
|
+
const verdict = (0, cwtools_shared_1.parseReadiness)(status);
|
|
15
|
+
if (verdict.ready)
|
|
16
|
+
ready = true;
|
|
17
|
+
return verdict;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { ready: false, reason: 'lsp_unavailable' };
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
return async (name, args = {}) => {
|
|
24
|
+
// Read-only surface: reject anything outside the registered (read-only) tools,
|
|
25
|
+
// including any write tool called directly. Writes go through the host agent.
|
|
26
|
+
if (!(0, cwtools_shared_1.isMcpToolName)(name)) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
status: 'denied',
|
|
30
|
+
source: 'cwtools-mcp',
|
|
31
|
+
error: {
|
|
32
|
+
code: 'tool_not_available',
|
|
33
|
+
message: `Tool '${name}' is not available: this MCP server is read-only. Perform file writes through your own environment.`,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const result = await dispatcher(host, name, args);
|
|
38
|
+
// Tag vanilla-dependent results with cache provenance so clients never read a
|
|
39
|
+
// mod-only answer as complete.
|
|
40
|
+
const withVanilla = (0, cwtools_shared_1.annotateVanillaCache)(name, result, host.vanillaCache);
|
|
41
|
+
// For load-dependent tools, mark results produced before the game is ready as
|
|
42
|
+
// `loading` so clients retry instead of trusting an empty answer.
|
|
43
|
+
if (!cwtools_shared_1.LOAD_DEPENDENT_TOOLS.has(name))
|
|
44
|
+
return withVanilla;
|
|
45
|
+
return (0, cwtools_shared_1.annotateReadiness)(name, withVanilla, await probeReadiness());
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function toMcpCallToolResult(result) {
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: 'text',
|
|
53
|
+
text: `${JSON.stringify(result, null, 2)}\n`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
isError: !result.ok,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listRegisteredTools = listRegisteredTools;
|
|
4
|
+
const cwtools_shared_1 = require("cwtools-shared");
|
|
5
|
+
function listRegisteredTools() {
|
|
6
|
+
return (0, cwtools_shared_1.listMcpToolSchemas)();
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
export interface HttpTransportOptions {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
path?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function runHttpTransport(createServer: () => Server, options: HttpTransportOptions): Promise<void>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runHttpTransport = runHttpTransport;
|
|
37
|
+
const http = __importStar(require("http"));
|
|
38
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
39
|
+
async function runHttpTransport(createServer, options) {
|
|
40
|
+
const endpointPath = options.path ?? '/mcp';
|
|
41
|
+
const httpServer = http.createServer(async (request, response) => {
|
|
42
|
+
const url = new URL(request.url ?? '/', `http://${request.headers.host ?? `${options.host}:${options.port}`}`);
|
|
43
|
+
if (url.pathname === '/healthz') {
|
|
44
|
+
response.writeHead(200, { 'content-type': 'application/json' });
|
|
45
|
+
response.end(JSON.stringify({ ok: true, name: 'cwtools-mcp' }));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (url.pathname !== endpointPath) {
|
|
49
|
+
response.writeHead(404, { 'content-type': 'application/json' });
|
|
50
|
+
response.end(JSON.stringify({ error: 'not_found' }));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!['GET', 'POST', 'DELETE'].includes(request.method ?? '')) {
|
|
54
|
+
response.writeHead(405, { 'content-type': 'application/json' });
|
|
55
|
+
response.end(JSON.stringify({ error: 'method_not_allowed' }));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const requestServer = createServer();
|
|
59
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
60
|
+
sessionIdGenerator: undefined,
|
|
61
|
+
});
|
|
62
|
+
try {
|
|
63
|
+
await requestServer.connect(transport);
|
|
64
|
+
await transport.handleRequest(request, response);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!response.headersSent) {
|
|
68
|
+
response.writeHead(500, { 'content-type': 'application/json' });
|
|
69
|
+
}
|
|
70
|
+
response.end(JSON.stringify({
|
|
71
|
+
jsonrpc: '2.0',
|
|
72
|
+
error: {
|
|
73
|
+
code: -32603,
|
|
74
|
+
message: error instanceof Error ? error.message : String(error),
|
|
75
|
+
},
|
|
76
|
+
id: null,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
response.once('close', () => {
|
|
81
|
+
transport.close().catch(() => undefined);
|
|
82
|
+
requestServer.close();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
await new Promise((resolve, reject) => {
|
|
87
|
+
httpServer.once('error', reject);
|
|
88
|
+
httpServer.listen(options.port, options.host, () => {
|
|
89
|
+
httpServer.off('error', reject);
|
|
90
|
+
console.error(`[cwtools-mcp] Streamable HTTP listening at http://${options.host}:${options.port}${endpointPath}`);
|
|
91
|
+
resolve();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
await new Promise(resolve => {
|
|
95
|
+
const shutdown = async () => {
|
|
96
|
+
httpServer.close(() => resolve());
|
|
97
|
+
};
|
|
98
|
+
process.once('SIGINT', shutdown);
|
|
99
|
+
process.once('SIGTERM', shutdown);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runStdioTransport = runStdioTransport;
|
|
4
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
async function runStdioTransport(server) {
|
|
6
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
7
|
+
await server.connect(transport);
|
|
8
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import type { HostServices, SharedToolDispatcher } from 'cwtools-shared';
|
|
3
|
+
export declare function createCwtoolsMcpServer(host: HostServices, options?: {
|
|
4
|
+
dispatcher?: SharedToolDispatcher;
|
|
5
|
+
}): Server;
|