@intrig/mcp 0.0.1
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 +229 -0
- package/dist/app/intrig-mcp/src/formatters/documentation.d.ts +73 -0
- package/dist/app/intrig-mcp/src/formatters/documentation.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/main.d.ts +9 -0
- package/dist/app/intrig-mcp/src/main.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/mcp/server.d.ts +14 -0
- package/dist/app/intrig-mcp/src/mcp/server.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/mcp/tools/get-documentation.d.ts +23 -0
- package/dist/app/intrig-mcp/src/mcp/tools/get-documentation.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/mcp/tools/get-project.d.ts +21 -0
- package/dist/app/intrig-mcp/src/mcp/tools/get-project.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/mcp/tools/list-projects.d.ts +13 -0
- package/dist/app/intrig-mcp/src/mcp/tools/list-projects.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/mcp/tools/search.d.ts +24 -0
- package/dist/app/intrig-mcp/src/mcp/tools/search.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/services/daemon-client.d.ts +54 -0
- package/dist/app/intrig-mcp/src/services/daemon-client.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/services/discovery.service.d.ts +60 -0
- package/dist/app/intrig-mcp/src/services/discovery.service.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/types/daemon-api.d.ts +150 -0
- package/dist/app/intrig-mcp/src/types/daemon-api.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/types/discovery.d.ts +61 -0
- package/dist/app/intrig-mcp/src/types/discovery.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/utils/errors.d.ts +25 -0
- package/dist/app/intrig-mcp/src/utils/errors.d.ts.map +1 -0
- package/dist/app/intrig-mcp/src/utils/logger.d.ts +25 -0
- package/dist/app/intrig-mcp/src/utils/logger.d.ts.map +1 -0
- package/dist/main.js +1426 -0
- package/dist/tsconfig.app.tsbuildinfo +1 -0
- package/package.json +118 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// app/intrig-mcp/src/mcp/server.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
// app/intrig-mcp/src/services/discovery.service.ts
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import * as os from "node:os";
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
import tcpPortUsed from "tcp-port-used";
|
|
17
|
+
|
|
18
|
+
// app/intrig-mcp/src/types/discovery.ts
|
|
19
|
+
function ok(value) {
|
|
20
|
+
return { ok: true, value };
|
|
21
|
+
}
|
|
22
|
+
function err(error) {
|
|
23
|
+
return { ok: false, error };
|
|
24
|
+
}
|
|
25
|
+
function discoveryError(code, message, cause) {
|
|
26
|
+
return { code, message, cause };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// app/intrig-mcp/src/services/discovery.service.ts
|
|
30
|
+
function getRegistryDir() {
|
|
31
|
+
const baseDir = os.tmpdir();
|
|
32
|
+
const username = sanitizeName(os.userInfo().username);
|
|
33
|
+
return path.join(baseDir, `${username}.intrig`);
|
|
34
|
+
}
|
|
35
|
+
function sanitizeName(raw) {
|
|
36
|
+
return raw.replace(/[^a-zA-Z0-9\-_]/g, "_");
|
|
37
|
+
}
|
|
38
|
+
async function isPortInUse(port) {
|
|
39
|
+
try {
|
|
40
|
+
return await tcpPortUsed.check(port);
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function parseMetadataFile(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
if (!fs.existsSync(filePath)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
51
|
+
const parsed = JSON.parse(content);
|
|
52
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.projectName !== "string" || typeof parsed.url !== "string" || typeof parsed.port !== "number" || typeof parsed.pid !== "number" || typeof parsed.timestamp !== "string" || typeof parsed.path !== "string" || typeof parsed.type !== "string") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return parsed;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function scanRegistry() {
|
|
61
|
+
const registryDir = getRegistryDir();
|
|
62
|
+
if (!fs.existsSync(registryDir)) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const files = fs.readdirSync(registryDir);
|
|
67
|
+
const metadata = [];
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
if (!file.endsWith(".json")) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const filePath = path.join(registryDir, file);
|
|
73
|
+
const parsed = parseMetadataFile(filePath);
|
|
74
|
+
if (parsed !== null) {
|
|
75
|
+
metadata.push(parsed);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return metadata;
|
|
79
|
+
} catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function isDaemonRunning(metadata) {
|
|
84
|
+
return isPortInUse(metadata.port);
|
|
85
|
+
}
|
|
86
|
+
async function listProjects() {
|
|
87
|
+
const allMetadata = scanRegistry();
|
|
88
|
+
const projects = [];
|
|
89
|
+
for (const metadata of allMetadata) {
|
|
90
|
+
const running = await isDaemonRunning(metadata);
|
|
91
|
+
projects.push({
|
|
92
|
+
projectName: metadata.projectName,
|
|
93
|
+
path: metadata.path,
|
|
94
|
+
url: metadata.url,
|
|
95
|
+
port: metadata.port,
|
|
96
|
+
type: metadata.type,
|
|
97
|
+
running,
|
|
98
|
+
metadata
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return projects;
|
|
102
|
+
}
|
|
103
|
+
async function waitForDaemonReady(port, maxWaitMs = 1e4, pollIntervalMs = 500) {
|
|
104
|
+
const startTime = Date.now();
|
|
105
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
106
|
+
if (await isPortInUse(port)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
await sleep(pollIntervalMs);
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function sleep(ms) {
|
|
114
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
115
|
+
}
|
|
116
|
+
async function startDaemon(projectPath) {
|
|
117
|
+
return new Promise((resolve2) => {
|
|
118
|
+
const child = spawn("intrig", ["daemon", "up"], {
|
|
119
|
+
cwd: projectPath,
|
|
120
|
+
stdio: "ignore",
|
|
121
|
+
detached: true
|
|
122
|
+
});
|
|
123
|
+
child.on("error", (error) => {
|
|
124
|
+
resolve2(
|
|
125
|
+
err(
|
|
126
|
+
discoveryError(
|
|
127
|
+
"DAEMON_START_FAILED",
|
|
128
|
+
`Failed to start daemon: ${error.message}`,
|
|
129
|
+
error
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
child.unref();
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
resolve2(ok(void 0));
|
|
137
|
+
}, 500);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function resolveProjectPath(inputPath) {
|
|
141
|
+
const normalizedInput = path.resolve(inputPath);
|
|
142
|
+
const allMetadata = scanRegistry();
|
|
143
|
+
for (const metadata of allMetadata) {
|
|
144
|
+
const normalizedProject = path.resolve(metadata.path);
|
|
145
|
+
if (normalizedInput === normalizedProject) {
|
|
146
|
+
return metadata;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
let bestMatch = null;
|
|
150
|
+
let bestMatchLength = 0;
|
|
151
|
+
for (const metadata of allMetadata) {
|
|
152
|
+
const normalizedProject = path.resolve(metadata.path);
|
|
153
|
+
if (normalizedInput.startsWith(normalizedProject + path.sep) || normalizedInput.startsWith(normalizedProject + "/")) {
|
|
154
|
+
if (normalizedProject.length > bestMatchLength) {
|
|
155
|
+
bestMatch = metadata;
|
|
156
|
+
bestMatchLength = normalizedProject.length;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return bestMatch;
|
|
161
|
+
}
|
|
162
|
+
async function getProject(inputPath) {
|
|
163
|
+
const metadata = resolveProjectPath(inputPath);
|
|
164
|
+
if (metadata === null) {
|
|
165
|
+
return err(
|
|
166
|
+
discoveryError(
|
|
167
|
+
"PROJECT_NOT_FOUND",
|
|
168
|
+
`No registered Intrig project found for path: ${inputPath}`
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const running = await isDaemonRunning(metadata);
|
|
173
|
+
if (running) {
|
|
174
|
+
return ok({
|
|
175
|
+
projectName: metadata.projectName,
|
|
176
|
+
path: metadata.path,
|
|
177
|
+
url: metadata.url,
|
|
178
|
+
port: metadata.port,
|
|
179
|
+
type: metadata.type,
|
|
180
|
+
running: true,
|
|
181
|
+
metadata
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const startResult = await startDaemon(metadata.path);
|
|
185
|
+
if (!startResult.ok) {
|
|
186
|
+
return startResult;
|
|
187
|
+
}
|
|
188
|
+
const ready = await waitForDaemonReady(metadata.port);
|
|
189
|
+
if (!ready) {
|
|
190
|
+
return err(
|
|
191
|
+
discoveryError(
|
|
192
|
+
"DAEMON_START_FAILED",
|
|
193
|
+
`Daemon started but did not become ready within timeout for project: ${metadata.projectName}`
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return ok({
|
|
198
|
+
projectName: metadata.projectName,
|
|
199
|
+
path: metadata.path,
|
|
200
|
+
url: metadata.url,
|
|
201
|
+
port: metadata.port,
|
|
202
|
+
type: metadata.type,
|
|
203
|
+
running: true,
|
|
204
|
+
metadata
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function findProjectByName(projectName) {
|
|
208
|
+
const allMetadata = scanRegistry();
|
|
209
|
+
return allMetadata.find((m) => m.projectName === projectName) ?? null;
|
|
210
|
+
}
|
|
211
|
+
function resolveProjectIdentifier(identifier) {
|
|
212
|
+
const byPath = resolveProjectPath(identifier);
|
|
213
|
+
if (byPath !== null) {
|
|
214
|
+
return byPath;
|
|
215
|
+
}
|
|
216
|
+
return findProjectByName(identifier);
|
|
217
|
+
}
|
|
218
|
+
async function getProjectByIdentifier(identifier) {
|
|
219
|
+
const metadata = resolveProjectIdentifier(identifier);
|
|
220
|
+
if (metadata === null) {
|
|
221
|
+
return err(
|
|
222
|
+
discoveryError(
|
|
223
|
+
"PROJECT_NOT_FOUND",
|
|
224
|
+
`No registered Intrig project found for: ${identifier}`
|
|
225
|
+
)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return getProject(metadata.path);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// app/intrig-mcp/src/utils/errors.ts
|
|
232
|
+
var ERROR_SUGGESTIONS = {
|
|
233
|
+
// Discovery errors
|
|
234
|
+
PROJECT_NOT_FOUND: "Make sure you have initialized an Intrig project in this directory.\nRun `intrig init` to create a new project, or check that the path is correct.",
|
|
235
|
+
DAEMON_START_FAILED: "The Intrig daemon could not be started automatically.\nTry running `intrig daemon up` manually in the project directory to see detailed error messages.",
|
|
236
|
+
DAEMON_UNAVAILABLE: "The Intrig daemon is not responding.\nRun `intrig daemon up` in the project directory to start it.",
|
|
237
|
+
REGISTRY_ERROR: "Could not read the Intrig project registry.\nThis may be a permissions issue. Check that you have access to the temp directory.",
|
|
238
|
+
// Daemon client errors
|
|
239
|
+
RESOURCE_NOT_FOUND: "The requested resource was not found.\nThe ID may be invalid or the resource may have been removed. Try searching again to get current IDs.",
|
|
240
|
+
REQUEST_TIMEOUT: "The request to the daemon timed out.\nThe daemon may be overloaded or unresponsive. Try again, or restart the daemon with `intrig daemon restart`.",
|
|
241
|
+
INVALID_RESPONSE: "Received an unexpected response from the daemon.\nThis may indicate a version mismatch. Try updating Intrig: `npm update -g @intrig/core`"
|
|
242
|
+
};
|
|
243
|
+
function formatError(code, message) {
|
|
244
|
+
const suggestion = ERROR_SUGGESTIONS[code];
|
|
245
|
+
const lines = [`Error: ${message}`];
|
|
246
|
+
if (suggestion) {
|
|
247
|
+
lines.push("");
|
|
248
|
+
lines.push(suggestion);
|
|
249
|
+
}
|
|
250
|
+
return lines.join("\n");
|
|
251
|
+
}
|
|
252
|
+
function formatValidationError(field, expected) {
|
|
253
|
+
return `Invalid input: "${field}" ${expected}`;
|
|
254
|
+
}
|
|
255
|
+
function formatNoProjectsMessage() {
|
|
256
|
+
return "No registered Intrig projects found.\n\nTo get started:\n1. Navigate to your project directory\n2. Run `intrig init` to initialize Intrig\n3. Run `intrig daemon up` to start the daemon\n\nThe daemon will automatically register the project for the MCP server to discover.";
|
|
257
|
+
}
|
|
258
|
+
function formatNoSourcesMessage(projectName) {
|
|
259
|
+
return `Project "${projectName}" has no API sources configured.
|
|
260
|
+
|
|
261
|
+
Add API sources to your intrig.config.js file:
|
|
262
|
+
\`\`\`
|
|
263
|
+
sources: [
|
|
264
|
+
{ id: "my-api", specUrl: "https://api.example.com/openapi.json" }
|
|
265
|
+
]
|
|
266
|
+
\`\`\`
|
|
267
|
+
|
|
268
|
+
Then run \`intrig sync\` to fetch the API specifications.`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// app/intrig-mcp/src/utils/logger.ts
|
|
272
|
+
var DEBUG_ENV = process.env.DEBUG ?? "";
|
|
273
|
+
var isDebugEnabled = DEBUG_ENV === "intrig-mcp" || DEBUG_ENV === "intrig-mcp:*" || DEBUG_ENV.includes("intrig-mcp");
|
|
274
|
+
function createDebugger(namespace) {
|
|
275
|
+
const fullNamespace = `intrig-mcp:${namespace}`;
|
|
276
|
+
const isEnabled = DEBUG_ENV === "intrig-mcp:*" || DEBUG_ENV.includes(fullNamespace) || isDebugEnabled;
|
|
277
|
+
return (message, ...args) => {
|
|
278
|
+
if (!isEnabled) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
282
|
+
const prefix = `[${fullNamespace} ${timestamp}]`;
|
|
283
|
+
if (args.length > 0) {
|
|
284
|
+
console.error(prefix, message, ...args);
|
|
285
|
+
} else {
|
|
286
|
+
console.error(prefix, message);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// app/intrig-mcp/src/mcp/tools/list-projects.ts
|
|
292
|
+
var debug = createDebugger("tools:list-projects");
|
|
293
|
+
var listProjectsTool = {
|
|
294
|
+
name: "list_projects",
|
|
295
|
+
description: "List all registered Intrig projects and their daemon status. Use this to discover available projects before searching or getting documentation.",
|
|
296
|
+
inputSchema: {
|
|
297
|
+
type: "object",
|
|
298
|
+
properties: {},
|
|
299
|
+
required: []
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
async function handleListProjects() {
|
|
303
|
+
debug("Listing projects");
|
|
304
|
+
const projects = await listProjects();
|
|
305
|
+
debug(`Found ${projects.length} project(s)`);
|
|
306
|
+
if (projects.length === 0) {
|
|
307
|
+
return {
|
|
308
|
+
content: [
|
|
309
|
+
{
|
|
310
|
+
type: "text",
|
|
311
|
+
text: formatNoProjectsMessage()
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const lines = [];
|
|
317
|
+
lines.push(`Found ${projects.length} registered Intrig project(s):
|
|
318
|
+
`);
|
|
319
|
+
for (const project of projects) {
|
|
320
|
+
const status = project.running ? "running" : "stopped";
|
|
321
|
+
lines.push(`**${project.projectName}** [${status}]`);
|
|
322
|
+
lines.push(` - Path: ${project.path}`);
|
|
323
|
+
lines.push(` - Port: ${project.port}`);
|
|
324
|
+
lines.push(` - Type: ${project.type}`);
|
|
325
|
+
lines.push("");
|
|
326
|
+
}
|
|
327
|
+
lines.push("---");
|
|
328
|
+
lines.push("Use `get_project` with a path to see API sources, or `search` to find endpoints and schemas.");
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: lines.join("\n").trim()
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// app/intrig-mcp/src/services/daemon-client.ts
|
|
340
|
+
import * as http from "node:http";
|
|
341
|
+
import * as https from "node:https";
|
|
342
|
+
|
|
343
|
+
// app/intrig-mcp/src/types/daemon-api.ts
|
|
344
|
+
function daemonClientError(code, message, statusCode, cause) {
|
|
345
|
+
return { code, message, statusCode, cause };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// app/intrig-mcp/src/services/daemon-client.ts
|
|
349
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
350
|
+
var DEFAULT_RETRY_COUNT = 2;
|
|
351
|
+
var DEFAULT_RETRY_DELAY_MS = 500;
|
|
352
|
+
var defaultConfig = {
|
|
353
|
+
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
354
|
+
retryCount: DEFAULT_RETRY_COUNT,
|
|
355
|
+
retryDelayMs: DEFAULT_RETRY_DELAY_MS
|
|
356
|
+
};
|
|
357
|
+
function sleep2(ms) {
|
|
358
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
359
|
+
}
|
|
360
|
+
function isTimeoutError(error) {
|
|
361
|
+
if (error instanceof Error) {
|
|
362
|
+
return error.name === "AbortError" || error.message.includes("timeout") || error.message.includes("ETIMEDOUT");
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
function isConnectionError(error) {
|
|
367
|
+
if (error instanceof Error) {
|
|
368
|
+
const msg = error.message.toLowerCase();
|
|
369
|
+
return msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("econnreset") || msg.includes("network");
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
function createRequestError(error) {
|
|
374
|
+
if (isTimeoutError(error)) {
|
|
375
|
+
return daemonClientError(
|
|
376
|
+
"REQUEST_TIMEOUT",
|
|
377
|
+
"Request timed out",
|
|
378
|
+
void 0,
|
|
379
|
+
error
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (isConnectionError(error)) {
|
|
383
|
+
return daemonClientError(
|
|
384
|
+
"DAEMON_UNAVAILABLE",
|
|
385
|
+
"Could not connect to daemon",
|
|
386
|
+
void 0,
|
|
387
|
+
error
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
return daemonClientError(
|
|
391
|
+
"DAEMON_UNAVAILABLE",
|
|
392
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
393
|
+
void 0,
|
|
394
|
+
error
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
function createHttpError(statusCode, statusText) {
|
|
398
|
+
if (statusCode === 404) {
|
|
399
|
+
return daemonClientError(
|
|
400
|
+
"RESOURCE_NOT_FOUND",
|
|
401
|
+
`Resource not found: ${statusText}`,
|
|
402
|
+
statusCode
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
if (statusCode >= 500) {
|
|
406
|
+
return daemonClientError(
|
|
407
|
+
"DAEMON_UNAVAILABLE",
|
|
408
|
+
`Server error: ${statusCode} ${statusText}`,
|
|
409
|
+
statusCode
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
return daemonClientError(
|
|
413
|
+
"INVALID_RESPONSE",
|
|
414
|
+
`HTTP ${statusCode}: ${statusText}`,
|
|
415
|
+
statusCode
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
function httpGet(urlString, timeoutMs) {
|
|
419
|
+
return new Promise((resolve2, reject) => {
|
|
420
|
+
const parsedUrl = new URL(urlString);
|
|
421
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
422
|
+
const httpModule = isHttps ? https : http;
|
|
423
|
+
const options = {
|
|
424
|
+
hostname: parsedUrl.hostname,
|
|
425
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
426
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
427
|
+
method: "GET",
|
|
428
|
+
headers: {
|
|
429
|
+
Accept: "application/json"
|
|
430
|
+
},
|
|
431
|
+
timeout: timeoutMs
|
|
432
|
+
};
|
|
433
|
+
const req = httpModule.request(options, (res) => {
|
|
434
|
+
let body = "";
|
|
435
|
+
res.setEncoding("utf8");
|
|
436
|
+
res.on("data", (chunk) => {
|
|
437
|
+
body += chunk;
|
|
438
|
+
});
|
|
439
|
+
res.on("end", () => {
|
|
440
|
+
resolve2({
|
|
441
|
+
statusCode: res.statusCode ?? 0,
|
|
442
|
+
statusMessage: res.statusMessage ?? "",
|
|
443
|
+
body
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
req.on("error", (error) => {
|
|
448
|
+
reject(error);
|
|
449
|
+
});
|
|
450
|
+
req.on("timeout", () => {
|
|
451
|
+
req.destroy();
|
|
452
|
+
reject(new Error("Request timeout"));
|
|
453
|
+
});
|
|
454
|
+
req.end();
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
async function requestWithRetry(url, config) {
|
|
458
|
+
let lastError = null;
|
|
459
|
+
for (let attempt = 0; attempt <= config.retryCount; attempt++) {
|
|
460
|
+
try {
|
|
461
|
+
const response = await httpGet(url, config.timeoutMs);
|
|
462
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
463
|
+
lastError = createHttpError(response.statusCode, response.statusMessage);
|
|
464
|
+
if (response.statusCode >= 400 && response.statusCode < 500) {
|
|
465
|
+
return err(lastError);
|
|
466
|
+
}
|
|
467
|
+
if (attempt < config.retryCount) {
|
|
468
|
+
await sleep2(config.retryDelayMs);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
return err(lastError);
|
|
472
|
+
}
|
|
473
|
+
const data = JSON.parse(response.body);
|
|
474
|
+
return ok(data);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
lastError = createRequestError(error);
|
|
477
|
+
if (isTimeoutError(error)) {
|
|
478
|
+
return err(lastError);
|
|
479
|
+
}
|
|
480
|
+
if (attempt < config.retryCount) {
|
|
481
|
+
await sleep2(config.retryDelayMs);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
return err(lastError);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return err(
|
|
488
|
+
lastError ?? daemonClientError("DAEMON_UNAVAILABLE", "Request failed after retries")
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
async function search(baseUrl, params, config) {
|
|
492
|
+
const url = new URL("/api/data/search", baseUrl);
|
|
493
|
+
if (params.query) {
|
|
494
|
+
url.searchParams.set("query", params.query);
|
|
495
|
+
}
|
|
496
|
+
if (params.type) {
|
|
497
|
+
url.searchParams.set("type", params.type);
|
|
498
|
+
}
|
|
499
|
+
if (params.source) {
|
|
500
|
+
url.searchParams.set("source", params.source);
|
|
501
|
+
}
|
|
502
|
+
if (params.page !== void 0) {
|
|
503
|
+
url.searchParams.set("page", String(params.page));
|
|
504
|
+
}
|
|
505
|
+
if (params.size !== void 0) {
|
|
506
|
+
url.searchParams.set("size", String(params.size));
|
|
507
|
+
}
|
|
508
|
+
return requestWithRetry(url.toString(), {
|
|
509
|
+
...defaultConfig,
|
|
510
|
+
...config
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
async function getEndpointDocumentation(baseUrl, id, config) {
|
|
514
|
+
const url = new URL(`/api/data/get/endpoint/${encodeURIComponent(id)}`, baseUrl);
|
|
515
|
+
return requestWithRetry(url.toString(), {
|
|
516
|
+
...defaultConfig,
|
|
517
|
+
...config
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
async function getSchemaDocumentation(baseUrl, id, config) {
|
|
521
|
+
const url = new URL(`/api/data/get/schema/${encodeURIComponent(id)}`, baseUrl);
|
|
522
|
+
return requestWithRetry(url.toString(), {
|
|
523
|
+
...defaultConfig,
|
|
524
|
+
...config
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
async function listSources(baseUrl, config) {
|
|
528
|
+
const url = new URL("/api/config/sources/list", baseUrl);
|
|
529
|
+
return requestWithRetry(url.toString(), {
|
|
530
|
+
...defaultConfig,
|
|
531
|
+
...config
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
async function searchSimple(baseUrl, query, type, source, limit) {
|
|
535
|
+
const daemonType = type === "endpoint" ? "rest" : type;
|
|
536
|
+
return search(baseUrl, {
|
|
537
|
+
query,
|
|
538
|
+
type: daemonType,
|
|
539
|
+
source,
|
|
540
|
+
size: limit ?? 15,
|
|
541
|
+
page: 1
|
|
542
|
+
// Daemon API uses 1-based pagination
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// app/intrig-mcp/src/mcp/tools/get-project.ts
|
|
547
|
+
var debug2 = createDebugger("tools:get-project");
|
|
548
|
+
var getProjectTool = {
|
|
549
|
+
name: "get_project",
|
|
550
|
+
description: "Resolve a path to its Intrig project and list API sources. Auto-starts the daemon if not running. Use this to get project details and available API sources before searching.",
|
|
551
|
+
inputSchema: {
|
|
552
|
+
type: "object",
|
|
553
|
+
properties: {
|
|
554
|
+
path: {
|
|
555
|
+
type: "string",
|
|
556
|
+
description: "Absolute or relative path to project or subdirectory"
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
required: ["path"]
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
async function handleGetProject(input) {
|
|
563
|
+
debug2("Getting project for path:", input.path);
|
|
564
|
+
if (!input.path || typeof input.path !== "string") {
|
|
565
|
+
return {
|
|
566
|
+
isError: true,
|
|
567
|
+
content: [
|
|
568
|
+
{
|
|
569
|
+
type: "text",
|
|
570
|
+
text: formatValidationError("path", "is required and must be a string")
|
|
571
|
+
}
|
|
572
|
+
]
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const projectResult = await getProject(input.path);
|
|
576
|
+
if (!projectResult.ok) {
|
|
577
|
+
const error = projectResult.error;
|
|
578
|
+
debug2("Project lookup failed:", error.code, error.message);
|
|
579
|
+
return {
|
|
580
|
+
isError: true,
|
|
581
|
+
content: [
|
|
582
|
+
{
|
|
583
|
+
type: "text",
|
|
584
|
+
text: formatError(error.code, error.message)
|
|
585
|
+
}
|
|
586
|
+
]
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const project = projectResult.value;
|
|
590
|
+
debug2("Found project:", project.projectName);
|
|
591
|
+
const sourcesResult = await listSources(project.url);
|
|
592
|
+
const lines = [];
|
|
593
|
+
lines.push(`# ${project.projectName}`);
|
|
594
|
+
lines.push("");
|
|
595
|
+
lines.push(`**Path:** ${project.path}`);
|
|
596
|
+
lines.push(`**URL:** ${project.url}`);
|
|
597
|
+
lines.push(`**Port:** ${project.port}`);
|
|
598
|
+
lines.push(`**Type:** ${project.type}`);
|
|
599
|
+
lines.push(`**Status:** Running`);
|
|
600
|
+
if (sourcesResult.ok) {
|
|
601
|
+
const sources = sourcesResult.value;
|
|
602
|
+
debug2(`Found ${sources.length} source(s)`);
|
|
603
|
+
if (sources.length > 0) {
|
|
604
|
+
lines.push("");
|
|
605
|
+
lines.push("## API Sources");
|
|
606
|
+
lines.push("");
|
|
607
|
+
for (const source of sources) {
|
|
608
|
+
lines.push(`- **${source.name}** (\`${source.id}\`)`);
|
|
609
|
+
lines.push(` - Spec URL: ${source.specUrl}`);
|
|
610
|
+
}
|
|
611
|
+
lines.push("");
|
|
612
|
+
lines.push("---");
|
|
613
|
+
lines.push("Use `search` with the project path and a query to find endpoints and schemas.");
|
|
614
|
+
} else {
|
|
615
|
+
lines.push("");
|
|
616
|
+
lines.push(formatNoSourcesMessage(project.projectName));
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
debug2("Failed to fetch sources:", sourcesResult.error.message);
|
|
620
|
+
lines.push("");
|
|
621
|
+
lines.push(`**Note:** Could not fetch API sources: ${sourcesResult.error.message}`);
|
|
622
|
+
lines.push("The daemon is running, but sources may not be synced yet.");
|
|
623
|
+
lines.push("Run `intrig sync` in the project directory to sync API specifications.");
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
content: [
|
|
627
|
+
{
|
|
628
|
+
type: "text",
|
|
629
|
+
text: lines.join("\n")
|
|
630
|
+
}
|
|
631
|
+
]
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// app/intrig-mcp/src/mcp/tools/search.ts
|
|
636
|
+
var debug3 = createDebugger("tools:search");
|
|
637
|
+
var searchTool = {
|
|
638
|
+
name: "search",
|
|
639
|
+
description: "Search for API endpoints and schemas in an Intrig project. Uses keyword matching (not semantic search). Use specific terms: endpoint names, paths (/api/users), HTTP methods (GET, POST), or type names. Returns IDs that can be used with get_documentation.",
|
|
640
|
+
inputSchema: {
|
|
641
|
+
type: "object",
|
|
642
|
+
properties: {
|
|
643
|
+
project: {
|
|
644
|
+
type: "string",
|
|
645
|
+
description: "Project path or projectName"
|
|
646
|
+
},
|
|
647
|
+
query: {
|
|
648
|
+
type: "string",
|
|
649
|
+
description: "Search query. Use specific terms: endpoint names, paths (/api/users), HTTP methods (GET, POST), or type names."
|
|
650
|
+
},
|
|
651
|
+
type: {
|
|
652
|
+
type: "string",
|
|
653
|
+
enum: ["endpoint", "schema"],
|
|
654
|
+
description: "Filter results by type. Omit to search both."
|
|
655
|
+
},
|
|
656
|
+
source: {
|
|
657
|
+
type: "string",
|
|
658
|
+
description: "Filter by API source ID"
|
|
659
|
+
},
|
|
660
|
+
limit: {
|
|
661
|
+
type: "number",
|
|
662
|
+
description: "Maximum results to return (default: 15)"
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
required: ["project", "query"]
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
async function handleSearch(input) {
|
|
669
|
+
debug3("Searching:", input);
|
|
670
|
+
if (!input.project || typeof input.project !== "string") {
|
|
671
|
+
return {
|
|
672
|
+
isError: true,
|
|
673
|
+
content: [
|
|
674
|
+
{
|
|
675
|
+
type: "text",
|
|
676
|
+
text: formatValidationError("project", "is required and must be a string")
|
|
677
|
+
}
|
|
678
|
+
]
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (!input.query || typeof input.query !== "string") {
|
|
682
|
+
return {
|
|
683
|
+
isError: true,
|
|
684
|
+
content: [
|
|
685
|
+
{
|
|
686
|
+
type: "text",
|
|
687
|
+
text: formatValidationError("query", "is required and must be a string")
|
|
688
|
+
}
|
|
689
|
+
]
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
const projectResult = await getProjectByIdentifier(input.project);
|
|
693
|
+
if (!projectResult.ok) {
|
|
694
|
+
const error = projectResult.error;
|
|
695
|
+
debug3("Project lookup failed:", error.code, error.message);
|
|
696
|
+
return {
|
|
697
|
+
isError: true,
|
|
698
|
+
content: [
|
|
699
|
+
{
|
|
700
|
+
type: "text",
|
|
701
|
+
text: formatError(error.code, error.message)
|
|
702
|
+
}
|
|
703
|
+
]
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const project = projectResult.value;
|
|
707
|
+
debug3("Searching in project:", project.projectName);
|
|
708
|
+
const searchResult = await searchSimple(
|
|
709
|
+
project.url,
|
|
710
|
+
input.query,
|
|
711
|
+
input.type,
|
|
712
|
+
input.source,
|
|
713
|
+
input.limit ?? 15
|
|
714
|
+
);
|
|
715
|
+
if (!searchResult.ok) {
|
|
716
|
+
const error = searchResult.error;
|
|
717
|
+
debug3("Search failed:", error.code, error.message);
|
|
718
|
+
return {
|
|
719
|
+
isError: true,
|
|
720
|
+
content: [
|
|
721
|
+
{
|
|
722
|
+
type: "text",
|
|
723
|
+
text: formatError(error.code, error.message)
|
|
724
|
+
}
|
|
725
|
+
]
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
const response = searchResult.value;
|
|
729
|
+
debug3(`Found ${response.total} result(s)`);
|
|
730
|
+
if (response.data.length === 0) {
|
|
731
|
+
const typeHint = input.type ? ` of type "${input.type}"` : "";
|
|
732
|
+
return {
|
|
733
|
+
content: [
|
|
734
|
+
{
|
|
735
|
+
type: "text",
|
|
736
|
+
text: `No results found for "${input.query}"${typeHint} in project ${project.projectName}.
|
|
737
|
+
|
|
738
|
+
Tips for better search results:
|
|
739
|
+
- Try different keywords (endpoint names, paths like /api/users, or type names)
|
|
740
|
+
- Remove the type filter to search both endpoints and schemas
|
|
741
|
+
- Use partial matches (e.g., "user" instead of "getUserById")`
|
|
742
|
+
}
|
|
743
|
+
]
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
const lines = [];
|
|
747
|
+
lines.push(`Found ${response.total} result(s) for "${input.query}" in **${project.projectName}**:`);
|
|
748
|
+
lines.push("");
|
|
749
|
+
for (const item of response.data) {
|
|
750
|
+
const displayType = item.type === "rest" ? "endpoint" : item.type;
|
|
751
|
+
if (item.type === "rest") {
|
|
752
|
+
lines.push(`**[${displayType}]** \`${item.method ?? "GET"}\` ${item.path ?? item.name}`);
|
|
753
|
+
lines.push(` - Name: ${item.name}`);
|
|
754
|
+
lines.push(` - ID: \`${item.id}\``);
|
|
755
|
+
lines.push(` - Source: ${item.source}`);
|
|
756
|
+
if (item.description) {
|
|
757
|
+
lines.push(` - ${item.description}`);
|
|
758
|
+
}
|
|
759
|
+
} else {
|
|
760
|
+
lines.push(`**[${displayType}]** ${item.name}`);
|
|
761
|
+
lines.push(` - ID: \`${item.id}\``);
|
|
762
|
+
lines.push(` - Source: ${item.source}`);
|
|
763
|
+
if (item.description) {
|
|
764
|
+
lines.push(` - ${item.description}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
lines.push("");
|
|
768
|
+
}
|
|
769
|
+
if (response.total > response.data.length) {
|
|
770
|
+
lines.push(`*Showing ${response.data.length} of ${response.total} results. Refine your query or increase the limit for more results.*`);
|
|
771
|
+
lines.push("");
|
|
772
|
+
}
|
|
773
|
+
lines.push("---");
|
|
774
|
+
lines.push("Use `get_documentation` with the ID and type to view full documentation.");
|
|
775
|
+
return {
|
|
776
|
+
content: [
|
|
777
|
+
{
|
|
778
|
+
type: "text",
|
|
779
|
+
text: lines.join("\n").trim()
|
|
780
|
+
}
|
|
781
|
+
]
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// app/intrig-mcp/src/formatters/documentation.ts
|
|
786
|
+
function formatTab(tab) {
|
|
787
|
+
return `## ${tab.name}
|
|
788
|
+
|
|
789
|
+
${tab.content}`;
|
|
790
|
+
}
|
|
791
|
+
function formatTabs(tabs) {
|
|
792
|
+
if (tabs.length === 0) {
|
|
793
|
+
return "";
|
|
794
|
+
}
|
|
795
|
+
return tabs.map(formatTab).join("\n\n");
|
|
796
|
+
}
|
|
797
|
+
function formatEndpointHeader(doc, source) {
|
|
798
|
+
const lines = [];
|
|
799
|
+
lines.push(`# ${doc.name}`);
|
|
800
|
+
lines.push("");
|
|
801
|
+
if (source) {
|
|
802
|
+
lines.push(`**Source:** ${source}`);
|
|
803
|
+
}
|
|
804
|
+
lines.push(`**Method:** \`${doc.method}\``);
|
|
805
|
+
lines.push(`**Path:** \`${doc.path}\``);
|
|
806
|
+
if (doc.description) {
|
|
807
|
+
lines.push("");
|
|
808
|
+
lines.push(doc.description);
|
|
809
|
+
}
|
|
810
|
+
return lines.join("\n");
|
|
811
|
+
}
|
|
812
|
+
function formatTypeReferences(doc) {
|
|
813
|
+
const sections = [];
|
|
814
|
+
if (doc.requestBody) {
|
|
815
|
+
sections.push(`**Request Body:** ${doc.requestBody.name}`);
|
|
816
|
+
}
|
|
817
|
+
if (doc.response) {
|
|
818
|
+
sections.push(`**Response:** ${doc.response.name}`);
|
|
819
|
+
}
|
|
820
|
+
if (doc.contentType) {
|
|
821
|
+
sections.push(`**Content-Type:** \`${doc.contentType}\``);
|
|
822
|
+
}
|
|
823
|
+
if (doc.responseType) {
|
|
824
|
+
sections.push(`**Response Type:** \`${doc.responseType}\``);
|
|
825
|
+
}
|
|
826
|
+
if (sections.length === 0) {
|
|
827
|
+
return "";
|
|
828
|
+
}
|
|
829
|
+
return sections.join("\n");
|
|
830
|
+
}
|
|
831
|
+
function formatEndpointDocumentation(doc, source) {
|
|
832
|
+
const sections = [];
|
|
833
|
+
sections.push(formatEndpointHeader(doc, source));
|
|
834
|
+
const typeRefs = formatTypeReferences(doc);
|
|
835
|
+
if (typeRefs) {
|
|
836
|
+
sections.push("");
|
|
837
|
+
sections.push(typeRefs);
|
|
838
|
+
}
|
|
839
|
+
if (doc.requestUrl) {
|
|
840
|
+
sections.push("");
|
|
841
|
+
sections.push(`**Request URL:** \`${doc.requestUrl}\``);
|
|
842
|
+
}
|
|
843
|
+
if (doc.tabs.length > 0) {
|
|
844
|
+
sections.push("");
|
|
845
|
+
sections.push(formatTabs(doc.tabs));
|
|
846
|
+
}
|
|
847
|
+
return sections.join("\n");
|
|
848
|
+
}
|
|
849
|
+
function extractEndpointRelatedTypes(doc) {
|
|
850
|
+
const types = [];
|
|
851
|
+
if (doc.requestBody) {
|
|
852
|
+
types.push(doc.requestBody);
|
|
853
|
+
}
|
|
854
|
+
if (doc.response) {
|
|
855
|
+
types.push(doc.response);
|
|
856
|
+
}
|
|
857
|
+
for (const variable of doc.variables) {
|
|
858
|
+
if (variable.relatedType) {
|
|
859
|
+
types.push(variable.relatedType);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const seen = /* @__PURE__ */ new Set();
|
|
863
|
+
return types.filter((t) => {
|
|
864
|
+
if (seen.has(t.id)) {
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
seen.add(t.id);
|
|
868
|
+
return true;
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
function formatSchemaHeader(doc, source) {
|
|
872
|
+
const lines = [];
|
|
873
|
+
lines.push(`# ${doc.name}`);
|
|
874
|
+
lines.push("");
|
|
875
|
+
if (source) {
|
|
876
|
+
lines.push(`**Source:** ${source}`);
|
|
877
|
+
}
|
|
878
|
+
if (doc.description) {
|
|
879
|
+
lines.push("");
|
|
880
|
+
lines.push(doc.description);
|
|
881
|
+
}
|
|
882
|
+
return lines.join("\n");
|
|
883
|
+
}
|
|
884
|
+
function formatJsonSchema(schema) {
|
|
885
|
+
const lines = [];
|
|
886
|
+
lines.push("## JSON Schema");
|
|
887
|
+
lines.push("");
|
|
888
|
+
lines.push("```json");
|
|
889
|
+
lines.push(JSON.stringify(schema, null, 2));
|
|
890
|
+
lines.push("```");
|
|
891
|
+
return lines.join("\n");
|
|
892
|
+
}
|
|
893
|
+
function formatRelatedTypes(types) {
|
|
894
|
+
if (types.length === 0) {
|
|
895
|
+
return "";
|
|
896
|
+
}
|
|
897
|
+
const lines = [];
|
|
898
|
+
lines.push("## Related Types");
|
|
899
|
+
lines.push("");
|
|
900
|
+
for (const type of types) {
|
|
901
|
+
lines.push(`- ${type.name} (\`${type.id}\`)`);
|
|
902
|
+
}
|
|
903
|
+
return lines.join("\n");
|
|
904
|
+
}
|
|
905
|
+
function formatRelatedEndpoints(endpoints) {
|
|
906
|
+
if (endpoints.length === 0) {
|
|
907
|
+
return "";
|
|
908
|
+
}
|
|
909
|
+
const lines = [];
|
|
910
|
+
lines.push("## Related Endpoints");
|
|
911
|
+
lines.push("");
|
|
912
|
+
for (const endpoint of endpoints) {
|
|
913
|
+
lines.push(`- \`${endpoint.method}\` ${endpoint.path} - ${endpoint.name}`);
|
|
914
|
+
}
|
|
915
|
+
return lines.join("\n");
|
|
916
|
+
}
|
|
917
|
+
function formatSchemaDocumentation(doc, source) {
|
|
918
|
+
const sections = [];
|
|
919
|
+
sections.push(formatSchemaHeader(doc, source));
|
|
920
|
+
if (doc.jsonSchema && Object.keys(doc.jsonSchema).length > 0) {
|
|
921
|
+
sections.push("");
|
|
922
|
+
sections.push(formatJsonSchema(doc.jsonSchema));
|
|
923
|
+
}
|
|
924
|
+
if (doc.tabs.length > 0) {
|
|
925
|
+
sections.push("");
|
|
926
|
+
sections.push(formatTabs(doc.tabs));
|
|
927
|
+
}
|
|
928
|
+
const relatedTypes = formatRelatedTypes(doc.relatedTypes);
|
|
929
|
+
if (relatedTypes) {
|
|
930
|
+
sections.push("");
|
|
931
|
+
sections.push(relatedTypes);
|
|
932
|
+
}
|
|
933
|
+
const relatedEndpoints = formatRelatedEndpoints(doc.relatedEndpoints);
|
|
934
|
+
if (relatedEndpoints) {
|
|
935
|
+
sections.push("");
|
|
936
|
+
sections.push(relatedEndpoints);
|
|
937
|
+
}
|
|
938
|
+
return sections.join("\n");
|
|
939
|
+
}
|
|
940
|
+
function extractTypeScriptDefinition(doc) {
|
|
941
|
+
const tsTab = doc.tabs.find(
|
|
942
|
+
(tab) => tab.name === "Typescript Type" || tab.name === "TypeScript Type"
|
|
943
|
+
);
|
|
944
|
+
if (!tsTab) {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
const definitionMatch = tsTab.content.match(
|
|
948
|
+
/## Definition\s*\n+```ts\n([\s\S]*?)```/
|
|
949
|
+
);
|
|
950
|
+
if (definitionMatch && definitionMatch[1]) {
|
|
951
|
+
return definitionMatch[1].trim();
|
|
952
|
+
}
|
|
953
|
+
const codeBlockMatch = tsTab.content.match(/```ts\n([\s\S]*?)```/);
|
|
954
|
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
955
|
+
return codeBlockMatch[1].trim();
|
|
956
|
+
}
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
function formatInlineSchemas(schemas, requestBodyType, responseType) {
|
|
960
|
+
const sections = [];
|
|
961
|
+
if (responseType && schemas.has(responseType.id)) {
|
|
962
|
+
const responseSchema = schemas.get(responseType.id);
|
|
963
|
+
const tsDefinition = extractTypeScriptDefinition(responseSchema);
|
|
964
|
+
if (tsDefinition) {
|
|
965
|
+
sections.push("## Response Type Definition");
|
|
966
|
+
sections.push("");
|
|
967
|
+
sections.push(`\`${responseType.name}\``);
|
|
968
|
+
sections.push("");
|
|
969
|
+
sections.push("```typescript");
|
|
970
|
+
sections.push(tsDefinition);
|
|
971
|
+
sections.push("```");
|
|
972
|
+
if (responseSchema.relatedTypes.length > 0) {
|
|
973
|
+
const inlinedRelated = [];
|
|
974
|
+
for (const related of responseSchema.relatedTypes.slice(0, 5)) {
|
|
975
|
+
if (schemas.has(related.id)) {
|
|
976
|
+
const relatedDef = extractTypeScriptDefinition(schemas.get(related.id));
|
|
977
|
+
if (relatedDef) {
|
|
978
|
+
inlinedRelated.push(`// ${related.name}`);
|
|
979
|
+
inlinedRelated.push(relatedDef);
|
|
980
|
+
inlinedRelated.push("");
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (inlinedRelated.length > 0) {
|
|
985
|
+
sections.push("");
|
|
986
|
+
sections.push("### Nested Types");
|
|
987
|
+
sections.push("");
|
|
988
|
+
sections.push("```typescript");
|
|
989
|
+
sections.push(inlinedRelated.join("\n").trim());
|
|
990
|
+
sections.push("```");
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (requestBodyType && schemas.has(requestBodyType.id)) {
|
|
996
|
+
const requestSchema = schemas.get(requestBodyType.id);
|
|
997
|
+
const tsDefinition = extractTypeScriptDefinition(requestSchema);
|
|
998
|
+
if (tsDefinition) {
|
|
999
|
+
if (sections.length > 0) {
|
|
1000
|
+
sections.push("");
|
|
1001
|
+
}
|
|
1002
|
+
sections.push("## Request Body Type Definition");
|
|
1003
|
+
sections.push("");
|
|
1004
|
+
sections.push(`\`${requestBodyType.name}\``);
|
|
1005
|
+
sections.push("");
|
|
1006
|
+
sections.push("```typescript");
|
|
1007
|
+
sections.push(tsDefinition);
|
|
1008
|
+
sections.push("```");
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return sections.join("\n");
|
|
1012
|
+
}
|
|
1013
|
+
function formatEndpointDocumentationWithSchemas(doc, schemas, source) {
|
|
1014
|
+
const sections = [];
|
|
1015
|
+
sections.push(formatEndpointHeader(doc, source));
|
|
1016
|
+
const typeRefs = formatTypeReferences(doc);
|
|
1017
|
+
if (typeRefs) {
|
|
1018
|
+
sections.push("");
|
|
1019
|
+
sections.push(typeRefs);
|
|
1020
|
+
}
|
|
1021
|
+
if (doc.requestUrl) {
|
|
1022
|
+
sections.push("");
|
|
1023
|
+
sections.push(`**Request URL:** \`${doc.requestUrl}\``);
|
|
1024
|
+
}
|
|
1025
|
+
if (doc.tabs.length > 0) {
|
|
1026
|
+
sections.push("");
|
|
1027
|
+
sections.push(formatTabs(doc.tabs));
|
|
1028
|
+
}
|
|
1029
|
+
const inlineSchemas = formatInlineSchemas(
|
|
1030
|
+
schemas,
|
|
1031
|
+
doc.requestBody,
|
|
1032
|
+
doc.response
|
|
1033
|
+
);
|
|
1034
|
+
if (inlineSchemas) {
|
|
1035
|
+
sections.push("");
|
|
1036
|
+
sections.push(inlineSchemas);
|
|
1037
|
+
}
|
|
1038
|
+
return sections.join("\n");
|
|
1039
|
+
}
|
|
1040
|
+
function createEndpointResult(doc, source) {
|
|
1041
|
+
return {
|
|
1042
|
+
id: doc.id,
|
|
1043
|
+
name: doc.name,
|
|
1044
|
+
type: "endpoint",
|
|
1045
|
+
source,
|
|
1046
|
+
method: doc.method,
|
|
1047
|
+
path: doc.path,
|
|
1048
|
+
documentation: formatEndpointDocumentation(doc, source),
|
|
1049
|
+
relatedTypes: extractEndpointRelatedTypes(doc),
|
|
1050
|
+
relatedEndpoints: []
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
function createEndpointResultWithSchemas(doc, schemas, source) {
|
|
1054
|
+
const inlinedIds = /* @__PURE__ */ new Set();
|
|
1055
|
+
if (doc.requestBody)
|
|
1056
|
+
inlinedIds.add(doc.requestBody.id);
|
|
1057
|
+
if (doc.response)
|
|
1058
|
+
inlinedIds.add(doc.response.id);
|
|
1059
|
+
if (doc.response && schemas.has(doc.response.id)) {
|
|
1060
|
+
const responseSchema = schemas.get(doc.response.id);
|
|
1061
|
+
for (const related of responseSchema.relatedTypes.slice(0, 5)) {
|
|
1062
|
+
if (schemas.has(related.id)) {
|
|
1063
|
+
inlinedIds.add(related.id);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
const allRelatedTypes = extractEndpointRelatedTypes(doc);
|
|
1068
|
+
const remainingRelatedTypes = allRelatedTypes.filter(
|
|
1069
|
+
(t) => !inlinedIds.has(t.id)
|
|
1070
|
+
);
|
|
1071
|
+
return {
|
|
1072
|
+
id: doc.id,
|
|
1073
|
+
name: doc.name,
|
|
1074
|
+
type: "endpoint",
|
|
1075
|
+
source,
|
|
1076
|
+
method: doc.method,
|
|
1077
|
+
path: doc.path,
|
|
1078
|
+
documentation: formatEndpointDocumentationWithSchemas(doc, schemas, source),
|
|
1079
|
+
relatedTypes: remainingRelatedTypes,
|
|
1080
|
+
relatedEndpoints: []
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
function createSchemaResult(doc, source) {
|
|
1084
|
+
return {
|
|
1085
|
+
id: doc.id,
|
|
1086
|
+
name: doc.name,
|
|
1087
|
+
type: "schema",
|
|
1088
|
+
source,
|
|
1089
|
+
documentation: formatSchemaDocumentation(doc, source),
|
|
1090
|
+
relatedTypes: doc.relatedTypes.map((t) => ({ name: t.name, id: t.id })),
|
|
1091
|
+
relatedEndpoints: doc.relatedEndpoints.map((e) => ({
|
|
1092
|
+
name: e.name,
|
|
1093
|
+
id: e.id
|
|
1094
|
+
}))
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// app/intrig-mcp/src/mcp/tools/get-documentation.ts
|
|
1099
|
+
var debug4 = createDebugger("tools:get-documentation");
|
|
1100
|
+
var getDocumentationTool = {
|
|
1101
|
+
name: "get_documentation",
|
|
1102
|
+
description: "Get full documentation for an API endpoint or schema. Use IDs from search results. Returns formatted markdown documentation including request/response types with INLINED TypeScript definitions, examples, and related resources for drill-down.",
|
|
1103
|
+
inputSchema: {
|
|
1104
|
+
type: "object",
|
|
1105
|
+
properties: {
|
|
1106
|
+
project: {
|
|
1107
|
+
type: "string",
|
|
1108
|
+
description: "Project path or projectName"
|
|
1109
|
+
},
|
|
1110
|
+
type: {
|
|
1111
|
+
type: "string",
|
|
1112
|
+
enum: ["endpoint", "schema"],
|
|
1113
|
+
description: "Resource type"
|
|
1114
|
+
},
|
|
1115
|
+
id: {
|
|
1116
|
+
type: "string",
|
|
1117
|
+
description: "Resource ID from search results"
|
|
1118
|
+
}
|
|
1119
|
+
},
|
|
1120
|
+
required: ["project", "type", "id"]
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
async function fetchEndpointSchemas(daemonUrl, requestBody, response) {
|
|
1124
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
1125
|
+
const schemaIdsToFetch = [];
|
|
1126
|
+
if (requestBody) {
|
|
1127
|
+
schemaIdsToFetch.push(requestBody.id);
|
|
1128
|
+
}
|
|
1129
|
+
if (response) {
|
|
1130
|
+
schemaIdsToFetch.push(response.id);
|
|
1131
|
+
}
|
|
1132
|
+
if (schemaIdsToFetch.length === 0) {
|
|
1133
|
+
return schemas;
|
|
1134
|
+
}
|
|
1135
|
+
debug4("Fetching schemas for endpoint:", schemaIdsToFetch);
|
|
1136
|
+
const results = await Promise.allSettled(
|
|
1137
|
+
schemaIdsToFetch.map((id) => getSchemaDocumentation(daemonUrl, id))
|
|
1138
|
+
);
|
|
1139
|
+
for (let i = 0; i < results.length; i++) {
|
|
1140
|
+
const result = results[i];
|
|
1141
|
+
const id = schemaIdsToFetch[i];
|
|
1142
|
+
if (result.status === "fulfilled" && result.value.ok) {
|
|
1143
|
+
schemas.set(id, result.value.value);
|
|
1144
|
+
} else {
|
|
1145
|
+
debug4("Failed to fetch schema:", id);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (response && schemas.has(response.id)) {
|
|
1149
|
+
const responseSchema = schemas.get(response.id);
|
|
1150
|
+
const nestedIds = responseSchema.relatedTypes.slice(0, 5).filter((t) => !schemas.has(t.id)).map((t) => t.id);
|
|
1151
|
+
if (nestedIds.length > 0) {
|
|
1152
|
+
debug4("Fetching nested schemas:", nestedIds);
|
|
1153
|
+
const nestedResults = await Promise.allSettled(
|
|
1154
|
+
nestedIds.map((id) => getSchemaDocumentation(daemonUrl, id))
|
|
1155
|
+
);
|
|
1156
|
+
for (let i = 0; i < nestedResults.length; i++) {
|
|
1157
|
+
const result = nestedResults[i];
|
|
1158
|
+
const id = nestedIds[i];
|
|
1159
|
+
if (result.status === "fulfilled" && result.value.ok) {
|
|
1160
|
+
schemas.set(id, result.value.value);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
debug4("Fetched", schemas.size, "schemas for inlining");
|
|
1166
|
+
return schemas;
|
|
1167
|
+
}
|
|
1168
|
+
async function handleGetDocumentation(input) {
|
|
1169
|
+
debug4("Getting documentation:", input);
|
|
1170
|
+
if (!input.project || typeof input.project !== "string") {
|
|
1171
|
+
return {
|
|
1172
|
+
isError: true,
|
|
1173
|
+
content: [
|
|
1174
|
+
{
|
|
1175
|
+
type: "text",
|
|
1176
|
+
text: formatValidationError("project", "is required and must be a string")
|
|
1177
|
+
}
|
|
1178
|
+
]
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
if (!input.type || input.type !== "endpoint" && input.type !== "schema") {
|
|
1182
|
+
return {
|
|
1183
|
+
isError: true,
|
|
1184
|
+
content: [
|
|
1185
|
+
{
|
|
1186
|
+
type: "text",
|
|
1187
|
+
text: formatValidationError("type", 'must be either "endpoint" or "schema"')
|
|
1188
|
+
}
|
|
1189
|
+
]
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
if (!input.id || typeof input.id !== "string") {
|
|
1193
|
+
return {
|
|
1194
|
+
isError: true,
|
|
1195
|
+
content: [
|
|
1196
|
+
{
|
|
1197
|
+
type: "text",
|
|
1198
|
+
text: formatValidationError("id", "is required and must be a string")
|
|
1199
|
+
}
|
|
1200
|
+
]
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
const projectResult = await getProjectByIdentifier(input.project);
|
|
1204
|
+
if (!projectResult.ok) {
|
|
1205
|
+
const error = projectResult.error;
|
|
1206
|
+
debug4("Project lookup failed:", error.code, error.message);
|
|
1207
|
+
return {
|
|
1208
|
+
isError: true,
|
|
1209
|
+
content: [
|
|
1210
|
+
{
|
|
1211
|
+
type: "text",
|
|
1212
|
+
text: formatError(error.code, error.message)
|
|
1213
|
+
}
|
|
1214
|
+
]
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
const project = projectResult.value;
|
|
1218
|
+
debug4("Found project:", project.projectName);
|
|
1219
|
+
if (input.type === "endpoint") {
|
|
1220
|
+
const docResult = await getEndpointDocumentation(project.url, input.id);
|
|
1221
|
+
if (!docResult.ok) {
|
|
1222
|
+
const error = docResult.error;
|
|
1223
|
+
debug4("Endpoint documentation fetch failed:", error.code, error.message);
|
|
1224
|
+
if (error.code === "RESOURCE_NOT_FOUND") {
|
|
1225
|
+
return {
|
|
1226
|
+
isError: true,
|
|
1227
|
+
content: [
|
|
1228
|
+
{
|
|
1229
|
+
type: "text",
|
|
1230
|
+
text: `Endpoint not found: "${input.id}"
|
|
1231
|
+
|
|
1232
|
+
The ID may be invalid or the endpoint may have been removed.
|
|
1233
|
+
Use \`search\` to find current endpoint IDs.`
|
|
1234
|
+
}
|
|
1235
|
+
]
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
return {
|
|
1239
|
+
isError: true,
|
|
1240
|
+
content: [
|
|
1241
|
+
{
|
|
1242
|
+
type: "text",
|
|
1243
|
+
text: formatError(error.code, error.message)
|
|
1244
|
+
}
|
|
1245
|
+
]
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
const endpointDoc = docResult.value;
|
|
1249
|
+
debug4("Got endpoint documentation:", endpointDoc.name);
|
|
1250
|
+
const schemas = await fetchEndpointSchemas(
|
|
1251
|
+
project.url,
|
|
1252
|
+
endpointDoc.requestBody,
|
|
1253
|
+
endpointDoc.response
|
|
1254
|
+
);
|
|
1255
|
+
const formatted = schemas.size > 0 ? createEndpointResultWithSchemas(endpointDoc, schemas) : createEndpointResult(endpointDoc);
|
|
1256
|
+
const lines = [];
|
|
1257
|
+
lines.push(formatted.documentation);
|
|
1258
|
+
if (formatted.relatedTypes.length > 0) {
|
|
1259
|
+
lines.push("");
|
|
1260
|
+
lines.push("---");
|
|
1261
|
+
lines.push("");
|
|
1262
|
+
lines.push('**Additional Related Types** (use `get_documentation` with type="schema"):');
|
|
1263
|
+
for (const rt of formatted.relatedTypes) {
|
|
1264
|
+
lines.push(`- ${rt.name}: \`${rt.id}\``);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
return {
|
|
1268
|
+
content: [
|
|
1269
|
+
{
|
|
1270
|
+
type: "text",
|
|
1271
|
+
text: lines.join("\n")
|
|
1272
|
+
}
|
|
1273
|
+
]
|
|
1274
|
+
};
|
|
1275
|
+
} else {
|
|
1276
|
+
const docResult = await getSchemaDocumentation(project.url, input.id);
|
|
1277
|
+
if (!docResult.ok) {
|
|
1278
|
+
const error = docResult.error;
|
|
1279
|
+
debug4("Schema documentation fetch failed:", error.code, error.message);
|
|
1280
|
+
if (error.code === "RESOURCE_NOT_FOUND") {
|
|
1281
|
+
return {
|
|
1282
|
+
isError: true,
|
|
1283
|
+
content: [
|
|
1284
|
+
{
|
|
1285
|
+
type: "text",
|
|
1286
|
+
text: `Schema not found: "${input.id}"
|
|
1287
|
+
|
|
1288
|
+
The ID may be invalid or the schema may have been removed.
|
|
1289
|
+
Use \`search\` with type="schema" to find current schema IDs.`
|
|
1290
|
+
}
|
|
1291
|
+
]
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
return {
|
|
1295
|
+
isError: true,
|
|
1296
|
+
content: [
|
|
1297
|
+
{
|
|
1298
|
+
type: "text",
|
|
1299
|
+
text: formatError(error.code, error.message)
|
|
1300
|
+
}
|
|
1301
|
+
]
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
debug4("Got schema documentation:", docResult.value.name);
|
|
1305
|
+
const formatted = createSchemaResult(docResult.value);
|
|
1306
|
+
const lines = [];
|
|
1307
|
+
lines.push(formatted.documentation);
|
|
1308
|
+
const hasRelated = formatted.relatedTypes.length > 0 || formatted.relatedEndpoints.length > 0;
|
|
1309
|
+
if (hasRelated) {
|
|
1310
|
+
lines.push("");
|
|
1311
|
+
lines.push("---");
|
|
1312
|
+
lines.push("");
|
|
1313
|
+
lines.push("**Drill-down** (use `get_documentation` with appropriate type):");
|
|
1314
|
+
if (formatted.relatedTypes.length > 0) {
|
|
1315
|
+
lines.push("");
|
|
1316
|
+
lines.push('Related Types (type="schema"):');
|
|
1317
|
+
for (const rt of formatted.relatedTypes) {
|
|
1318
|
+
lines.push(`- ${rt.name}: \`${rt.id}\``);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
if (formatted.relatedEndpoints.length > 0) {
|
|
1322
|
+
lines.push("");
|
|
1323
|
+
lines.push('Related Endpoints (type="endpoint"):');
|
|
1324
|
+
for (const re of formatted.relatedEndpoints) {
|
|
1325
|
+
lines.push(`- ${re.name}: \`${re.id}\``);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return {
|
|
1330
|
+
content: [
|
|
1331
|
+
{
|
|
1332
|
+
type: "text",
|
|
1333
|
+
text: lines.join("\n")
|
|
1334
|
+
}
|
|
1335
|
+
]
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// app/intrig-mcp/src/mcp/server.ts
|
|
1341
|
+
var TOOLS = [
|
|
1342
|
+
listProjectsTool,
|
|
1343
|
+
getProjectTool,
|
|
1344
|
+
searchTool,
|
|
1345
|
+
getDocumentationTool
|
|
1346
|
+
];
|
|
1347
|
+
function createServer() {
|
|
1348
|
+
const server = new Server(
|
|
1349
|
+
{
|
|
1350
|
+
name: "intrig-mcp",
|
|
1351
|
+
version: "0.0.1"
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
capabilities: {
|
|
1355
|
+
tools: {}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
);
|
|
1359
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1360
|
+
return {
|
|
1361
|
+
tools: TOOLS
|
|
1362
|
+
};
|
|
1363
|
+
});
|
|
1364
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1365
|
+
const { name, arguments: args } = request.params;
|
|
1366
|
+
try {
|
|
1367
|
+
switch (name) {
|
|
1368
|
+
case "list_projects":
|
|
1369
|
+
return await handleListProjects();
|
|
1370
|
+
case "get_project":
|
|
1371
|
+
return await handleGetProject(args);
|
|
1372
|
+
case "search":
|
|
1373
|
+
return await handleSearch(
|
|
1374
|
+
args
|
|
1375
|
+
);
|
|
1376
|
+
case "get_documentation":
|
|
1377
|
+
return await handleGetDocumentation(
|
|
1378
|
+
args
|
|
1379
|
+
);
|
|
1380
|
+
default:
|
|
1381
|
+
return {
|
|
1382
|
+
isError: true,
|
|
1383
|
+
content: [
|
|
1384
|
+
{
|
|
1385
|
+
type: "text",
|
|
1386
|
+
text: `Unknown tool: ${name}`
|
|
1387
|
+
}
|
|
1388
|
+
]
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1393
|
+
return {
|
|
1394
|
+
isError: true,
|
|
1395
|
+
content: [
|
|
1396
|
+
{
|
|
1397
|
+
type: "text",
|
|
1398
|
+
text: `Internal error: ${message}`
|
|
1399
|
+
}
|
|
1400
|
+
]
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
return server;
|
|
1405
|
+
}
|
|
1406
|
+
async function startServer() {
|
|
1407
|
+
const server = createServer();
|
|
1408
|
+
const transport = new StdioServerTransport();
|
|
1409
|
+
await server.connect(transport);
|
|
1410
|
+
console.error("Intrig MCP server started");
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// app/intrig-mcp/src/main.ts
|
|
1414
|
+
async function main() {
|
|
1415
|
+
const shutdown = () => {
|
|
1416
|
+
console.error("Intrig MCP server shutting down");
|
|
1417
|
+
process.exit(0);
|
|
1418
|
+
};
|
|
1419
|
+
process.on("SIGINT", shutdown);
|
|
1420
|
+
process.on("SIGTERM", shutdown);
|
|
1421
|
+
await startServer();
|
|
1422
|
+
}
|
|
1423
|
+
main().catch((error) => {
|
|
1424
|
+
console.error("Fatal error:", error);
|
|
1425
|
+
process.exit(1);
|
|
1426
|
+
});
|