@intangle/mcp-server 2.6.9 → 2.6.10

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.
@@ -0,0 +1,211 @@
1
+ const SPACE_SCOPE_PROPERTIES = {
2
+ space_id: {
3
+ type: "string",
4
+ description: "REQUIRED: Intangle Space whose authorized HighQ connection should be used."
5
+ },
6
+ organization_id: {
7
+ type: "string",
8
+ description: "Optional Clerk organization ID for an organization-owned Space. Include it when the same raw space_id could exist in more than one graph."
9
+ }
10
+ };
11
+ export const HIGHQ_DIRECT_TOOL_NAMES = [
12
+ "HIGHQ_LIST_PENDING_REVIEWS",
13
+ "HIGHQ_GET_LEGAL_REVIEW_CONTEXT",
14
+ "HIGHQ_RESOLVE_FOLDER_PATH",
15
+ "HIGHQ_LIST_FOLDER_FILES",
16
+ "HIGHQ_GET_FILE_CONTENT",
17
+ "HIGHQ_FILL_LEGAL_REPORT"
18
+ ];
19
+ export const HIGHQ_DIRECT_TOOLS = [
20
+ {
21
+ name: "HIGHQ_LIST_PENDING_REVIEWS",
22
+ title: "List Pending HighQ Reviews",
23
+ description: "Return a clean shortlist of pending or in-progress HighQ request reviews from the connected site.",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ ...SPACE_SCOPE_PROPERTIES,
28
+ site_id: {
29
+ type: "string",
30
+ description: "HighQ site ID. Optional when exactly one allowed site is connected."
31
+ },
32
+ status_filter: {
33
+ type: "string",
34
+ enum: ["in_progress", "pending", "active", "all"],
35
+ description: "Optional review status filter. Defaults to active."
36
+ },
37
+ request_type_contains: {
38
+ type: "string",
39
+ description: "Optional case-insensitive request-type substring."
40
+ },
41
+ page_size: {
42
+ type: "integer",
43
+ minimum: 1,
44
+ maximum: 1000,
45
+ description: "Optional number of request rows to inspect. Defaults to 100."
46
+ }
47
+ },
48
+ required: ["space_id"]
49
+ }
50
+ },
51
+ {
52
+ name: "HIGHQ_GET_LEGAL_REVIEW_CONTEXT",
53
+ title: "Get HighQ Legal Review Context",
54
+ description: "Resolve one HighQ request into structured review fields, source-document context, a safe writeback blueprint, and canonical Intangle Item retrieval coordinates for ingested attachments. Original file_* IDs remain accepted compatibility identities for the same Item.",
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {
58
+ ...SPACE_SCOPE_PROPERTIES,
59
+ site_id: {
60
+ type: "string",
61
+ description: "HighQ site ID. Optional when exactly one allowed site is connected."
62
+ },
63
+ request_id: {
64
+ type: "string",
65
+ description: "Request-style identifier such as RQ-0023. Required when item_id is omitted."
66
+ },
67
+ item_id: {
68
+ type: "string",
69
+ description: "Direct HighQ iSheet row ID. Required when request_id is omitted."
70
+ },
71
+ isheet_id: {
72
+ type: "string",
73
+ description: "Optional Requests iSheet ID."
74
+ },
75
+ file_metadata_isheet_id: {
76
+ type: "string",
77
+ description: "Optional File Metadata iSheet ID used to resolve request-linked documents."
78
+ },
79
+ section_label: {
80
+ type: "string",
81
+ description: "Optional section label. Defaults to Legal Report."
82
+ },
83
+ view_id: {
84
+ type: "string",
85
+ description: "Optional HighQ view ID matching the visible form."
86
+ },
87
+ max_documents: {
88
+ type: "integer",
89
+ minimum: 1,
90
+ maximum: 20,
91
+ description: "Optional maximum number of source documents. Defaults to 8."
92
+ }
93
+ },
94
+ required: ["space_id"]
95
+ }
96
+ },
97
+ {
98
+ name: "HIGHQ_RESOLVE_FOLDER_PATH",
99
+ title: "Resolve HighQ Folder Path",
100
+ description: "Resolve a human-readable HighQ folder path to its exact HighQ folder ID.",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: {
104
+ ...SPACE_SCOPE_PROPERTIES,
105
+ site_id: { type: "string", description: "REQUIRED HighQ site ID." },
106
+ folder_path: {
107
+ type: "string",
108
+ description: "REQUIRED HighQ folder path. Numeric UI prefixes may be included."
109
+ },
110
+ start_folder_id: {
111
+ type: "string",
112
+ description: "Optional existing folder ID to resolve relative to."
113
+ }
114
+ },
115
+ required: ["space_id", "site_id", "folder_path"]
116
+ }
117
+ },
118
+ {
119
+ name: "HIGHQ_LIST_FOLDER_FILES",
120
+ title: "List HighQ Folder Files",
121
+ description: "List the files stored directly in one HighQ folder.",
122
+ inputSchema: {
123
+ type: "object",
124
+ properties: {
125
+ ...SPACE_SCOPE_PROPERTIES,
126
+ site_id: {
127
+ type: "string",
128
+ description: "Optional when exactly one allowed HighQ site is connected. The resolved file must belong to that site."
129
+ },
130
+ folder_id: { type: "string", description: "REQUIRED HighQ folder ID." }
131
+ },
132
+ required: ["space_id", "folder_id"]
133
+ }
134
+ },
135
+ {
136
+ name: "HIGHQ_GET_FILE_CONTENT",
137
+ title: "Get HighQ File Content",
138
+ description: "Retrieve text when safely extractable from one HighQ file, otherwise return metadata and a retrieval hint. Raw binary payloads are not returned.",
139
+ inputSchema: {
140
+ type: "object",
141
+ properties: {
142
+ ...SPACE_SCOPE_PROPERTIES,
143
+ site_id: {
144
+ type: "string",
145
+ description: "Optional allowed HighQ site ID."
146
+ },
147
+ file_id: { type: "string", description: "REQUIRED HighQ file ID." },
148
+ original: {
149
+ type: "boolean",
150
+ description: "Optional request for the original file on older HighQ APIs."
151
+ },
152
+ get_converted_file: {
153
+ type: "boolean",
154
+ description: "Optional request for converted content on newer HighQ APIs."
155
+ },
156
+ api_version: {
157
+ type: "integer",
158
+ minimum: 1,
159
+ description: "Optional HighQ file-download API version."
160
+ }
161
+ },
162
+ required: ["space_id", "file_id"]
163
+ }
164
+ },
165
+ {
166
+ name: "HIGHQ_FILL_LEGAL_REPORT",
167
+ title: "Fill HighQ Legal Report",
168
+ description: "Prepare a Phoenix-authorized, durable writeback for the visible Legal Report fields of one HighQ request using the blueprint returned by HIGHQ_GET_LEGAL_REVIEW_CONTEXT.",
169
+ inputSchema: {
170
+ type: "object",
171
+ properties: {
172
+ ...SPACE_SCOPE_PROPERTIES,
173
+ site_id: {
174
+ type: "string",
175
+ description: "HighQ site ID. Optional when exactly one allowed site is connected."
176
+ },
177
+ request_id: {
178
+ type: "string",
179
+ description: "Request-style identifier such as RQ-0023. Required when item_id is omitted."
180
+ },
181
+ item_id: {
182
+ type: "string",
183
+ description: "Direct HighQ iSheet row ID. Required when request_id is omitted."
184
+ },
185
+ isheet_id: {
186
+ type: "string",
187
+ description: "Optional Requests iSheet ID."
188
+ },
189
+ section_label: {
190
+ type: "string",
191
+ description: "Optional section label. Defaults to Legal Report."
192
+ },
193
+ view_id: {
194
+ type: "string",
195
+ description: "Optional HighQ view ID matching the visible form."
196
+ },
197
+ field_values: {
198
+ type: "object",
199
+ additionalProperties: true,
200
+ description: "REQUIRED field labels or column keys mapped to intended Legal Report values."
201
+ },
202
+ idempotency_key: {
203
+ type: "string",
204
+ pattern: "\\S",
205
+ description: "REQUIRED stable identity for this intended write. Generate it once and reuse the exact value when polling or retrying so Phoenix can reconcile the durable approval without duplicate HighQ writes."
206
+ }
207
+ },
208
+ required: ["space_id", "field_values", "idempotency_key"]
209
+ }
210
+ }
211
+ ];
package/dist/index.js CHANGED
@@ -10,7 +10,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
10
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
- import { TOOLS } from "./tool-definitions.js";
13
+ import { HIGHQ_DIRECT_TOOL_NAMES } from "./highq-tool-definitions.js";
14
+ const HIGHQ_DIRECT_TOOL_NAME_SET = new Set(HIGHQ_DIRECT_TOOL_NAMES);
14
15
  // ... imports ...
