@heuresis/mcp 1.0.0-rc.16 → 1.0.0-rc.17
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/dist/cloudTools.js +19 -3
- package/dist/index.js +90 -16
- package/package.json +1 -1
package/dist/cloudTools.js
CHANGED
|
@@ -188,11 +188,25 @@ function shapeProject(p) {
|
|
|
188
188
|
// ---------------------------------------------------------------------------
|
|
189
189
|
export const getSubtreeInput = z
|
|
190
190
|
.object({
|
|
191
|
-
rootId: z.string(),
|
|
191
|
+
rootId: z.string().min(1).optional(),
|
|
192
|
+
id: z
|
|
193
|
+
.string()
|
|
194
|
+
.min(1)
|
|
195
|
+
.optional()
|
|
196
|
+
.describe('Alias for rootId — the concept to root the subtree at (use either).'),
|
|
192
197
|
depth: z.number().int().min(0).max(6).default(3),
|
|
193
198
|
detail: detailArg,
|
|
194
199
|
})
|
|
195
|
-
.strict()
|
|
200
|
+
.strict()
|
|
201
|
+
.refine((a) => Boolean(a.rootId ?? a.id), {
|
|
202
|
+
message: 'Provide `id` (or `rootId`) — the concept to get the subtree for.',
|
|
203
|
+
path: ['id'],
|
|
204
|
+
})
|
|
205
|
+
.transform((a) => ({
|
|
206
|
+
rootId: (a.rootId ?? a.id),
|
|
207
|
+
depth: a.depth,
|
|
208
|
+
detail: a.detail,
|
|
209
|
+
}));
|
|
196
210
|
export async function getSubtree(client, args) {
|
|
197
211
|
const rootRes = await client.from('nodes').select('*').eq('id', args.rootId).maybeSingle();
|
|
198
212
|
if (rootRes.error)
|
|
@@ -1554,7 +1568,9 @@ export const CLOUD_TOOLS = [
|
|
|
1554
1568
|
},
|
|
1555
1569
|
{
|
|
1556
1570
|
name: 'get_subtree',
|
|
1557
|
-
description: 'A node and its descendants up to a given depth. Use when the user asks "tell me about everything under X".',
|
|
1571
|
+
description: 'A node and its descendants up to a given depth. Pass the concept as `id` (or `rootId`). Use when the user asks "tell me about everything under X".',
|
|
1572
|
+
// ZodEffects (refine+transform for the id/rootId alias) — cast like the
|
|
1573
|
+
// operator tools; zodToJsonSchema unwraps it for the served schema.
|
|
1558
1574
|
inputSchema: getSubtreeInput,
|
|
1559
1575
|
handler: async (client, args) => getSubtree(client, getSubtreeInput.parse(args)),
|
|
1560
1576
|
},
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// Subcommands (one-shot, never start the MCP server):
|
|
16
16
|
// login | logout | whoami | --help
|
|
17
|
-
import { existsSync } from 'node:fs';
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
18
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
19
19
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
20
20
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -27,8 +27,87 @@ import { CloudAuthError, getCloudClient, getCloudClientFromPassword } from './cl
|
|
|
27
27
|
import { ensureProxyAgent } from './proxy.js';
|
|
28
28
|
import { DEFAULT_SUPABASE_URL, helpCommand, loginCommand, logoutCommand, whoamiCommand, } from './cli.js';
|
|
29
29
|
import { readRealtimeFlag, resolveSubscriptionWorkspaceId, startRealtimeSubscription, stripRealtimeFlags, } from './realtime.js';
|
|
30
|
-
|
|
30
|
+
// Report the real published version (dist/index.js → ../package.json) so
|
|
31
|
+
// `heuresis-mcp --version` and the startup banner never lie about what's loaded.
|
|
32
|
+
const VERSION = (() => {
|
|
33
|
+
try {
|
|
34
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
35
|
+
return pkg.version ?? '0.0.0';
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return '0.0.0';
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
31
41
|
const MAX_RESULT_CHARS = 50_000;
|
|
42
|
+
// Fields worth preserving when a node/edge-bearing result is degraded to fit
|
|
43
|
+
// the size cap (everything else — descriptions, rationale, tags — is dropped).
|
|
44
|
+
const SKELETON_KEYS = [
|
|
45
|
+
'id',
|
|
46
|
+
'label',
|
|
47
|
+
'name',
|
|
48
|
+
'parentId',
|
|
49
|
+
'parent_id',
|
|
50
|
+
'kind',
|
|
51
|
+
'from',
|
|
52
|
+
'to',
|
|
53
|
+
'status',
|
|
54
|
+
'standing',
|
|
55
|
+
'starred',
|
|
56
|
+
'projectId',
|
|
57
|
+
'rootNodeId',
|
|
58
|
+
];
|
|
59
|
+
function skeletonize(item) {
|
|
60
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
61
|
+
const o = item;
|
|
62
|
+
const keep = {};
|
|
63
|
+
for (const k of SKELETON_KEYS)
|
|
64
|
+
if (k in o)
|
|
65
|
+
keep[k] = o[k];
|
|
66
|
+
return Object.keys(keep).length > 0 ? keep : item;
|
|
67
|
+
}
|
|
68
|
+
return item;
|
|
69
|
+
}
|
|
70
|
+
// Never hard-fail a read on size. If a result blows the cap, first drop verbose
|
|
71
|
+
// fields from its array elements (keep id/label/parent/kind…), then, if still
|
|
72
|
+
// too big, slice the largest array — always tagging `_truncated` + `_note` so
|
|
73
|
+
// the agent knows to narrow (limit/depth/scope) or fetch detail via get_concept.
|
|
74
|
+
function fitResultToCap(result, cap) {
|
|
75
|
+
if (JSON.stringify(result, null, 2).length <= cap)
|
|
76
|
+
return result;
|
|
77
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) {
|
|
78
|
+
const s = JSON.stringify(result, null, 2);
|
|
79
|
+
return {
|
|
80
|
+
_truncated: true,
|
|
81
|
+
_note: `Result exceeded ${cap} chars and could not be structurally reduced; showing a prefix. Narrow the query.`,
|
|
82
|
+
preview: s.slice(0, Math.max(0, cap - 200)),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const obj = { ...result };
|
|
86
|
+
const arrayKeys = Object.keys(obj).filter((k) => Array.isArray(obj[k]) && obj[k].some((x) => x && typeof x === 'object'));
|
|
87
|
+
for (const k of arrayKeys)
|
|
88
|
+
obj[k] = obj[k].map(skeletonize);
|
|
89
|
+
obj._truncated = true;
|
|
90
|
+
obj._note =
|
|
91
|
+
'Result exceeded the size cap; verbose fields were dropped (id/label/parent/kind kept). Use get_concept(id) for full detail, or narrow with limit/depth/scope.';
|
|
92
|
+
let guard = 0;
|
|
93
|
+
while (JSON.stringify(obj, null, 2).length > cap && guard++ < 100) {
|
|
94
|
+
let biggestKey;
|
|
95
|
+
let biggestLen = 0;
|
|
96
|
+
for (const k of arrayKeys) {
|
|
97
|
+
const len = obj[k].length;
|
|
98
|
+
if (len > biggestLen) {
|
|
99
|
+
biggestLen = len;
|
|
100
|
+
biggestKey = k;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!biggestKey || biggestLen <= 1)
|
|
104
|
+
break;
|
|
105
|
+
const arr = obj[biggestKey];
|
|
106
|
+
obj[biggestKey] = arr.slice(0, Math.max(1, Math.floor(arr.length * 0.8)));
|
|
107
|
+
obj._note = `Result exceeded the size cap; showing a truncated, skeletonized view (some "${biggestKey}" omitted). Narrow with limit/depth/scope, or fetch specifics with get_concept.`;
|
|
108
|
+
}
|
|
109
|
+
return obj;
|
|
110
|
+
}
|
|
32
111
|
function makeCloudTools(getClient, operatorTools) {
|
|
33
112
|
// Lazy: defer the actual auth handshake until the first tool call so the
|
|
34
113
|
// MCP server boots fast.
|
|
@@ -252,20 +331,10 @@ async function runServer() {
|
|
|
252
331
|
// background with their own concurrency control (cloudOperators.ts), so no
|
|
253
332
|
// per-call serialization is needed here.
|
|
254
333
|
const result = await tool.handler(req.params.arguments ?? {});
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
content: [
|
|
260
|
-
{
|
|
261
|
-
type: 'text',
|
|
262
|
-
text: `Result too large (${text.length} chars, limit ${MAX_RESULT_CHARS}). ` +
|
|
263
|
-
`Narrow the query: lower 'limit'/'depth', keep detail='compact', ` +
|
|
264
|
-
`or fetch individual nodes with get_concept.`,
|
|
265
|
-
},
|
|
266
|
-
],
|
|
267
|
-
};
|
|
268
|
-
}
|
|
334
|
+
// Never hard-fail on size: auto-degrade node/edge-bearing results
|
|
335
|
+
// (skeletonize → slice) so the agent always gets actionable structure
|
|
336
|
+
// back instead of an error it has to recover from.
|
|
337
|
+
const text = JSON.stringify(fitResultToCap(result, MAX_RESULT_CHARS), null, 2);
|
|
269
338
|
return {
|
|
270
339
|
content: [{ type: 'text', text }],
|
|
271
340
|
};
|
|
@@ -358,6 +427,11 @@ async function main() {
|
|
|
358
427
|
case 'whoami':
|
|
359
428
|
await whoamiCommand();
|
|
360
429
|
return;
|
|
430
|
+
case '-v':
|
|
431
|
+
case '--version':
|
|
432
|
+
case 'version':
|
|
433
|
+
console.log(VERSION);
|
|
434
|
+
return;
|
|
361
435
|
case '-h':
|
|
362
436
|
case '--help':
|
|
363
437
|
case 'help':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heuresis/mcp",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.17",
|
|
4
4
|
"mcpName": "io.github.ToremLabs/heuresis",
|
|
5
5
|
"description": "Cloud-authenticated Model Context Protocol server for a Heuresis workspace. Logs into the user's Heuresis account and lets any MCP client (Claude Desktop, Claude Code, Cursor, custom agents) read and write the same workspace the webapp uses. 31 data tools, 3 operator tools (Branch/Matrix/C-K/ASIT/TRIZ/Free/Combine/Explore), and live Realtime change subscriptions.",
|
|
6
6
|
"type": "module",
|