@intangle/mcp-server 2.6.7 → 2.6.9
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 +4 -4
- package/dist/index.js +4 -10
- package/dist/tool-definitions.js +16 -26
- package/package.json +9 -3
- package/.env.local +0 -4
- package/biome.json +0 -39
- package/index.ts +0 -813
- package/tool-definitions.ts +0 -990
- package/tsconfig.json +0 -18
package/index.ts
DELETED
|
@@ -1,813 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Intangle MCP Server - Exposes Intangle context functionality through MCP
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync } from "fs"
|
|
8
|
-
import { dirname, join } from "path"
|
|
9
|
-
import { fileURLToPath } from "url"
|
|
10
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
|
|
11
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
12
|
-
import {
|
|
13
|
-
CallToolRequestSchema,
|
|
14
|
-
ErrorCode,
|
|
15
|
-
ListResourceTemplatesRequestSchema,
|
|
16
|
-
ListResourcesRequestSchema,
|
|
17
|
-
ListToolsRequestSchema,
|
|
18
|
-
McpError,
|
|
19
|
-
ReadResourceRequestSchema,
|
|
20
|
-
} from "@modelcontextprotocol/sdk/types.js"
|
|
21
|
-
import { config } from "dotenv"
|
|
22
|
-
import fetch from "node-fetch"
|
|
23
|
-
import { TOOLS } from "./tool-definitions.js"
|
|
24
|
-
|
|
25
|
-
// ... imports ...
|
|
26
|
-
|
|
27
|
-
// Debug logging
|
|
28
|
-
import { appendFileSync } from "fs"
|
|
29
|
-
const DEBUG_LOG = "/tmp/intangle-mcp-debug.log"
|
|
30
|
-
function log(msg: string) {
|
|
31
|
-
try {
|
|
32
|
-
appendFileSync(DEBUG_LOG, `${new Date().toISOString()}: ${msg}\n`)
|
|
33
|
-
} catch (e) {}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
log("--- Server Starting ---")
|
|
37
|
-
|
|
38
|
-
// Client info captured from MCP initialize handshake
|
|
39
|
-
let mcpClientName: string | undefined
|
|
40
|
-
let mcpClientVersion: string | undefined
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
// Load environment variables from .env and .env.local
|
|
44
|
-
config({ quiet: true })
|
|
45
|
-
config({ path: ".env.local", quiet: true })
|
|
46
|
-
|
|
47
|
-
// Base URL for API calls to Next.js app
|
|
48
|
-
const API_BASE_URL = process.env.NEXT_APP_URL || "https://renvoi.co/intangle"
|
|
49
|
-
const MCP_API_KEY = process.env.MCP_API_KEY
|
|
50
|
-
const VERCEL_BYPASS_TOKEN =
|
|
51
|
-
process.env.VERCEL_BYPASS_TOKEN || process.env.VERCEL_PROTECTION_BYPASS
|
|
52
|
-
|
|
53
|
-
log(`CWD: ${process.cwd()}`)
|
|
54
|
-
log(`Has API Key: ${!!MCP_API_KEY}`)
|
|
55
|
-
log(`Has Vercel Bypass Token: ${!!VERCEL_BYPASS_TOKEN}`)
|
|
56
|
-
log(`Node Version: ${process.version}`)
|
|
57
|
-
|
|
58
|
-
if (!MCP_API_KEY) {
|
|
59
|
-
log("Error: MCP_API_KEY environment variable is required")
|
|
60
|
-
console.error("Error: MCP_API_KEY environment variable is required")
|
|
61
|
-
console.error(
|
|
62
|
-
"Please set your Intangle API key in the Claude Desktop configuration"
|
|
63
|
-
)
|
|
64
|
-
process.exit(1)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
console.error("Intangle MCP Server starting - connecting to", API_BASE_URL)
|
|
68
|
-
|
|
69
|
-
// Version checking - automatically read from package.json
|
|
70
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
71
|
-
let packageJson
|
|
72
|
-
try {
|
|
73
|
-
packageJson = JSON.parse(
|
|
74
|
-
readFileSync(join(__dirname, "package.json"), "utf-8")
|
|
75
|
-
)
|
|
76
|
-
} catch {
|
|
77
|
-
packageJson = JSON.parse(
|
|
78
|
-
readFileSync(join(__dirname, "../package.json"), "utf-8")
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
const CURRENT_VERSION = packageJson.version
|
|
82
|
-
let latestVersion: string | null = null
|
|
83
|
-
let versionCheckDone = false
|
|
84
|
-
|
|
85
|
-
async function checkVersion() {
|
|
86
|
-
if (versionCheckDone) return
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const response = await fetch(
|
|
90
|
-
"https://registry.npmjs.org/@intangle/mcp-server/latest"
|
|
91
|
-
)
|
|
92
|
-
const data = (await response.json()) as { version: string }
|
|
93
|
-
latestVersion = data.version
|
|
94
|
-
versionCheckDone = true
|
|
95
|
-
|
|
96
|
-
if (latestVersion && latestVersion !== CURRENT_VERSION) {
|
|
97
|
-
console.error("\n UPDATE AVAILABLE ")
|
|
98
|
-
console.error(`Current version: ${CURRENT_VERSION}`)
|
|
99
|
-
console.error(`Latest version: ${latestVersion}`)
|
|
100
|
-
console.error("Update with: npx @intangle/mcp-server@latest\n")
|
|
101
|
-
} else {
|
|
102
|
-
console.error(`✓ Running latest version (${CURRENT_VERSION})`)
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
// Silently fail version check - don't block startup
|
|
106
|
-
versionCheckDone = true
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Check version on startup (non-blocking)
|
|
111
|
-
checkVersion()
|
|
112
|
-
|
|
113
|
-
// Helper to ensure client info is captured (called before API requests)
|
|
114
|
-
function ensureClientInfo() {
|
|
115
|
-
if (!mcpClientName) {
|
|
116
|
-
const clientInfo = server.getClientVersion()
|
|
117
|
-
if (clientInfo) {
|
|
118
|
-
mcpClientName = clientInfo.name
|
|
119
|
-
mcpClientVersion = clientInfo.version
|
|
120
|
-
log(
|
|
121
|
-
`Client identified (on-demand): ${mcpClientName} v${mcpClientVersion}`
|
|
122
|
-
)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Default timeout for API calls (90 seconds)
|
|
128
|
-
const API_TIMEOUT_MS = 90_000
|
|
129
|
-
|
|
130
|
-
function buildApiUrl(endpoint: string) {
|
|
131
|
-
const url = new URL(`/api/mcp/${endpoint}`, API_BASE_URL)
|
|
132
|
-
|
|
133
|
-
if (VERCEL_BYPASS_TOKEN) {
|
|
134
|
-
url.searchParams.set("x-vercel-protection-bypass", VERCEL_BYPASS_TOKEN)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return url.toString()
|
|
138
|
-
}
|
|
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
|
-
|
|
150
|
-
async function makeApiCall(endpoint: string, data: any, timeoutMs?: number) {
|
|
151
|
-
// Ensure we have client info before making requests
|
|
152
|
-
ensureClientInfo()
|
|
153
|
-
|
|
154
|
-
// Build headers with client info if available
|
|
155
|
-
const headers: Record<string, string> = {
|
|
156
|
-
"Content-Type": "application/json",
|
|
157
|
-
Authorization: `Bearer ${MCP_API_KEY}`,
|
|
158
|
-
"User-Agent": mcpClientName
|
|
159
|
-
? `${mcpClientName}/${mcpClientVersion || "unknown"} (mcp-stdio)`
|
|
160
|
-
: "MCP-Client-Stdio/1.1.2 (mcp)",
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Add explicit client info headers for backend processing
|
|
164
|
-
if (mcpClientName) {
|
|
165
|
-
headers["X-MCP-Client-Name"] = mcpClientName
|
|
166
|
-
}
|
|
167
|
-
if (mcpClientVersion) {
|
|
168
|
-
headers["X-MCP-Client-Version"] = mcpClientVersion
|
|
169
|
-
}
|
|
170
|
-
if (VERCEL_BYPASS_TOKEN) {
|
|
171
|
-
headers["x-vercel-protection-bypass"] = VERCEL_BYPASS_TOKEN
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Apply timeout to prevent hanging requests
|
|
175
|
-
const controller = new AbortController()
|
|
176
|
-
const effectiveTimeout = timeoutMs || API_TIMEOUT_MS
|
|
177
|
-
const timeoutId = setTimeout(() => controller.abort(), effectiveTimeout)
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
const response = await fetch(buildApiUrl(endpoint), {
|
|
181
|
-
method: "POST",
|
|
182
|
-
headers,
|
|
183
|
-
body: JSON.stringify(data),
|
|
184
|
-
signal: controller.signal as AbortSignal,
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
const contentType = response.headers.get("content-type") || ""
|
|
188
|
-
const responseBody = await response.text()
|
|
189
|
-
|
|
190
|
-
if (!response.ok) {
|
|
191
|
-
if (
|
|
192
|
-
response.status === 401 &&
|
|
193
|
-
contentType.includes("text/html") &&
|
|
194
|
-
responseBody.includes("Vercel Authentication")
|
|
195
|
-
) {
|
|
196
|
-
throw new Error(
|
|
197
|
-
VERCEL_BYPASS_TOKEN
|
|
198
|
-
? "API call blocked by Vercel deployment protection despite bypass token. Check token validity and deployment protection settings."
|
|
199
|
-
: "API call blocked by Vercel deployment protection. Set VERCEL_BYPASS_TOKEN in MCP server env."
|
|
200
|
-
)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
throw new Error(
|
|
204
|
-
`API call failed: ${response.status} ${response.statusText}`
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (responseBody.trim() === "") {
|
|
209
|
-
return {}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (
|
|
213
|
-
contentType.includes("text/html") ||
|
|
214
|
-
/^\s*<!DOCTYPE\s+html/i.test(responseBody) ||
|
|
215
|
-
/^\s*<html\b/i.test(responseBody)
|
|
216
|
-
) {
|
|
217
|
-
throw new Error(
|
|
218
|
-
`API call to ${buildApiUrl(endpoint)} returned HTML instead of JSON. Check NEXT_APP_URL and point it at the app host, e.g. https://renvoi.co/intangle.`
|
|
219
|
-
)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return JSON.parse(responseBody)
|
|
223
|
-
} catch (error: unknown) {
|
|
224
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
225
|
-
throw new Error(
|
|
226
|
-
`API call to ${endpoint} timed out after ${effectiveTimeout / 1000}s`
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
throw error
|
|
230
|
-
} finally {
|
|
231
|
-
clearTimeout(timeoutId)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
async function makeRemoteRpcCall(
|
|
236
|
-
method: string,
|
|
237
|
-
params: Record<string, unknown>
|
|
238
|
-
) {
|
|
239
|
-
ensureClientInfo()
|
|
240
|
-
|
|
241
|
-
const headers: Record<string, string> = {
|
|
242
|
-
"Content-Type": "application/json",
|
|
243
|
-
Authorization: `Bearer ${MCP_API_KEY}`,
|
|
244
|
-
"User-Agent": mcpClientName
|
|
245
|
-
? `${mcpClientName}/${mcpClientVersion || "unknown"} (mcp-stdio)`
|
|
246
|
-
: "MCP-Client-Stdio/1.1.2 (mcp)",
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (mcpClientName) {
|
|
250
|
-
headers["X-MCP-Client-Name"] = mcpClientName
|
|
251
|
-
}
|
|
252
|
-
if (mcpClientVersion) {
|
|
253
|
-
headers["X-MCP-Client-Version"] = mcpClientVersion
|
|
254
|
-
}
|
|
255
|
-
if (VERCEL_BYPASS_TOKEN) {
|
|
256
|
-
headers["x-vercel-protection-bypass"] = VERCEL_BYPASS_TOKEN
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const requestId = Date.now()
|
|
260
|
-
const rpcBody = {
|
|
261
|
-
jsonrpc: "2.0",
|
|
262
|
-
id: requestId,
|
|
263
|
-
method,
|
|
264
|
-
params,
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const response = await fetch(buildRemoteApiUrl(), {
|
|
268
|
-
method: "POST",
|
|
269
|
-
headers,
|
|
270
|
-
body: JSON.stringify(rpcBody),
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
const responseText = await response.text()
|
|
274
|
-
let responseJson: any = {}
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
responseJson = responseText ? JSON.parse(responseText) : {}
|
|
278
|
-
} catch {
|
|
279
|
-
throw new Error(`Failed to parse MCP remote ${method} response`)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (!response.ok) {
|
|
283
|
-
const message =
|
|
284
|
-
responseJson?.error?.message ||
|
|
285
|
-
responseJson?.message ||
|
|
286
|
-
`${response.status} ${response.statusText}`
|
|
287
|
-
throw new Error(`MCP remote ${method} failed: ${message}`)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (responseJson?.error) {
|
|
291
|
-
const code = responseJson.error.code
|
|
292
|
-
const message = responseJson.error.message || "Unknown MCP remote error"
|
|
293
|
-
throw new Error(`MCP remote ${method} error (${code}): ${message}`)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return responseJson?.result ?? {}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const server = new Server(
|
|
300
|
-
{
|
|
301
|
-
name: "intangle-context",
|
|
302
|
-
version: "1.0.0",
|
|
303
|
-
icons: [
|
|
304
|
-
{
|
|
305
|
-
src: "https://intangle.tools/icon.png",
|
|
306
|
-
mimeType: "image/png",
|
|
307
|
-
},
|
|
308
|
-
],
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
capabilities: {
|
|
312
|
-
resources: {},
|
|
313
|
-
tools: {},
|
|
314
|
-
},
|
|
315
|
-
}
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
319
|
-
tools: TOOLS,
|
|
320
|
-
}))
|
|
321
|
-
|
|
322
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
323
|
-
const result = await makeRemoteRpcCall("resources/list", {})
|
|
324
|
-
return {
|
|
325
|
-
resources: Array.isArray(result?.resources) ? result.resources : [],
|
|
326
|
-
nextCursor:
|
|
327
|
-
typeof result?.nextCursor === "string" ? result.nextCursor : undefined,
|
|
328
|
-
}
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request: any) => {
|
|
332
|
-
const uri = request?.params?.uri
|
|
333
|
-
if (!uri || typeof uri !== "string") {
|
|
334
|
-
throw new McpError(
|
|
335
|
-
ErrorCode.InvalidParams,
|
|
336
|
-
"uri is required for resources/read"
|
|
337
|
-
)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const result = await makeRemoteRpcCall("resources/read", { uri })
|
|
341
|
-
return {
|
|
342
|
-
contents: Array.isArray(result?.contents) ? result.contents : [],
|
|
343
|
-
}
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
347
|
-
const result = await makeRemoteRpcCall("resources/templates/list", {})
|
|
348
|
-
return {
|
|
349
|
-
resourceTemplates: Array.isArray(result?.resourceTemplates)
|
|
350
|
-
? result.resourceTemplates
|
|
351
|
-
: [],
|
|
352
|
-
}
|
|
353
|
-
})
|
|
354
|
-
|
|
355
|
-
async function handleSearchContext(args: any) {
|
|
356
|
-
const { space_id, query, topics, group_id, project_id, folder_id } = args as {
|
|
357
|
-
space_id: string
|
|
358
|
-
query: string
|
|
359
|
-
topics?: string[]
|
|
360
|
-
group_id?: string
|
|
361
|
-
project_id?: string
|
|
362
|
-
folder_id?: string
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Require space_id
|
|
366
|
-
if (!space_id) {
|
|
367
|
-
throw new Error(
|
|
368
|
-
"space_id is required. Use list_spaces to see available options."
|
|
369
|
-
)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return makeApiCall("search", {
|
|
373
|
-
space_id,
|
|
374
|
-
query,
|
|
375
|
-
group_id,
|
|
376
|
-
project_id,
|
|
377
|
-
folder_id,
|
|
378
|
-
topics,
|
|
379
|
-
})
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function handleFetch(args: any) {
|
|
383
|
-
const { id, ids, space_id } = args as {
|
|
384
|
-
id?: string
|
|
385
|
-
ids?: string[]
|
|
386
|
-
space_id?: string
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return makeApiCall("fetch", { id, ids, space_id })
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async function handleUsageStatus() {
|
|
393
|
-
return makeApiCall("usage-status", {})
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async function handleFetchConversationMessages(args: any) {
|
|
397
|
-
const { space_id, conversation_id } = args as {
|
|
398
|
-
space_id?: string
|
|
399
|
-
conversation_id?: string
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (!space_id || !conversation_id) {
|
|
403
|
-
throw new Error("space_id and conversation_id are required")
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return makeApiCall("conversation-messages", args)
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
async function handleSubscribeConversation(args: any) {
|
|
410
|
-
const { space_id, conversation_id } = args as {
|
|
411
|
-
space_id?: string
|
|
412
|
-
conversation_id?: string
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (!space_id || !conversation_id) {
|
|
416
|
-
throw new Error("space_id and conversation_id are required")
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return makeApiCall("conversation-subscribe", args)
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async function handleConversationInbox(args: any) {
|
|
423
|
-
const { space_id } = args as { space_id?: string }
|
|
424
|
-
|
|
425
|
-
if (!space_id) {
|
|
426
|
-
throw new Error("space_id is required")
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return makeApiCall("conversation-inbox", args)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async function handleMarkConversationRead(args: any) {
|
|
433
|
-
const { space_id, conversation_id } = args as {
|
|
434
|
-
space_id?: string
|
|
435
|
-
conversation_id?: string
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (!space_id || !conversation_id) {
|
|
439
|
-
throw new Error("space_id and conversation_id are required")
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return makeApiCall("conversation-read", args)
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async function handleViewSpaces() {
|
|
446
|
-
return makeApiCall("list-spaces", {})
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
async function handleViewGroups(args: any) {
|
|
450
|
-
const { space_id } = args as { space_id?: string }
|
|
451
|
-
if (!space_id) {
|
|
452
|
-
throw new Error(
|
|
453
|
-
"space_id is required. Use view_spaces to see available options."
|
|
454
|
-
)
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return makeApiCall("view-groups", args)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
async function handleViewProjects(args: any) {
|
|
461
|
-
const { space_id } = args as { space_id: string }
|
|
462
|
-
if (!space_id) {
|
|
463
|
-
throw new Error(
|
|
464
|
-
"space_id is required. Use view_spaces to see available options."
|
|
465
|
-
)
|
|
466
|
-
}
|
|
467
|
-
return makeApiCall("view-projects", args)
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
async function handleViewProject(args: any) {
|
|
471
|
-
const { project_id, space_id, slug } = args as {
|
|
472
|
-
project_id?: string
|
|
473
|
-
space_id?: string
|
|
474
|
-
slug?: string
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (!project_id && !slug) {
|
|
478
|
-
throw new Error(
|
|
479
|
-
"Either project_id or slug (with space_id) is required. Use view_projects to get valid IDs."
|
|
480
|
-
)
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (!space_id) {
|
|
484
|
-
throw new Error(
|
|
485
|
-
"space_id is required. Use view_spaces to see available options."
|
|
486
|
-
)
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return makeApiCall("view-project", args)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
async function handleCreateGroup(args: any) {
|
|
493
|
-
const { space_id, name } = args as { space_id?: string; name?: string }
|
|
494
|
-
if (!space_id || !name) {
|
|
495
|
-
throw new Error("space_id and name are required")
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
return makeApiCall("create-group", args)
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
async function handleRenameGroup(args: any) {
|
|
502
|
-
const { space_id, group_id, name } = args as {
|
|
503
|
-
space_id?: string
|
|
504
|
-
group_id?: string
|
|
505
|
-
name?: string
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (!space_id || !group_id || !name) {
|
|
509
|
-
throw new Error("space_id, group_id, and name are required")
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
return makeApiCall("rename-group", args)
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async function handleDeleteGroup(args: any) {
|
|
516
|
-
const { space_id, group_id } = args as {
|
|
517
|
-
space_id?: string
|
|
518
|
-
group_id?: string
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
if (!space_id || !group_id) {
|
|
522
|
-
throw new Error("space_id and group_id are required")
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return makeApiCall("delete-group", args)
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
async function handleCreateProject(args: any) {
|
|
529
|
-
const { space_id, name, group_id } = args as {
|
|
530
|
-
space_id?: string
|
|
531
|
-
name?: string
|
|
532
|
-
group_id?: string
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (!space_id || !name) {
|
|
536
|
-
throw new Error("space_id and name are required")
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return makeApiCall("create-project", args)
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
async function handleRenameProject(args: any) {
|
|
543
|
-
const { space_id, project_id, name } = args as {
|
|
544
|
-
space_id?: string
|
|
545
|
-
project_id?: string
|
|
546
|
-
name?: string
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
if (!space_id || !project_id || !name) {
|
|
550
|
-
throw new Error("space_id, project_id, and name are required")
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
return makeApiCall("rename-project", args)
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
async function handleDeleteProject(args: any) {
|
|
557
|
-
const { space_id, project_id } = args as {
|
|
558
|
-
space_id?: string
|
|
559
|
-
project_id?: string
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (!space_id || !project_id) {
|
|
563
|
-
throw new Error("space_id and project_id are required")
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
return makeApiCall("delete-project", args)
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
async function handleAssignProjectToGroup(args: any) {
|
|
570
|
-
const { space_id, project_id, group_id } = args as {
|
|
571
|
-
space_id?: string
|
|
572
|
-
project_id?: string
|
|
573
|
-
group_id?: string | null
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (!space_id || !project_id) {
|
|
577
|
-
throw new Error("space_id and project_id are required")
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
return makeApiCall("assign-project-to-group", args)
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
async function handleCreateSpace(args: any) {
|
|
584
|
-
return makeApiCall("create-space", args)
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
async function handleViewSpace(args: any) {
|
|
588
|
-
const { space_id } = args as { space_id: string }
|
|
589
|
-
return makeApiCall("view-space", args)
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
async function handleStart(args: any) {
|
|
593
|
-
const { space_id } = args as { space_id: string }
|
|
594
|
-
return makeApiCall("start", args)
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
async function handleUpdateSpace(args: any) {
|
|
598
|
-
if (!args.space_id) {
|
|
599
|
-
throw new Error(
|
|
600
|
-
"space_id is required. Use view_spaces to see available options."
|
|
601
|
-
)
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
if (!args.add && !args.update && !args.delete) {
|
|
605
|
-
throw new Error("At least one operation must be provided")
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// Ensure all add items have a type field to prevent classification timeout
|
|
609
|
-
const add = args.add
|
|
610
|
-
if (add?.items && Array.isArray(add.items)) {
|
|
611
|
-
for (const item of add.items) {
|
|
612
|
-
if (!item.type) {
|
|
613
|
-
log(
|
|
614
|
-
`WARNING: Item "${item.title}" missing type field, defaulting to "context" to prevent classification timeout`
|
|
615
|
-
)
|
|
616
|
-
item.type = "context"
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// Pass through to API with new structure
|
|
622
|
-
return makeApiCall("update-memory", {
|
|
623
|
-
space_id: args.space_id,
|
|
624
|
-
project_id: args.project_id,
|
|
625
|
-
add,
|
|
626
|
-
update: args.update,
|
|
627
|
-
delete: args.delete,
|
|
628
|
-
})
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
async function handleUpdateFolders(args: any) {
|
|
632
|
-
if (!args.space_id) {
|
|
633
|
-
throw new Error(
|
|
634
|
-
"space_id is required. Use view_spaces to see available options."
|
|
635
|
-
)
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const hasOperation =
|
|
639
|
-
!!args.list ||
|
|
640
|
-
(Array.isArray(args.create) && args.create.length > 0) ||
|
|
641
|
-
(Array.isArray(args.rename) && args.rename.length > 0) ||
|
|
642
|
-
(Array.isArray(args.move_items) && args.move_items.length > 0) ||
|
|
643
|
-
(Array.isArray(args.delete) && args.delete.length > 0)
|
|
644
|
-
|
|
645
|
-
if (!hasOperation) {
|
|
646
|
-
throw new Error(
|
|
647
|
-
"At least one operation is required: list, create, rename, move_items, or delete."
|
|
648
|
-
)
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
return makeApiCall("update-folders", {
|
|
652
|
-
space_id: args.space_id,
|
|
653
|
-
list: args.list,
|
|
654
|
-
create: args.create,
|
|
655
|
-
rename: args.rename,
|
|
656
|
-
move_items: args.move_items,
|
|
657
|
-
delete: args.delete,
|
|
658
|
-
})
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
async function handleMessage(args: any) {
|
|
662
|
-
if (!args.space_id || !args.content) {
|
|
663
|
-
throw new Error("space_id and content are required")
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Keep one-turn MCP calls safely under common external tool-call deadlines
|
|
667
|
-
// while still allowing caller override for longer jobs.
|
|
668
|
-
const timeoutMs =
|
|
669
|
-
typeof args.timeout_ms === "number" && Number.isFinite(args.timeout_ms)
|
|
670
|
-
? Math.max(10_000, Math.min(300_000, Math.trunc(args.timeout_ms)))
|
|
671
|
-
: 100_000
|
|
672
|
-
|
|
673
|
-
// Give backend a little extra headroom beyond requested assistant timeout.
|
|
674
|
-
const requestTimeout = timeoutMs + 10_000
|
|
675
|
-
|
|
676
|
-
return makeApiCall(
|
|
677
|
-
"message",
|
|
678
|
-
{ ...args, timeout_ms: timeoutMs },
|
|
679
|
-
requestTimeout
|
|
680
|
-
)
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
|
|
684
|
-
const { name, arguments: args } = request.params
|
|
685
|
-
|
|
686
|
-
try {
|
|
687
|
-
let result: any
|
|
688
|
-
|
|
689
|
-
switch (name) {
|
|
690
|
-
case "search":
|
|
691
|
-
result = await handleSearchContext(args)
|
|
692
|
-
break
|
|
693
|
-
case "fetch_items":
|
|
694
|
-
result = await handleFetch(args)
|
|
695
|
-
break
|
|
696
|
-
case "usage_status":
|
|
697
|
-
result = await handleUsageStatus()
|
|
698
|
-
break
|
|
699
|
-
case "fetch_conversation_messages":
|
|
700
|
-
result = await handleFetchConversationMessages(args)
|
|
701
|
-
break
|
|
702
|
-
case "subscribe_conversation":
|
|
703
|
-
result = await handleSubscribeConversation(args)
|
|
704
|
-
break
|
|
705
|
-
case "list_conversation_inbox":
|
|
706
|
-
result = await handleConversationInbox(args)
|
|
707
|
-
break
|
|
708
|
-
case "mark_conversation_read":
|
|
709
|
-
result = await handleMarkConversationRead(args)
|
|
710
|
-
break
|
|
711
|
-
case "view_spaces":
|
|
712
|
-
result = await handleViewSpaces()
|
|
713
|
-
break
|
|
714
|
-
case "view_groups":
|
|
715
|
-
result = await handleViewGroups(args)
|
|
716
|
-
break
|
|
717
|
-
case "create_group":
|
|
718
|
-
result = await handleCreateGroup(args)
|
|
719
|
-
break
|
|
720
|
-
case "rename_group":
|
|
721
|
-
result = await handleRenameGroup(args)
|
|
722
|
-
break
|
|
723
|
-
case "delete_group":
|
|
724
|
-
result = await handleDeleteGroup(args)
|
|
725
|
-
break
|
|
726
|
-
case "view_projects":
|
|
727
|
-
result = await handleViewProjects(args)
|
|
728
|
-
break
|
|
729
|
-
case "view_project":
|
|
730
|
-
result = await handleViewProject(args)
|
|
731
|
-
break
|
|
732
|
-
case "create_project":
|
|
733
|
-
result = await handleCreateProject(args)
|
|
734
|
-
break
|
|
735
|
-
case "rename_project":
|
|
736
|
-
result = await handleRenameProject(args)
|
|
737
|
-
break
|
|
738
|
-
case "delete_project":
|
|
739
|
-
result = await handleDeleteProject(args)
|
|
740
|
-
break
|
|
741
|
-
case "assign_project_to_group":
|
|
742
|
-
result = await handleAssignProjectToGroup(args)
|
|
743
|
-
break
|
|
744
|
-
case "create_space":
|
|
745
|
-
result = await handleCreateSpace(args)
|
|
746
|
-
break
|
|
747
|
-
case "view_space":
|
|
748
|
-
result = await handleViewSpace(args)
|
|
749
|
-
break
|
|
750
|
-
case "start":
|
|
751
|
-
result = await handleStart(args)
|
|
752
|
-
break
|
|
753
|
-
case "update_memory":
|
|
754
|
-
case "update_space":
|
|
755
|
-
result = await handleUpdateSpace(args)
|
|
756
|
-
break
|
|
757
|
-
case "update_folders":
|
|
758
|
-
result = await handleUpdateFolders(args)
|
|
759
|
-
break
|
|
760
|
-
case "message":
|
|
761
|
-
result = await handleMessage(args)
|
|
762
|
-
break
|
|
763
|
-
default:
|
|
764
|
-
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`)
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Extract response field for Layer 2 compressed tools, otherwise stringify full result
|
|
768
|
-
let responseText: string
|
|
769
|
-
if (
|
|
770
|
-
result &&
|
|
771
|
-
typeof result === "object" &&
|
|
772
|
-
"response" in result &&
|
|
773
|
-
typeof result.response === "string"
|
|
774
|
-
) {
|
|
775
|
-
// Tool has Layer 2 compression - use the formatted response field
|
|
776
|
-
responseText = result.response
|
|
777
|
-
} else {
|
|
778
|
-
// Standard tool - return full JSON
|
|
779
|
-
responseText = JSON.stringify(result, null, 2)
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// Add version warning to response if outdated
|
|
783
|
-
if (latestVersion && latestVersion !== CURRENT_VERSION) {
|
|
784
|
-
const warning = `\n\n UPDATE AVAILABLE: MCP Server v${latestVersion} is available (you're on v${CURRENT_VERSION}). Update with: npx @intangle/mcp-server@latest`
|
|
785
|
-
responseText += warning
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
return {
|
|
789
|
-
content: [{ type: "text", text: responseText }],
|
|
790
|
-
}
|
|
791
|
-
} catch (error) {
|
|
792
|
-
throw new McpError(
|
|
793
|
-
ErrorCode.InternalError,
|
|
794
|
-
`Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`
|
|
795
|
-
)
|
|
796
|
-
}
|
|
797
|
-
})
|
|
798
|
-
|
|
799
|
-
async function main() {
|
|
800
|
-
const transport = new StdioServerTransport()
|
|
801
|
-
await server.connect(transport)
|
|
802
|
-
log("Server connected to transport")
|
|
803
|
-
// Client info is captured on-demand in ensureClientInfo() before first API call
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
main().catch((err) => {
|
|
807
|
-
log(`Error in main: ${err}`)
|
|
808
|
-
process.exit(1)
|
|
809
|
-
})
|
|
810
|
-
} catch (err) {
|
|
811
|
-
log(`Fatal startup error: ${err}`)
|
|
812
|
-
process.exit(1)
|
|
813
|
-
}
|