15
16
  // Debug logging
16
17
  const DEBUG_LOG = "/tmp/intangle-mcp-debug.log";
@@ -196,31 +197,45 @@ try {
196
197
  method,
197
198
  params,
198
199
  };
199
- const response = await fetch(buildRemoteApiUrl(), {
200
- method: "POST",
201
- headers,
202
- body: JSON.stringify(rpcBody),
203
- });
204
- const responseText = await response.text();
205
- let responseJson = {};
200
+ const controller = new AbortController();
201
+ const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
206
202
  try {
207
- responseJson = responseText ? JSON.parse(responseText) : {};
208
- }
209
- catch {
210
- throw new Error(`Failed to parse MCP remote ${method} response`);
203
+ const response = await fetch(buildRemoteApiUrl(), {
204
+ method: "POST",
205
+ headers,
206
+ body: JSON.stringify(rpcBody),
207
+ signal: controller.signal,
208
+ });
209
+ const responseText = await response.text();
210
+ let responseJson = {};
211
+ try {
212
+ responseJson = responseText ? JSON.parse(responseText) : {};
213
+ }
214
+ catch {
215
+ throw new Error(`Failed to parse MCP remote ${method} response`);
216
+ }
217
+ if (!response.ok) {
218
+ const message = responseJson?.error?.message ||
219
+ responseJson?.message ||
220
+ `${response.status} ${response.statusText}`;
221
+ throw new Error(`MCP remote ${method} failed: ${message}`);
222
+ }
223
+ if (responseJson?.error) {
224
+ const code = responseJson.error.code;
225
+ const message = responseJson.error.message || "Unknown MCP remote error";
226
+ throw new Error(`MCP remote ${method} error (${code}): ${message}`);
227
+ }
228
+ return responseJson?.result ?? {};
211
229
  }
