@intangle/mcp-server 2.5.4 → 2.5.6
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/.env.local +1 -0
- package/dist/index.js +109 -4
- package/dist/tool-definitions.js +144 -12
- package/index.ts +161 -10
- package/package.json +1 -1
- package/tool-definitions.ts +152 -12
package/.env.local
CHANGED
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { dirname, join } from "path";
|
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
-
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { CallToolRequestSchema, ErrorCode, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
11
11
|
import { config } from "dotenv";
|
|
12
12
|
import fetch from "node-fetch";
|
|
13
13
|
import { TOOLS } from "./tool-definitions.js";
|
|
@@ -101,6 +101,13 @@ try {
|
|
|
101
101
|
}
|
|
102
102
|
return url.toString();
|
|
103
103
|
}
|
|
104
|
+
function buildRemoteApiUrl() {
|
|
105
|
+
const url = new URL("/api/mcp-remote", API_BASE_URL);
|
|
106
|
+
if (VERCEL_BYPASS_TOKEN) {
|
|
107
|
+
url.searchParams.set("x-vercel-protection-bypass", VERCEL_BYPASS_TOKEN);
|
|
108
|
+
}
|
|
109
|
+
return url.toString();
|
|
110
|
+
}
|
|
104
111
|
async function makeApiCall(endpoint, data, timeoutMs) {
|
|
105
112
|
// Ensure we have client info before making requests
|
|
106
113
|
ensureClientInfo();
|
|
@@ -160,6 +167,57 @@ try {
|
|
|
160
167
|
clearTimeout(timeoutId);
|
|
161
168
|
}
|
|
162
169
|
}
|
|
170
|
+
async function makeRemoteRpcCall(method, params) {
|
|
171
|
+
ensureClientInfo();
|
|
172
|
+
const headers = {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
Authorization: `Bearer ${MCP_API_KEY}`,
|
|
175
|
+
"User-Agent": mcpClientName
|
|
176
|
+
? `${mcpClientName}/${mcpClientVersion || "unknown"} (mcp-stdio)`
|
|
177
|
+
: "MCP-Client-Stdio/1.1.2 (mcp)",
|
|
178
|
+
};
|
|
179
|
+
if (mcpClientName) {
|
|
180
|
+
headers["X-MCP-Client-Name"] = mcpClientName;
|
|
181
|
+
}
|
|
182
|
+
if (mcpClientVersion) {
|
|
183
|
+
headers["X-MCP-Client-Version"] = mcpClientVersion;
|
|
184
|
+
}
|
|
185
|
+
if (VERCEL_BYPASS_TOKEN) {
|
|
186
|
+
headers["x-vercel-protection-bypass"] = VERCEL_BYPASS_TOKEN;
|
|
187
|
+
}
|
|
188
|
+
const requestId = Date.now();
|
|
189
|
+
const rpcBody = {
|
|
190
|
+
jsonrpc: "2.0",
|
|
191
|
+
id: requestId,
|
|
192
|
+
method,
|
|
193
|
+
params,
|
|
194
|
+
};
|
|
195
|
+
const response = await fetch(buildRemoteApiUrl(), {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers,
|
|
198
|
+
body: JSON.stringify(rpcBody),
|
|
199
|
+
});
|
|
200
|
+
const responseText = await response.text();
|
|
201
|
+
let responseJson = {};
|
|
202
|
+
try {
|
|
203
|
+
responseJson = responseText ? JSON.parse(responseText) : {};
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
throw new Error(`Failed to parse MCP remote ${method} response`);
|
|
207
|
+
}
|
|
208
|
+
if (!response.ok) {
|
|
209
|
+
const message = responseJson?.error?.message ||
|
|
210
|
+
responseJson?.message ||
|
|
211
|
+
`${response.status} ${response.statusText}`;
|
|
212
|
+
throw new Error(`MCP remote ${method} failed: ${message}`);
|
|
213
|
+
}
|
|
214
|
+
if (responseJson?.error) {
|
|
215
|
+
const code = responseJson.error.code;
|
|
216
|
+
const message = responseJson.error.message || "Unknown MCP remote error";
|
|
217
|
+
throw new Error(`MCP remote ${method} error (${code}): ${message}`);
|
|
218
|
+
}
|
|
219
|
+
return responseJson?.result ?? {};
|
|
220
|
+
}
|
|
163
221
|
const server = new Server({
|
|
164
222
|
name: "intangle-context",
|
|
165
223
|
version: "1.0.0",
|
|
@@ -171,12 +229,33 @@ try {
|
|
|
171
229
|
],
|
|
172
230
|
}, {
|
|
173
231
|
capabilities: {
|
|
232
|
+
resources: {},
|
|
174
233
|
tools: {},
|
|
175
234
|
},
|
|
176
235
|
});
|
|
177
236
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
178
237
|
tools: TOOLS,
|
|
179
238
|
}));
|
|
239
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
240
|
+
const result = await makeRemoteRpcCall("resources/list", {});
|
|
241
|
+
return {
|
|
242
|
+
resources: Array.isArray(result?.resources) ? result.resources : [],
|
|
243
|
+
nextCursor: typeof result?.nextCursor === "string" ? result.nextCursor : undefined,
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
247
|
+
const uri = request?.params?.uri;
|
|
248
|
+
if (!uri || typeof uri !== "string") {
|
|
249
|
+
throw new McpError(ErrorCode.InvalidParams, "uri is required for resources/read");
|
|
250
|
+
}
|
|
251
|
+
const result = await makeRemoteRpcCall("resources/read", { uri });
|
|
252
|
+
return {
|
|
253
|
+
contents: Array.isArray(result?.contents) ? result.contents : [],
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
257
|
+
resourceTemplates: [],
|
|
258
|
+
}));
|
|
180
259
|
async function handleSearchContext(args) {
|
|
181
260
|
const { space_id, query, topics } = args;
|
|
182
261
|
// Require space_id
|
|
@@ -250,16 +329,39 @@ try {
|
|
|
250
329
|
delete: args.delete,
|
|
251
330
|
});
|
|
252
331
|
}
|
|
332
|
+
async function handleUpdateFolders(args) {
|
|
333
|
+
if (!args.space_id) {
|
|
334
|
+
throw new Error("space_id is required. Use view_spaces to see available options.");
|
|
335
|
+
}
|
|
336
|
+
const hasOperation = !!args.list ||
|
|
337
|
+
(Array.isArray(args.create) && args.create.length > 0) ||
|
|
338
|
+
(Array.isArray(args.rename) && args.rename.length > 0) ||
|
|
339
|
+
(Array.isArray(args.move_items) && args.move_items.length > 0) ||
|
|
340
|
+
(Array.isArray(args.delete) && args.delete.length > 0);
|
|
341
|
+
if (!hasOperation) {
|
|
342
|
+
throw new Error("At least one operation is required: list, create, rename, move_items, or delete.");
|
|
343
|
+
}
|
|
344
|
+
return makeApiCall("update-folders", {
|
|
345
|
+
space_id: args.space_id,
|
|
346
|
+
list: args.list,
|
|
347
|
+
create: args.create,
|
|
348
|
+
rename: args.rename,
|
|
349
|
+
move_items: args.move_items,
|
|
350
|
+
delete: args.delete,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
253
353
|
async function handleMessage(args) {
|
|
254
354
|
if (!args.space_id || !args.content) {
|
|
255
355
|
throw new Error("space_id and content are required");
|
|
256
356
|
}
|
|
357
|
+
// Keep one-turn MCP calls safely under common external tool-call deadlines
|
|
358
|
+
// while still allowing caller override for longer jobs.
|
|
257
359
|
const timeoutMs = typeof args.timeout_ms === "number" && Number.isFinite(args.timeout_ms)
|
|
258
360
|
? Math.max(10_000, Math.min(300_000, Math.trunc(args.timeout_ms)))
|
|
259
|
-
:
|
|
361
|
+
: 100_000;
|
|
260
362
|
// Give backend a little extra headroom beyond requested assistant timeout.
|
|
261
|
-
const requestTimeout = timeoutMs
|
|
262
|
-
return makeApiCall("message", args, requestTimeout);
|
|
363
|
+
const requestTimeout = timeoutMs + 10_000;
|
|
364
|
+
return makeApiCall("message", { ...args, timeout_ms: timeoutMs }, requestTimeout);
|
|
263
365
|
}
|
|
264
366
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
265
367
|
const { name, arguments: args } = request.params;
|
|
@@ -295,6 +397,9 @@ try {
|
|
|
295
397
|
case "update_space":
|
|
296
398
|
result = await handleUpdateSpace(args);
|
|
297
399
|
break;
|
|
400
|
+
case "update_folders":
|
|
401
|
+
result = await handleUpdateFolders(args);
|
|
402
|
+
break;
|
|
298
403
|
case "message":
|
|
299
404
|
result = await handleMessage(args);
|
|
300
405
|
break;
|
package/dist/tool-definitions.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Tools definition - matches the stdio server
|
|
2
|
-
|
|
2
|
+
const TOOL_DEFINITIONS = [
|
|
3
3
|
{
|
|
4
4
|
name: "search",
|
|
5
5
|
title: "Search Space",
|
|
6
|
-
description: "Search for context, tasks, skills, and projects within a space. System automatically extracts quantity from natural language ('show 3 tasks' → 3 results, 'the last one' → 1 result) and intelligently formats results (1-3 items → summaries, 4+ items → IDs only).
|
|
6
|
+
description: "Search for context, tasks, skills, and projects within a space. System automatically extracts quantity from natural language ('show 3 tasks' → 3 results, 'the last one' → 1 result) and intelligently formats results (1-3 items → summaries, 4+ items → IDs only). Default to one search per question, then use fetch_items for full details instead of repeating paraphrased searches.",
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
@@ -90,7 +90,7 @@ export const TOOLS = [
|
|
|
90
90
|
{
|
|
91
91
|
name: "start",
|
|
92
92
|
title: "Start Space Session",
|
|
93
|
-
description: "
|
|
93
|
+
description: "Load a space fast for a new external caller. Returns assistant-curated current items, useful insights, skills, recent conversations, and assistant preferences so the caller can get oriented quickly and fetch deeper details only when needed.",
|
|
94
94
|
inputSchema: {
|
|
95
95
|
type: "object",
|
|
96
96
|
properties: {
|
|
@@ -157,7 +157,7 @@ export const TOOLS = [
|
|
|
157
157
|
{
|
|
158
158
|
name: "update_memory",
|
|
159
159
|
title: "Update Memory",
|
|
160
|
-
description: "Add, update, or delete items in a space. Supports any combination of operations in a single call.\n\nTIPS FOR RICH MEMORY: The more context you provide, the smarter the system becomes:\n- Include WHY something matters, not just WHAT it is\n- Note user preferences, patterns, and approaches you observe\n- Describe repeatable workflows as skills (step-by-step procedures)\n- Link related items using parent_id or linkedItemIds\n- Use descriptive titles that capture the essence\n- Add context about decisions, reasoning, and outcomes\n\nThe system learns from patterns - sharing how the user works helps it anticipate their needs.",
|
|
160
|
+
description: "Add, update, or delete items in a space. Supports any combination of operations in a single call.\n\nIMPORTANT: Include at least one operation: add, update, or delete.\n\nTIPS FOR RICH MEMORY: The more context you provide, the smarter the system becomes:\n- Include WHY something matters, not just WHAT it is\n- Note user preferences, patterns, and approaches you observe\n- Describe repeatable workflows as skills (step-by-step procedures)\n- Link related items using parent_id or linkedItemIds\n- Use descriptive titles that capture the essence\n- Add context about decisions, reasoning, and outcomes\n\nThe system learns from patterns - sharing how the user works helps it anticipate their needs.",
|
|
161
161
|
inputSchema: {
|
|
162
162
|
type: "object",
|
|
163
163
|
properties: {
|
|
@@ -185,8 +185,8 @@ export const TOOLS = [
|
|
|
185
185
|
},
|
|
186
186
|
type: {
|
|
187
187
|
type: "string",
|
|
188
|
-
enum: ["task", "context", "skill", "document"],
|
|
189
|
-
description: "REQUIRED: Item type. 'task' for actionable items, 'context' for knowledge/facts, 'skill' for workflows, 'document' for long-form reference material. Omitting this triggers expensive auto-classification."
|
|
188
|
+
enum: ["task", "context", "skill", "document", "insight"],
|
|
189
|
+
description: "REQUIRED: Item type. 'task' for actionable items, 'context' for knowledge/facts, 'skill' for workflows, 'document' for long-form reference material, 'insight' for durable patterns/principles. Omitting this triggers expensive auto-classification."
|
|
190
190
|
},
|
|
191
191
|
subtasks: {
|
|
192
192
|
type: "array",
|
|
@@ -306,13 +306,117 @@ export const TOOLS = [
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
},
|
|
309
|
-
required: ["space_id"]
|
|
309
|
+
required: ["space_id"],
|
|
310
|
+
anyOf: [{ required: ["add"] }, { required: ["update"] }, { required: ["delete"] }]
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "update_folders",
|
|
315
|
+
title: "Update Folders",
|
|
316
|
+
description: "Unified folder management. Supports list, create, rename, move_items, and delete operations in one call so external MCP callers can organize memory the same way as the in-app assistant.",
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
space_id: {
|
|
321
|
+
type: "string",
|
|
322
|
+
description: "REQUIRED: Space to operate in (use view_spaces to see available options)."
|
|
323
|
+
},
|
|
324
|
+
list: {
|
|
325
|
+
type: "object",
|
|
326
|
+
description: "Optional list operation. Use this first to discover existing folders and IDs.",
|
|
327
|
+
properties: {
|
|
328
|
+
section_type: {
|
|
329
|
+
type: "string",
|
|
330
|
+
enum: [
|
|
331
|
+
"projects",
|
|
332
|
+
"insights",
|
|
333
|
+
"insights_space",
|
|
334
|
+
"insights_user",
|
|
335
|
+
"agents",
|
|
336
|
+
"skills",
|
|
337
|
+
"conversations",
|
|
338
|
+
"dashboard"
|
|
339
|
+
],
|
|
340
|
+
description: "Optional: filter folders by sidebar section."
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
create: {
|
|
345
|
+
type: "array",
|
|
346
|
+
description: "Optional create operations.",
|
|
347
|
+
items: {
|
|
348
|
+
type: "object",
|
|
349
|
+
properties: {
|
|
350
|
+
name: { type: "string", description: "Folder name." },
|
|
351
|
+
section_type: {
|
|
352
|
+
type: "string",
|
|
353
|
+
enum: [
|
|
354
|
+
"projects",
|
|
355
|
+
"insights",
|
|
356
|
+
"insights_space",
|
|
357
|
+
"insights_user",
|
|
358
|
+
"agents",
|
|
359
|
+
"skills",
|
|
360
|
+
"conversations",
|
|
361
|
+
"dashboard"
|
|
362
|
+
],
|
|
363
|
+
description: "Sidebar section this folder belongs to."
|
|
364
|
+
},
|
|
365
|
+
parent_id: {
|
|
366
|
+
type: "string",
|
|
367
|
+
description: "Optional parent folder ID for nesting."
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
required: ["name", "section_type"]
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
rename: {
|
|
374
|
+
type: "array",
|
|
375
|
+
description: "Optional rename operations.",
|
|
376
|
+
items: {
|
|
377
|
+
type: "object",
|
|
378
|
+
properties: {
|
|
379
|
+
folder_id: { type: "string", description: "Folder ID to rename." },
|
|
380
|
+
new_name: { type: "string", description: "New folder name." }
|
|
381
|
+
},
|
|
382
|
+
required: ["folder_id", "new_name"]
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
move_items: {
|
|
386
|
+
type: "array",
|
|
387
|
+
description: "Optional item move operations. Set folder_id to null to move an item back to root.",
|
|
388
|
+
items: {
|
|
389
|
+
type: "object",
|
|
390
|
+
properties: {
|
|
391
|
+
item_id: { type: "string", description: "Item ID to move." },
|
|
392
|
+
folder_id: {
|
|
393
|
+
type: ["string", "null"],
|
|
394
|
+
description: "Target folder ID, or null to remove from folder."
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
required: ["item_id", "folder_id"]
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
delete: {
|
|
401
|
+
type: "array",
|
|
402
|
+
description: "Optional delete operations (folder IDs).",
|
|
403
|
+
items: { type: "string" }
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
required: ["space_id"],
|
|
407
|
+
anyOf: [
|
|
408
|
+
{ required: ["list"] },
|
|
409
|
+
{ required: ["create"] },
|
|
410
|
+
{ required: ["rename"] },
|
|
411
|
+
{ required: ["move_items"] },
|
|
412
|
+
{ required: ["delete"] }
|
|
413
|
+
]
|
|
310
414
|
}
|
|
311
415
|
},
|
|
312
416
|
{
|
|
313
417
|
name: "message",
|
|
314
418
|
title: "Message Assistant",
|
|
315
|
-
description: "Send a
|
|
419
|
+
description: "Primary orchestration tool. Send a request to the Intangle assistant and wait for one assistant turn to complete. Returns assistant text plus continuity IDs (`session_id`, `conversation_id`) so callers can continue the same thread reliably across turns.",
|
|
316
420
|
inputSchema: {
|
|
317
421
|
type: "object",
|
|
318
422
|
properties: {
|
|
@@ -330,11 +434,11 @@ export const TOOLS = [
|
|
|
330
434
|
},
|
|
331
435
|
conversation_id: {
|
|
332
436
|
type: "string",
|
|
333
|
-
description: "Optional existing conversation/chat summary ID to resume
|
|
437
|
+
description: "Optional existing conversation/chat summary ID to resume a specific conversation. If omitted, Intangle uses/creates the active conversation for this space."
|
|
334
438
|
},
|
|
335
439
|
session_id: {
|
|
336
440
|
type: "string",
|
|
337
|
-
description: "Optional runtime session ID to reuse across
|
|
441
|
+
description: "Optional runtime session ID to reuse across calls. Reuse the returned session_id for best continuity and lower warm-up overhead."
|
|
338
442
|
},
|
|
339
443
|
project_id: {
|
|
340
444
|
type: "string",
|
|
@@ -355,9 +459,9 @@ export const TOOLS = [
|
|
|
355
459
|
},
|
|
356
460
|
required: ["space_id", "content"]
|
|
357
461
|
}
|
|
358
|
-
}
|
|
462
|
+
}
|
|
359
463
|
// DISABLED: memory_action tool is broken and causing errors.
|
|
360
|
-
// Pending OHM protocol fix. Use
|
|
464
|
+
// Pending OHM protocol fix. Use update_memory, search, or fetch_items instead.
|
|
361
465
|
// {
|
|
362
466
|
// name: "memory_action",
|
|
363
467
|
// title: "Memory Action (OHM Protocol)",
|
|
@@ -380,3 +484,31 @@ export const TOOLS = [
|
|
|
380
484
|
// }
|
|
381
485
|
// }
|
|
382
486
|
];
|
|
487
|
+
const READ_ONLY_TOOLS = new Set([
|
|
488
|
+
"search",
|
|
489
|
+
"fetch_items",
|
|
490
|
+
"start",
|
|
491
|
+
"view_spaces",
|
|
492
|
+
"view_space"
|
|
493
|
+
]);
|
|
494
|
+
const DESTRUCTIVE_TOOLS = new Set([
|
|
495
|
+
"create_space",
|
|
496
|
+
"update_memory",
|
|
497
|
+
"update_folders"
|
|
498
|
+
]);
|
|
499
|
+
const IDEMPOTENT_TOOLS = new Set([
|
|
500
|
+
"search",
|
|
501
|
+
"fetch_items",
|
|
502
|
+
"start",
|
|
503
|
+
"view_spaces",
|
|
504
|
+
"view_space"
|
|
505
|
+
]);
|
|
506
|
+
export const TOOLS = TOOL_DEFINITIONS.map((tool) => ({
|
|
507
|
+
...tool,
|
|
508
|
+
annotations: {
|
|
509
|
+
...(READ_ONLY_TOOLS.has(tool.name) ? { readOnlyHint: true } : {}),
|
|
510
|
+
...(DESTRUCTIVE_TOOLS.has(tool.name) ? { destructiveHint: true } : {}),
|
|
511
|
+
...(IDEMPOTENT_TOOLS.has(tool.name) ? { idempotentHint: true } : {}),
|
|
512
|
+
openWorldHint: true
|
|
513
|
+
}
|
|
514
|
+
}));
|
package/index.ts
CHANGED
|
@@ -12,8 +12,11 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
12
12
|
import {
|
|
13
13
|
CallToolRequestSchema,
|
|
14
14
|
ErrorCode,
|
|
15
|
+
ListResourceTemplatesRequestSchema,
|
|
16
|
+
ListResourcesRequestSchema,
|
|
15
17
|
ListToolsRequestSchema,
|
|
16
18
|
McpError,
|
|
19
|
+
ReadResourceRequestSchema,
|
|
17
20
|
} from "@modelcontextprotocol/sdk/types.js"
|
|
18
21
|
import { config } from "dotenv"
|
|
19
22
|
import fetch from "node-fetch"
|
|
@@ -134,6 +137,16 @@ try {
|
|
|
134
137
|
return url.toString()
|
|
135
138
|
}
|
|
136
139
|
|
|
140
|
+
function buildRemoteApiUrl() {
|
|
141
|
+
const url = new URL("/api/mcp-remote", API_BASE_URL)
|
|
142
|
+
|
|
143
|
+
if (VERCEL_BYPASS_TOKEN) {
|
|
144
|
+
url.searchParams.set("x-vercel-protection-bypass", VERCEL_BYPASS_TOKEN)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return url.toString()
|
|
148
|
+
}
|
|
149
|
+
|
|
137
150
|
async function makeApiCall(endpoint: string, data: any, timeoutMs?: number) {
|
|
138
151
|
// Ensure we have client info before making requests
|
|
139
152
|
ensureClientInfo()
|
|
@@ -187,7 +200,9 @@ try {
|
|
|
187
200
|
)
|
|
188
201
|
}
|
|
189
202
|
|
|
190
|
-
throw new Error(
|
|
203
|
+
throw new Error(
|
|
204
|
+
`API call failed: ${response.status} ${response.statusText}`
|
|
205
|
+
)
|
|
191
206
|
}
|
|
192
207
|
|
|
193
208
|
if (responseBody.trim() === "") {
|
|
@@ -207,6 +222,70 @@ try {
|
|
|
207
222
|
}
|
|
208
223
|
}
|
|
209
224
|
|
|
225
|
+
async function makeRemoteRpcCall(
|
|
226
|
+
method: string,
|
|
227
|
+
params: Record<string, unknown>
|
|
228
|
+
) {
|
|
229
|
+
ensureClientInfo()
|
|
230
|
+
|
|
231
|
+
const headers: Record<string, string> = {
|
|
232
|
+
"Content-Type": "application/json",
|
|
233
|
+
Authorization: `Bearer ${MCP_API_KEY}`,
|
|
234
|
+
"User-Agent": mcpClientName
|
|
235
|
+
? `${mcpClientName}/${mcpClientVersion || "unknown"} (mcp-stdio)`
|
|
236
|
+
: "MCP-Client-Stdio/1.1.2 (mcp)",
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (mcpClientName) {
|
|
240
|
+
headers["X-MCP-Client-Name"] = mcpClientName
|
|
241
|
+
}
|
|
242
|
+
if (mcpClientVersion) {
|
|
243
|
+
headers["X-MCP-Client-Version"] = mcpClientVersion
|
|
244
|
+
}
|
|
245
|
+
if (VERCEL_BYPASS_TOKEN) {
|
|
246
|
+
headers["x-vercel-protection-bypass"] = VERCEL_BYPASS_TOKEN
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const requestId = Date.now()
|
|
250
|
+
const rpcBody = {
|
|
251
|
+
jsonrpc: "2.0",
|
|
252
|
+
id: requestId,
|
|
253
|
+
method,
|
|
254
|
+
params,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const response = await fetch(buildRemoteApiUrl(), {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers,
|
|
260
|
+
body: JSON.stringify(rpcBody),
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const responseText = await response.text()
|
|
264
|
+
let responseJson: any = {}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
responseJson = responseText ? JSON.parse(responseText) : {}
|
|
268
|
+
} catch {
|
|
269
|
+
throw new Error(`Failed to parse MCP remote ${method} response`)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
const message =
|
|
274
|
+
responseJson?.error?.message ||
|
|
275
|
+
responseJson?.message ||
|
|
276
|
+
`${response.status} ${response.statusText}`
|
|
277
|
+
throw new Error(`MCP remote ${method} failed: ${message}`)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (responseJson?.error) {
|
|
281
|
+
const code = responseJson.error.code
|
|
282
|
+
const message = responseJson.error.message || "Unknown MCP remote error"
|
|
283
|
+
throw new Error(`MCP remote ${method} error (${code}): ${message}`)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return responseJson?.result ?? {}
|
|
287
|
+
}
|
|
288
|
+
|
|
210
289
|
const server = new Server(
|
|
211
290
|
{
|
|
212
291
|
name: "intangle-context",
|
|
@@ -220,6 +299,7 @@ try {
|
|
|
220
299
|
},
|
|
221
300
|
{
|
|
222
301
|
capabilities: {
|
|
302
|
+
resources: {},
|
|
223
303
|
tools: {},
|
|
224
304
|
},
|
|
225
305
|
}
|
|
@@ -229,6 +309,34 @@ try {
|
|
|
229
309
|
tools: TOOLS,
|
|
230
310
|
}))
|
|
231
311
|
|
|
312
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
313
|
+
const result = await makeRemoteRpcCall("resources/list", {})
|
|
314
|
+
return {
|
|
315
|
+
resources: Array.isArray(result?.resources) ? result.resources : [],
|
|
316
|
+
nextCursor:
|
|
317
|
+
typeof result?.nextCursor === "string" ? result.nextCursor : undefined,
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request: any) => {
|
|
322
|
+
const uri = request?.params?.uri
|
|
323
|
+
if (!uri || typeof uri !== "string") {
|
|
324
|
+
throw new McpError(
|
|
325
|
+
ErrorCode.InvalidParams,
|
|
326
|
+
"uri is required for resources/read"
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const result = await makeRemoteRpcCall("resources/read", { uri })
|
|
331
|
+
return {
|
|
332
|
+
contents: Array.isArray(result?.contents) ? result.contents : [],
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
337
|
+
resourceTemplates: [],
|
|
338
|
+
}))
|
|
339
|
+
|
|
232
340
|
async function handleSearchContext(args: any) {
|
|
233
341
|
const { space_id, query, topics } = args as {
|
|
234
342
|
space_id: string
|
|
@@ -278,11 +386,15 @@ try {
|
|
|
278
386
|
}
|
|
279
387
|
|
|
280
388
|
if (!project_id && !slug) {
|
|
281
|
-
throw new Error(
|
|
389
|
+
throw new Error(
|
|
390
|
+
"Either project_id or slug (with space_id) is required. Use view_projects to get valid IDs."
|
|
391
|
+
)
|
|
282
392
|
}
|
|
283
393
|
|
|
284
394
|
if (slug && !space_id) {
|
|
285
|
-
throw new Error(
|
|
395
|
+
throw new Error(
|
|
396
|
+
"space_id is required when using slug. Use view_spaces to see available options."
|
|
397
|
+
)
|
|
286
398
|
}
|
|
287
399
|
|
|
288
400
|
return makeApiCall("view-project", { project_id, space_id, slug })
|
|
@@ -310,9 +422,7 @@ try {
|
|
|
310
422
|
}
|
|
311
423
|
|
|
312
424
|
if (!args.add && !args.update && !args.delete) {
|
|
313
|
-
throw new Error(
|
|
314
|
-
"At least one operation must be provided"
|
|
315
|
-
)
|
|
425
|
+
throw new Error("At least one operation must be provided")
|
|
316
426
|
}
|
|
317
427
|
|
|
318
428
|
// Ensure all add items have a type field to prevent classification timeout
|
|
@@ -320,7 +430,9 @@ try {
|
|
|
320
430
|
if (add?.items && Array.isArray(add.items)) {
|
|
321
431
|
for (const item of add.items) {
|
|
322
432
|
if (!item.type) {
|
|
323
|
-
log(
|
|
433
|
+
log(
|
|
434
|
+
`WARNING: Item "${item.title}" missing type field, defaulting to "context" to prevent classification timeout`
|
|
435
|
+
)
|
|
324
436
|
item.type = "context"
|
|
325
437
|
}
|
|
326
438
|
}
|
|
@@ -336,20 +448,56 @@ try {
|
|
|
336
448
|
})
|
|
337
449
|
}
|
|
338
450
|
|
|
451
|
+
async function handleUpdateFolders(args: any) {
|
|
452
|
+
if (!args.space_id) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
"space_id is required. Use view_spaces to see available options."
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const hasOperation =
|
|
459
|
+
!!args.list ||
|
|
460
|
+
(Array.isArray(args.create) && args.create.length > 0) ||
|
|
461
|
+
(Array.isArray(args.rename) && args.rename.length > 0) ||
|
|
462
|
+
(Array.isArray(args.move_items) && args.move_items.length > 0) ||
|
|
463
|
+
(Array.isArray(args.delete) && args.delete.length > 0)
|
|
464
|
+
|
|
465
|
+
if (!hasOperation) {
|
|
466
|
+
throw new Error(
|
|
467
|
+
"At least one operation is required: list, create, rename, move_items, or delete."
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return makeApiCall("update-folders", {
|
|
472
|
+
space_id: args.space_id,
|
|
473
|
+
list: args.list,
|
|
474
|
+
create: args.create,
|
|
475
|
+
rename: args.rename,
|
|
476
|
+
move_items: args.move_items,
|
|
477
|
+
delete: args.delete,
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
339
481
|
async function handleMessage(args: any) {
|
|
340
482
|
if (!args.space_id || !args.content) {
|
|
341
483
|
throw new Error("space_id and content are required")
|
|
342
484
|
}
|
|
343
485
|
|
|
486
|
+
// Keep one-turn MCP calls safely under common external tool-call deadlines
|
|
487
|
+
// while still allowing caller override for longer jobs.
|
|
344
488
|
const timeoutMs =
|
|
345
489
|
typeof args.timeout_ms === "number" && Number.isFinite(args.timeout_ms)
|
|
346
490
|
? Math.max(10_000, Math.min(300_000, Math.trunc(args.timeout_ms)))
|
|
347
|
-
:
|
|
491
|
+
: 100_000
|
|
348
492
|
|
|
349
493
|
// Give backend a little extra headroom beyond requested assistant timeout.
|
|
350
|
-
const requestTimeout = timeoutMs
|
|
494
|
+
const requestTimeout = timeoutMs + 10_000
|
|
351
495
|
|
|
352
|
-
return makeApiCall(
|
|
496
|
+
return makeApiCall(
|
|
497
|
+
"message",
|
|
498
|
+
{ ...args, timeout_ms: timeoutMs },
|
|
499
|
+
requestTimeout
|
|
500
|
+
)
|
|
353
501
|
}
|
|
354
502
|
|
|
355
503
|
server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
|
|
@@ -388,6 +536,9 @@ try {
|
|
|
388
536
|
case "update_space":
|
|
389
537
|
result = await handleUpdateSpace(args)
|
|
390
538
|
break
|
|
539
|
+
case "update_folders":
|
|
540
|
+
result = await handleUpdateFolders(args)
|
|
541
|
+
break
|
|
391
542
|
case "message":
|
|
392
543
|
result = await handleMessage(args)
|
|
393
544
|
break
|
package/package.json
CHANGED
package/tool-definitions.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Tools definition - matches the stdio server
|
|
2
|
-
|
|
2
|
+
const TOOL_DEFINITIONS = [
|
|
3
3
|
{
|
|
4
4
|
name: "search",
|
|
5
5
|
title: "Search Space",
|
|
6
6
|
description:
|
|
7
|
-
"Search for context, tasks, skills, and projects within a space. System automatically extracts quantity from natural language ('show 3 tasks' → 3 results, 'the last one' → 1 result) and intelligently formats results (1-3 items → summaries, 4+ items → IDs only).
|
|
7
|
+
"Search for context, tasks, skills, and projects within a space. System automatically extracts quantity from natural language ('show 3 tasks' → 3 results, 'the last one' → 1 result) and intelligently formats results (1-3 items → summaries, 4+ items → IDs only). Default to one search per question, then use fetch_items for full details instead of repeating paraphrased searches.",
|
|
8
8
|
inputSchema: {
|
|
9
9
|
type: "object",
|
|
10
10
|
properties: {
|
|
@@ -97,7 +97,7 @@ export const TOOLS = [
|
|
|
97
97
|
name: "start",
|
|
98
98
|
title: "Start Space Session",
|
|
99
99
|
description:
|
|
100
|
-
"
|
|
100
|
+
"Load a space fast for a new external caller. Returns assistant-curated current items, useful insights, skills, recent conversations, and assistant preferences so the caller can get oriented quickly and fetch deeper details only when needed.",
|
|
101
101
|
inputSchema: {
|
|
102
102
|
type: "object",
|
|
103
103
|
properties: {
|
|
@@ -171,7 +171,7 @@ export const TOOLS = [
|
|
|
171
171
|
name: "update_memory",
|
|
172
172
|
title: "Update Memory",
|
|
173
173
|
description:
|
|
174
|
-
"Add, update, or delete items in a space. Supports any combination of operations in a single call.\n\nTIPS FOR RICH MEMORY: The more context you provide, the smarter the system becomes:\n- Include WHY something matters, not just WHAT it is\n- Note user preferences, patterns, and approaches you observe\n- Describe repeatable workflows as skills (step-by-step procedures)\n- Link related items using parent_id or linkedItemIds\n- Use descriptive titles that capture the essence\n- Add context about decisions, reasoning, and outcomes\n\nThe system learns from patterns - sharing how the user works helps it anticipate their needs.",
|
|
174
|
+
"Add, update, or delete items in a space. Supports any combination of operations in a single call.\n\nIMPORTANT: Include at least one operation: add, update, or delete.\n\nTIPS FOR RICH MEMORY: The more context you provide, the smarter the system becomes:\n- Include WHY something matters, not just WHAT it is\n- Note user preferences, patterns, and approaches you observe\n- Describe repeatable workflows as skills (step-by-step procedures)\n- Link related items using parent_id or linkedItemIds\n- Use descriptive titles that capture the essence\n- Add context about decisions, reasoning, and outcomes\n\nThe system learns from patterns - sharing how the user works helps it anticipate their needs.",
|
|
175
175
|
inputSchema: {
|
|
176
176
|
type: "object",
|
|
177
177
|
properties: {
|
|
@@ -203,9 +203,9 @@ export const TOOLS = [
|
|
|
203
203
|
},
|
|
204
204
|
type: {
|
|
205
205
|
type: "string",
|
|
206
|
-
enum: ["task", "context", "skill", "document"],
|
|
206
|
+
enum: ["task", "context", "skill", "document", "insight"],
|
|
207
207
|
description:
|
|
208
|
-
"REQUIRED: Item type. 'task' for actionable items, 'context' for knowledge/facts, 'skill' for workflows, 'document' for long-form reference material. Omitting this triggers expensive auto-classification."
|
|
208
|
+
"REQUIRED: Item type. 'task' for actionable items, 'context' for knowledge/facts, 'skill' for workflows, 'document' for long-form reference material, 'insight' for durable patterns/principles. Omitting this triggers expensive auto-classification."
|
|
209
209
|
},
|
|
210
210
|
subtasks: {
|
|
211
211
|
type: "array",
|
|
@@ -339,14 +339,122 @@ export const TOOLS = [
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
},
|
|
342
|
-
required: ["space_id"]
|
|
342
|
+
required: ["space_id"],
|
|
343
|
+
anyOf: [{ required: ["add"] }, { required: ["update"] }, { required: ["delete"] }]
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "update_folders",
|
|
348
|
+
title: "Update Folders",
|
|
349
|
+
description:
|
|
350
|
+
"Unified folder management. Supports list, create, rename, move_items, and delete operations in one call so external MCP callers can organize memory the same way as the in-app assistant.",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
type: "object",
|
|
353
|
+
properties: {
|
|
354
|
+
space_id: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description:
|
|
357
|
+
"REQUIRED: Space to operate in (use view_spaces to see available options)."
|
|
358
|
+
},
|
|
359
|
+
list: {
|
|
360
|
+
type: "object",
|
|
361
|
+
description:
|
|
362
|
+
"Optional list operation. Use this first to discover existing folders and IDs.",
|
|
363
|
+
properties: {
|
|
364
|
+
section_type: {
|
|
365
|
+
type: "string",
|
|
366
|
+
enum: [
|
|
367
|
+
"projects",
|
|
368
|
+
"insights",
|
|
369
|
+
"insights_space",
|
|
370
|
+
"insights_user",
|
|
371
|
+
"agents",
|
|
372
|
+
"skills",
|
|
373
|
+
"conversations",
|
|
374
|
+
"dashboard"
|
|
375
|
+
],
|
|
376
|
+
description: "Optional: filter folders by sidebar section."
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
create: {
|
|
381
|
+
type: "array",
|
|
382
|
+
description: "Optional create operations.",
|
|
383
|
+
items: {
|
|
384
|
+
type: "object",
|
|
385
|
+
properties: {
|
|
386
|
+
name: { type: "string", description: "Folder name." },
|
|
387
|
+
section_type: {
|
|
388
|
+
type: "string",
|
|
389
|
+
enum: [
|
|
390
|
+
"projects",
|
|
391
|
+
"insights",
|
|
392
|
+
"insights_space",
|
|
393
|
+
"insights_user",
|
|
394
|
+
"agents",
|
|
395
|
+
"skills",
|
|
396
|
+
"conversations",
|
|
397
|
+
"dashboard"
|
|
398
|
+
],
|
|
399
|
+
description: "Sidebar section this folder belongs to."
|
|
400
|
+
},
|
|
401
|
+
parent_id: {
|
|
402
|
+
type: "string",
|
|
403
|
+
description: "Optional parent folder ID for nesting."
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
required: ["name", "section_type"]
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
rename: {
|
|
410
|
+
type: "array",
|
|
411
|
+
description: "Optional rename operations.",
|
|
412
|
+
items: {
|
|
413
|
+
type: "object",
|
|
414
|
+
properties: {
|
|
415
|
+
folder_id: { type: "string", description: "Folder ID to rename." },
|
|
416
|
+
new_name: { type: "string", description: "New folder name." }
|
|
417
|
+
},
|
|
418
|
+
required: ["folder_id", "new_name"]
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
move_items: {
|
|
422
|
+
type: "array",
|
|
423
|
+
description:
|
|
424
|
+
"Optional item move operations. Set folder_id to null to move an item back to root.",
|
|
425
|
+
items: {
|
|
426
|
+
type: "object",
|
|
427
|
+
properties: {
|
|
428
|
+
item_id: { type: "string", description: "Item ID to move." },
|
|
429
|
+
folder_id: {
|
|
430
|
+
type: ["string", "null"],
|
|
431
|
+
description: "Target folder ID, or null to remove from folder."
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
required: ["item_id", "folder_id"]
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
delete: {
|
|
438
|
+
type: "array",
|
|
439
|
+
description: "Optional delete operations (folder IDs).",
|
|
440
|
+
items: { type: "string" }
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
required: ["space_id"],
|
|
444
|
+
anyOf: [
|
|
445
|
+
{ required: ["list"] },
|
|
446
|
+
{ required: ["create"] },
|
|
447
|
+
{ required: ["rename"] },
|
|
448
|
+
{ required: ["move_items"] },
|
|
449
|
+
{ required: ["delete"] }
|
|
450
|
+
]
|
|
343
451
|
}
|
|
344
452
|
},
|
|
345
453
|
{
|
|
346
454
|
name: "message",
|
|
347
455
|
title: "Message Assistant",
|
|
348
456
|
description:
|
|
349
|
-
"Send a
|
|
457
|
+
"Primary orchestration tool. Send a request to the Intangle assistant and wait for one assistant turn to complete. Returns assistant text plus continuity IDs (`session_id`, `conversation_id`) so callers can continue the same thread reliably across turns.",
|
|
350
458
|
inputSchema: {
|
|
351
459
|
type: "object",
|
|
352
460
|
properties: {
|
|
@@ -366,12 +474,12 @@ export const TOOLS = [
|
|
|
366
474
|
conversation_id: {
|
|
367
475
|
type: "string",
|
|
368
476
|
description:
|
|
369
|
-
"Optional existing conversation/chat summary ID to resume
|
|
477
|
+
"Optional existing conversation/chat summary ID to resume a specific conversation. If omitted, Intangle uses/creates the active conversation for this space."
|
|
370
478
|
},
|
|
371
479
|
session_id: {
|
|
372
480
|
type: "string",
|
|
373
481
|
description:
|
|
374
|
-
"Optional runtime session ID to reuse across
|
|
482
|
+
"Optional runtime session ID to reuse across calls. Reuse the returned session_id for best continuity and lower warm-up overhead."
|
|
375
483
|
},
|
|
376
484
|
project_id: {
|
|
377
485
|
type: "string",
|
|
@@ -394,9 +502,9 @@ export const TOOLS = [
|
|
|
394
502
|
},
|
|
395
503
|
required: ["space_id", "content"]
|
|
396
504
|
}
|
|
397
|
-
}
|
|
505
|
+
}
|
|
398
506
|
// DISABLED: memory_action tool is broken and causing errors.
|
|
399
|
-
// Pending OHM protocol fix. Use
|
|
507
|
+
// Pending OHM protocol fix. Use update_memory, search, or fetch_items instead.
|
|
400
508
|
// {
|
|
401
509
|
// name: "memory_action",
|
|
402
510
|
// title: "Memory Action (OHM Protocol)",
|
|
@@ -419,3 +527,35 @@ export const TOOLS = [
|
|
|
419
527
|
// }
|
|
420
528
|
// }
|
|
421
529
|
]
|
|
530
|
+
|
|
531
|
+
const READ_ONLY_TOOLS = new Set([
|
|
532
|
+
"search",
|
|
533
|
+
"fetch_items",
|
|
534
|
+
"start",
|
|
535
|
+
"view_spaces",
|
|
536
|
+
"view_space"
|
|
537
|
+
])
|
|
538
|
+
|
|
539
|
+
const DESTRUCTIVE_TOOLS = new Set([
|
|
540
|
+
"create_space",
|
|
541
|
+
"update_memory",
|
|
542
|
+
"update_folders"
|
|
543
|
+
])
|
|
544
|
+
|
|
545
|
+
const IDEMPOTENT_TOOLS = new Set([
|
|
546
|
+
"search",
|
|
547
|
+
"fetch_items",
|
|
548
|
+
"start",
|
|
549
|
+
"view_spaces",
|
|
550
|
+
"view_space"
|
|
551
|
+
])
|
|
552
|
+
|
|
553
|
+
export const TOOLS = TOOL_DEFINITIONS.map((tool) => ({
|
|
554
|
+
...tool,
|
|
555
|
+
annotations: {
|
|
556
|
+
...(READ_ONLY_TOOLS.has(tool.name) ? { readOnlyHint: true } : {}),
|
|
557
|
+
...(DESTRUCTIVE_TOOLS.has(tool.name) ? { destructiveHint: true } : {}),
|
|
558
|
+
...(IDEMPOTENT_TOOLS.has(tool.name) ? { idempotentHint: true } : {}),
|
|
559
|
+
openWorldHint: true
|
|
560
|
+
}
|
|
561
|
+
}))
|