@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.
@@ -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
- const VERSION = '0.2.0-alpha';
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
- const text = JSON.stringify(result, null, 2);
256
- if (text.length > MAX_RESULT_CHARS) {
257
- return {
258
- isError: true,
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.16",
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",