212
- if (!response.ok) {
213
- const message = responseJson?.error?.message ||
214
- responseJson?.message ||
215
- `${response.status} ${response.statusText}`;
216
- throw new Error(`MCP remote ${method} failed: ${message}`);
230
+ catch (error) {
231
+ if (error instanceof Error && error.name === "AbortError") {
232
+ throw new Error(`MCP remote ${method} timed out after ${API_TIMEOUT_MS / 1000}s`);
233
+ }
234
+ throw error;
217
235
  }
218
- if (responseJson?.error) {
219
- const code = responseJson.error.code;
220
- const message = responseJson.error.message || "Unknown MCP remote error";
221
- throw new Error(`MCP remote ${method} error (${code}): ${message}`);
236
+ finally {
237
+ clearTimeout(timeoutId);
222
238
  }
223
- return responseJson?.result ?? {};
224
239
  }
225
240
  const server = new Server({
226
241
  name: "intangle-context",
@@ -237,9 +252,13 @@ try {
237
252
  tools: {},
238
253
  },
239
254
  });
240
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
241
- tools: TOOLS,
242
- }));
255
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
256
+ const result = await makeRemoteRpcCall("tools/list", {});
257
+ if (!Array.isArray(result?.tools)) {
258
+ throw new McpError(ErrorCode.InternalError, "Remote tool catalog unavailable");
259
+ }
260
+ return { tools: result.tools };
261
+ });
243
262
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
244
263
  const result = await makeRemoteRpcCall("resources/list", {});
245
264
  return {
@@ -536,6 +555,17 @@ try {
536
555
  result = await handleMessage(args);
537
556
  break;
538
557
  default:
558
+ if (HIGHQ_DIRECT_TOOL_NAME_SET.has(name)) {
559
+ const remoteResult = await makeRemoteRpcCall("tools/call", {
560
+ name,
561
+ arguments: args,
562
+ });
563
+ const text = remoteResult?.content?.find((entry) => entry?.type === "text" && typeof entry.text === "string")?.text;
564
+ result = {
565
+ response: typeof text === "string" ? text : JSON.stringify(remoteResult),
566
+ };
567
+ break;
568
+ }
539
569
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
540
570
  }
541
571
  // Extract response field for Layer 2 compressed tools, otherwise stringify full result
@@ -1,3 +1,4 @@
1
+ import { HIGHQ_DIRECT_TOOLS } from "./highq-tool-definitions.js";
1
2
  // Tools definition - matches the stdio server
2
3
  const TOOL_DEFINITIONS = [
3
4
  {
@@ -39,7 +40,7 @@ const TOOL_DEFINITIONS = [
39
40
  {
40
41
  name: "fetch_items",
41
42
  title: "Fetch Items by ID",
42
- description: "Retrieve full content for specific items by ID, including `file_*`, `artifact_*`, and `summary_*` ids. File fetches return extracted content, file metadata, chunk metadata, and decrypted `data.source_data_url` when available; use this before asking Tan to reason over a specific uploaded contract/file in detail. Artifact fetches return content plus render metadata and `config`/`state`/`data` when available.",
43
+ description: "Retrieve full content for specific Items by ID. Canonical generalized Items retain original compatibility IDs such as `file_*`, `doc_*`, `context_*`, `task_*`, and `skill_*`; pass those IDs unchanged. When Phoenix resolves a distinct legacy alias, the result preserves `requested_id` and `legacy_id` while returning the canonical Item `id`. File fetches return extracted content, file metadata, chunk metadata, and decrypted `data.source_data_url` when available. Artifact fetches accept `artifact_*` IDs and return content plus render metadata and `config`/`state`/`data` when available.",
43
44
  inputSchema: {
44
45
  type: "object",
45
46
  properties: {
@@ -47,6 +48,10 @@ const TOOL_DEFINITIONS = [
47
48
  type: "string",
48
49
  description: "Optional: Space containing the items. Provide this for shared/org spaces so fetch resolves against the correct graph."
49
50
  },
51
+ organization_id: {
52
+ type: "string",
53
+ description: "Optional Clerk org ID when the Space belongs to an organization graph. Include it when the same raw space_id could exist in multiple graphs."
54
+ },
50
55
  id: {
51
56
  type: "string",
52
57
  description: "Single ID to fetch (e.g. `mem_123`, `task_456`, `skill_789`, `doc_321`, `file_654`, `artifact_654`, or `summary_987`)."
@@ -59,7 +64,33 @@ const TOOL_DEFINITIONS = [
59
64
  }
60
65
  }
61
66
  },
62
- { name: "read_artifact", title: "Read Artifact", description: "Read one existing artifact by `artifact_id` without asking Tan to restream it in chat. Returns content plus render metadata and `config`/`state`/`data` when available. Prefer this when an external agent already has the artifact id and needs the current artifact payload directly.", inputSchema: { type: "object", properties: { space_id: { type: "string", description: "Optional: Space containing the artifact. Provide this for shared/org spaces so access checks resolve against the correct graph." }, artifact_id: { type: "string", description: "REQUIRED: Existing artifact ID to read (e.g. `artifact_123`)." }, item_id: { type: "string", description: "Optional alias for `artifact_id`." }, organization_id: { type: "string", description: "Optional Clerk org ID when the space belongs to an organization graph." } }, required: ["artifact_id"] } },
67
+ {
68
+ name: "read_artifact",
69
+ title: "Read Artifact",
70
+ description: "Read one existing artifact by `artifact_id` without asking Tan to restream it in chat. Returns content plus render metadata and `config`/`state`/`data` when available. Prefer this when an external agent already has the artifact id and needs the current artifact payload directly.",
71
+ inputSchema: {
72
+ type: "object",
73
+ properties: {
74
+ space_id: {
75
+ type: "string",
76
+ description: "Optional: Space containing the artifact. Provide this for shared/org spaces so access checks resolve against the correct graph."
77
+ },
78
+ artifact_id: {
79
+ type: "string",
80
+ description: "REQUIRED: Existing artifact ID to read (e.g. `artifact_123`)."
81
+ },
82
+ item_id: {
83
+ type: "string",
84
+ description: "Optional alias for `artifact_id`."
85
+ },
86
+ organization_id: {
87
+ type: "string",
88
+ description: "Optional Clerk org ID when the space belongs to an organization graph."
89
+ }
90
+ },
91
+ required: ["artifact_id"]
92
+ }
93
+ },
63
94
  {
64
95
  name: "usage_status",
65
96
  title: "Usage Status",
@@ -435,34 +466,6 @@ const TOOL_DEFINITIONS = [
435
466
  properties: {}
436
467
  }
437
468
  },
438
- {
439
- name: "create_space",
440
- title: "Create Space",
441
- description: "Create a new space with optional configuration.",
442
- inputSchema: {
443
- type: "object",
444
- properties: {
445
- name: {
446
- type: "string",
447
- description: "Name for the NEW space being created (e.g., 'Work', 'Personal', 'Project X')"
448
- },
449
- description: {
450
- type: "string",
451
- description: "Brief description of what this NEW space is for (e.g., 'Work-related context and tasks')"
452
- },
453
- config_badges: {
454
- type: "array",
455
- items: { type: "string" },
456
- description: "Array of keywords for this NEW space. Examples: ['fitness', 'recipes', 'travel planning'], ['client work', 'meetings', 'proposals'], ['TypeScript', 'React', 'API design']. They help agents understand and prioritize the space."
457
- },
458
- startup_preferences: {
459
- type: "string",
460
- description: "AI behavior preferences for this NEW space. Guides how AI assistants and agents should communicate and behave (e.g., 'be concise and frank', 'think critically', 'play devils advocate')."
461
- }
462
- },
463
- required: ["name"]
464
- }
465
- },
466
469
  {
467
470
  name: "view_space",
468
471
  title: "View Space",
@@ -701,7 +704,10 @@ const TOOL_DEFINITIONS = [
701
704
  items: {
702
705
  type: "object",
703
706
  properties: {
704
- folder_id: { type: "string", description: "Folder ID to rename." },
707
+ folder_id: {
708
+ type: "string",
709
+ description: "Folder ID to rename."
710
+ },
705
711
  new_name: { type: "string", description: "New folder name." }
706
712
  },
707
713
  required: ["folder_id", "new_name"]
@@ -814,7 +820,8 @@ const TOOL_DEFINITIONS = [
814
820
  },
815
821
  required: ["space_id", "content", "idempotency_key"]
816
822
  }
817
- }
823
+ },
824
+ ...HIGHQ_DIRECT_TOOLS
818
825
  // DISABLED: memory_action tool is broken and causing errors.
819
826
  // Pending OHM protocol fix. Use update_memory, search, or fetch_items instead.
820
827
  // {
@@ -847,12 +854,16 @@ const READ_ONLY_TOOLS = new Set([
847
854
  "fetch_conversation_messages",
848
855
  "list_conversation_inbox",
849
856
  "view_spaces",
850
- "view_space"
857
+ "view_space",
858
+ "HIGHQ_LIST_PENDING_REVIEWS",
859
+ "HIGHQ_RESOLVE_FOLDER_PATH",
860
+ "HIGHQ_LIST_FOLDER_FILES",
861
+ "HIGHQ_GET_FILE_CONTENT"
851
862
  ]);
852
863
  const DESTRUCTIVE_TOOLS = new Set([
853
- "create_space",
854
864
  "update_memory",
855
- "update_folders"
865
+ "update_folders",
866
+ "HIGHQ_FILL_LEGAL_REPORT"
856
867
  ]);
857
868
  const IDEMPOTENT_TOOLS = new Set([
858
869
  "search",
@@ -862,7 +873,11 @@ const IDEMPOTENT_TOOLS = new Set([
862
873
  "fetch_conversation_messages",
863
874
  "list_conversation_inbox",
864
875
  "view_spaces",
865
- "view_space"
876
+ "view_space",
877
+ "HIGHQ_LIST_PENDING_REVIEWS",
878
+ "HIGHQ_RESOLVE_FOLDER_PATH",
879
+ "HIGHQ_LIST_FOLDER_FILES",
880
+ "HIGHQ_GET_FILE_CONTENT"
866
881
  ]);
867
882
  export const TOOLS = TOOL_DEFINITIONS.map((tool) => ({
868
883
  ...tool,
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@intangle/mcp-server",
3
- "version": "2.6.9",
3
+ "version": "2.6.10",
4
4
  "description": "Model Context Protocol server for Intangle - AI context that persists across conversations",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "start": "node dist/index.js",
9
9
  "dev": "tsx watch index.ts",
10
- "prebuild": "cp ../web/src/app/api/mcp-remote/tool-definitions.ts ./tool-definitions.ts",
10
+ "prebuild": "node scripts/sync-tool-definitions.mjs",
11
11
  "build": "tsc",
12
12
  "lint": "biome check .",
13
- "lint:release": "biome check --config-path=biome.release.json package.json index.ts tool-definitions.ts",
13
+ "lint:release": "biome check --config-path=biome.release.json package.json index.ts tool-definitions.ts highq-tool-definitions.ts",
14
14
  "lint:fix": "biome check --fix .",
15
15
  "prepublishOnly": "npm run build"
16
16
  },
@@ -55,5 +55,5 @@
55
55
  "tsx": "^4.0.0",
56
56
  "typescript": "^5.0.0"
57
57
  },
58
- "intangleSourceHash": "9abfe72739f6805935f9dbc2649e64fe58535ea7776d22be212609679a600e7c"
58
+ "intangleSourceHash": "11c5f54f11f3f1ad4db90f88c9da05835ef99f8307554d7c7d22ba7cf61054c1"
59
59
  }