@nestr/mcp 0.1.46 → 0.1.48
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/build/api/client.d.ts +2 -0
- package/build/api/client.d.ts.map +1 -1
- package/build/api/client.js +4 -0
- package/build/api/client.js.map +1 -1
- package/build/server.d.ts.map +1 -1
- package/build/server.js +81 -5
- package/build/server.js.map +1 -1
- package/build/skills/doing-work.d.ts.map +1 -1
- package/build/skills/doing-work.js +62 -137
- package/build/skills/doing-work.js.map +1 -1
- package/build/skills/tension-processing.d.ts.map +1 -1
- package/build/skills/tension-processing.js +75 -181
- package/build/skills/tension-processing.js.map +1 -1
- package/build/skills/workspace-setup.d.ts.map +1 -1
- package/build/skills/workspace-setup.js +58 -138
- package/build/skills/workspace-setup.js.map +1 -1
- package/build/tools/index.d.ts +64 -10
- package/build/tools/index.d.ts.map +1 -1
- package/build/tools/index.js +121 -18
- package/build/tools/index.js.map +1 -1
- package/package.json +1 -1
package/build/tools/index.js
CHANGED
|
@@ -27,7 +27,7 @@ function completableResponse(data, source, title) {
|
|
|
27
27
|
// Fields to keep for compact list responses (reduces token usage)
|
|
28
28
|
const COMPACT_FIELDS = {
|
|
29
29
|
// Common fields for all nests (includes fields needed by the completable list app)
|
|
30
|
-
base: ["_id", "title", "purpose", "completed", "labels", "path", "parentId", "ancestors", "description", "due"],
|
|
30
|
+
base: ["_id", "title", "purpose", "completed", "labels", "path", "parentId", "ancestors", "description", "due", "hints"],
|
|
31
31
|
// Additional fields for roles
|
|
32
32
|
role: ["accountabilities", "domains"],
|
|
33
33
|
// Additional fields for users
|
|
@@ -65,6 +65,68 @@ function compactResponse(data, type = "nest") {
|
|
|
65
65
|
return compact;
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
|
+
// URL-to-tool mapping for hint enrichment.
|
|
69
|
+
// The Nestr API returns hints with relative URLs (e.g., "/nests/abc123/children?search=...").
|
|
70
|
+
// This maps those URL patterns to MCP tool calls so models can act on hints directly.
|
|
71
|
+
// Note: patterns are tried in order — more specific patterns must come before catch-alls.
|
|
72
|
+
const HINT_URL_PATTERNS = [
|
|
73
|
+
// /nests/{id}/children?search=... → nestr_search with in:{id} scoped query
|
|
74
|
+
{
|
|
75
|
+
pattern: /^\/nests\/([^/]+)\/children$/,
|
|
76
|
+
tool: "nestr_search",
|
|
77
|
+
params: (m, sp, workspaceId) => {
|
|
78
|
+
const search = sp.get("search") || "";
|
|
79
|
+
const result = { query: `in:${m[1]} ${search}`.trim() };
|
|
80
|
+
if (workspaceId)
|
|
81
|
+
result.workspaceId = workspaceId;
|
|
82
|
+
return result;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
// /nests/{id}/posts → nestr_get_comments
|
|
86
|
+
{ pattern: /^\/nests\/([^/]+)\/posts$/, tool: "nestr_get_comments", params: (m) => ({ nestId: m[1] }) },
|
|
87
|
+
// /nests/{id}/tensions → nestr_list_tensions
|
|
88
|
+
{ pattern: /^\/nests\/([^/]+)\/tensions$/, tool: "nestr_list_tensions", params: (m) => ({ nestId: m[1] }) },
|
|
89
|
+
// /nests/{id} → nestr_get_nest (must be last — catches all /nests/{id} patterns)
|
|
90
|
+
{ pattern: /^\/nests\/([^/]+)$/, tool: "nestr_get_nest", params: (m) => ({ nestId: m[1] }) },
|
|
91
|
+
];
|
|
92
|
+
// Enrich hints with tool call parameters so models can act on hints directly.
|
|
93
|
+
// Extracts workspaceId from nest ancestors (last element) for search-based hints.
|
|
94
|
+
function enrichHints(data) {
|
|
95
|
+
if (!data || typeof data !== "object")
|
|
96
|
+
return data;
|
|
97
|
+
// Handle arrays (e.g., from getNestChildren)
|
|
98
|
+
if (Array.isArray(data)) {
|
|
99
|
+
return data.map((item) => enrichHints(item));
|
|
100
|
+
}
|
|
101
|
+
// Handle wrapped responses { data: [...] }
|
|
102
|
+
if ("data" in data && Array.isArray(data.data)) {
|
|
103
|
+
return { ...data, data: enrichHints(data.data) };
|
|
104
|
+
}
|
|
105
|
+
// Enrich hints on this nest
|
|
106
|
+
const record = data;
|
|
107
|
+
if (Array.isArray(record.hints)) {
|
|
108
|
+
// Extract workspaceId from ancestors (last element is always the workspace)
|
|
109
|
+
const ancestors = record.ancestors;
|
|
110
|
+
const workspaceId = ancestors?.length ? ancestors[ancestors.length - 1] : undefined;
|
|
111
|
+
record.hints = record.hints.map((hint) => {
|
|
112
|
+
if (!hint.url)
|
|
113
|
+
return hint;
|
|
114
|
+
// Parse URL and query params
|
|
115
|
+
const [path, queryString] = hint.url.split("?");
|
|
116
|
+
const searchParams = new URLSearchParams(queryString || "");
|
|
117
|
+
for (const { pattern, tool, params } of HINT_URL_PATTERNS) {
|
|
118
|
+
const match = path.match(pattern);
|
|
119
|
+
if (match) {
|
|
120
|
+
return { ...hint, toolCall: { tool, params: params(match, searchParams, workspaceId) } };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Log unrecognized hint URLs so we can add mappings when the API adds new patterns
|
|
124
|
+
console.error(`[nestr-mcp] Unrecognized hint URL pattern: "${hint.url}" (hint type: ${hint.type})`);
|
|
125
|
+
return hint;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return data;
|
|
129
|
+
}
|
|
68
130
|
// Tool input schemas using Zod
|
|
69
131
|
export const schemas = {
|
|
70
132
|
listWorkspaces: z.object({
|
|
@@ -94,11 +156,13 @@ export const schemas = {
|
|
|
94
156
|
getNest: z.object({
|
|
95
157
|
nestId: z.string().describe("Nest ID. Supports comma-separated IDs to fetch multiple nests in one call (e.g., 'id1,id2,id3') — returns an array instead of a single object. Keep total URL under 2000 chars to avoid HTTP limits."),
|
|
96
158
|
fieldsMetaData: z.boolean().optional().describe("Set to true to include field schema metadata (e.g., available options for project.status)"),
|
|
159
|
+
hints: z.boolean().optional().describe("Include contextual hints on each nest (default: true). Hints surface actionable signals like unassigned roles, stale projects, or unread comments. Set to false for bulk lookups where you only need structural data, not contextual guidance."),
|
|
97
160
|
}),
|
|
98
161
|
getNestChildren: z.object({
|
|
99
162
|
nestId: z.string().describe("Parent nest ID"),
|
|
100
163
|
limit: z.number().optional().describe("Max results per page. Omit to see full count in meta.total."),
|
|
101
164
|
page: z.number().optional().describe("Page number for pagination"),
|
|
165
|
+
hints: z.boolean().optional().describe("Include contextual hints on each child nest (default: true). Set to false for large result sets or bulk operations where contextual signals aren't needed."),
|
|
102
166
|
_listTitle: z.string().optional().describe("Short descriptive title for the list UI (e.g., \"Tasks for Website Redesign\"). Omit for default."),
|
|
103
167
|
}),
|
|
104
168
|
createNest: z.object({
|
|
@@ -133,11 +197,11 @@ export const schemas = {
|
|
|
133
197
|
}),
|
|
134
198
|
addComment: z.object({
|
|
135
199
|
nestId: z.string().describe("Nest ID to comment on"),
|
|
136
|
-
body: z.string().describe("Comment text (supports HTML and @mentions)"),
|
|
200
|
+
body: z.string().describe("Comment text (supports HTML and @mentions: @{userId}, @{email}, @{circle})"),
|
|
137
201
|
}),
|
|
138
202
|
updateComment: z.object({
|
|
139
203
|
commentId: z.string().describe("Comment ID to update"),
|
|
140
|
-
body: z.string().describe("Updated comment text (supports HTML and @mentions)"),
|
|
204
|
+
body: z.string().describe("Updated comment text (supports HTML and @mentions: @{userId}, @{email}, @{circle})"),
|
|
141
205
|
}),
|
|
142
206
|
deleteComment: z.object({
|
|
143
207
|
commentId: z.string().describe("Comment ID to delete"),
|
|
@@ -491,7 +555,27 @@ export const toolDefinitions = [
|
|
|
491
555
|
},
|
|
492
556
|
{
|
|
493
557
|
name: "nestr_search",
|
|
494
|
-
description:
|
|
558
|
+
description: `Search for nests within a workspace. IMPORTANT: Use completed:false when searching for work to exclude old completed items.
|
|
559
|
+
|
|
560
|
+
Operators (combine with spaces for AND; commas within operator for OR; ! prefix for negation):
|
|
561
|
+
- label:role / label:!project — Filter/exclude by label
|
|
562
|
+
- parent-label:circle — Parent has this label
|
|
563
|
+
- assignee:me / assignee:userId / assignee:none / assignee:!userId — Filter by assignee
|
|
564
|
+
- completed:false / completed:true / completed:past_7_days / completed:this_month / completed:YYYY-MM-DD_YYYY-MM-DD
|
|
565
|
+
- in:nestId — Scope to descendants of a specific nest
|
|
566
|
+
- depth:1 / depth:2 — Limit depth (1=direct children, 2=children+grandchildren)
|
|
567
|
+
- mindepth:N — Minimum depth from context
|
|
568
|
+
- has:due / has:pastdue / has:children / has:incompletechildren (supports ! prefix)
|
|
569
|
+
- project->status:Current,Future / project->status:!Done — Field value search (label->field:value)
|
|
570
|
+
- fields.label.property:value — Search any field value (supports partial match)
|
|
571
|
+
- data.property:value — Search data properties
|
|
572
|
+
- updated-date:past_7_days / updated-date:!past_30_days — Filter by update recency
|
|
573
|
+
- sort:title / sort:due / sort:updatedAt / sort:createdAt + sort-order:asc/desc
|
|
574
|
+
- createdby:me / admin:me / type:comment / limit:N / template:id
|
|
575
|
+
|
|
576
|
+
Examples: label:role → all roles | assignee:me completed:false → my active work | in:circleId label:role depth:1 → roles in circle | label:project project->status:Current → active projects | in:roleId label:project → role's projects | has:pastdue completed:false → overdue items | label:accountability customer → accountabilities matching keyword
|
|
577
|
+
|
|
578
|
+
Response includes meta.total showing total matching count. IMPORTANT UI RULE: The completable list app must ONLY be used when results are confirmed to contain completable items (tasks, projects, todos) AND there are results to show. When searching for roles, circles, metrics, policies, or any non-completable type, you MUST omit the _listTitle parameter and respond in plain text instead. Never render empty results in the app.`,
|
|
495
579
|
inputSchema: {
|
|
496
580
|
type: "object",
|
|
497
581
|
properties: {
|
|
@@ -504,7 +588,8 @@ export const toolDefinitions = [
|
|
|
504
588
|
},
|
|
505
589
|
required: ["workspaceId", "query"],
|
|
506
590
|
},
|
|
507
|
-
_meta: completableListUi,
|
|
591
|
+
// No _meta: completableListUi — search returns all types of nests (roles, circles, etc.).
|
|
592
|
+
// The completable list app should only be used when results are confirmed to be completable items.
|
|
508
593
|
...readOnly,
|
|
509
594
|
},
|
|
510
595
|
{
|
|
@@ -515,6 +600,7 @@ export const toolDefinitions = [
|
|
|
515
600
|
properties: {
|
|
516
601
|
nestId: { type: "string", description: "Nest ID, or comma-separated IDs to fetch multiple nests at once (e.g., 'id1,id2,id3'). Keep total URL under 2000 chars." },
|
|
517
602
|
fieldsMetaData: { type: "boolean", description: "Set to true to include field schema metadata (available options, field types)" },
|
|
603
|
+
hints: { type: "boolean", description: "Include contextual hints (default: true). Set to false for bulk lookups where you only need structural data." },
|
|
518
604
|
stripDescription: { type: "boolean", description: "Set true to strip description fields from response, significantly reducing size." },
|
|
519
605
|
},
|
|
520
606
|
required: ["nestId"],
|
|
@@ -530,17 +616,19 @@ export const toolDefinitions = [
|
|
|
530
616
|
nestId: { type: "string", description: "Parent nest ID" },
|
|
531
617
|
limit: { type: "number", description: "Omit on first call to see meta.total count" },
|
|
532
618
|
page: { type: "number", description: "Page number (1-indexed)" },
|
|
619
|
+
hints: { type: "boolean", description: "Include contextual hints (default: true). Set to false for large result sets or bulk operations." },
|
|
533
620
|
stripDescription: { type: "boolean", description: "Set true to strip description fields from response, significantly reducing size. Ideal for bulk/index operations." },
|
|
534
621
|
_listTitle: { type: "string", description: "Short descriptive title for the list UI header (e.g., \"Tasks for Website Redesign\", \"API project sub-tasks\"). Include the parent name for context." },
|
|
535
622
|
},
|
|
536
623
|
required: ["nestId"],
|
|
537
624
|
},
|
|
538
|
-
_meta: completableListUi,
|
|
625
|
+
// No _meta: completableListUi — children can be any type (roles, accountabilities, etc.).
|
|
626
|
+
// The completable list app should only be used when results are confirmed to be completable items.
|
|
539
627
|
...readOnly,
|
|
540
628
|
},
|
|
541
629
|
{
|
|
542
630
|
name: "nestr_create_nest",
|
|
543
|
-
description: "Create a new nest (task, project, role, circle, etc.) under a parent. Set users to assign to people - placing under a role does NOT auto-assign. For roles and circles: include accountabilities and domains arrays to create them inline (requires workspaceId). The API auto-routes to the self-organization endpoint when governance labels are detected with accountabilities/domains.",
|
|
631
|
+
description: "Create a new nest (task, project, role, circle, etc.) under a parent. Set users to assign to people - placing under a role does NOT auto-assign. For roles and circles: include accountabilities and domains arrays to create them inline (requires workspaceId). The API auto-routes to the self-organization endpoint when governance labels are detected with accountabilities/domains. GOVERNANCE RULE: Only create roles, circles, accountabilities, domains, or policies directly during workspace/circle setup mode (new or sparsely populated workspace). In established workspaces with multiple users, governance changes MUST go through the tension/proposal flow (nestr_create_tension + nestr_add_tension_part) so circle members can consent.",
|
|
544
632
|
inputSchema: {
|
|
545
633
|
type: "object",
|
|
546
634
|
properties: {
|
|
@@ -579,7 +667,7 @@ export const toolDefinitions = [
|
|
|
579
667
|
},
|
|
580
668
|
{
|
|
581
669
|
name: "nestr_update_nest",
|
|
582
|
-
description: "Update properties of an existing nest. Use parentId to move a nest (e.g., inbox item to a project). For roles and circles: include accountabilities and domains arrays to update them inline (requires workspaceId). For AI knowledge persistence, create skill-labeled nests under roles/circles instead of using data fields.",
|
|
670
|
+
description: "Update properties of an existing nest. Use parentId to move a nest (e.g., inbox item to a project). For roles and circles: include accountabilities and domains arrays to update them inline (requires workspaceId). For AI knowledge persistence, create skill-labeled nests under roles/circles instead of using data fields. GOVERNANCE RULE: Only modify governance items (roles, circles, accountabilities, domains, policies) directly during setup mode. In established workspaces, governance changes MUST go through tensions (nestr_create_tension + nestr_add_tension_part).",
|
|
583
671
|
inputSchema: {
|
|
584
672
|
type: "object",
|
|
585
673
|
properties: {
|
|
@@ -635,7 +723,7 @@ export const toolDefinitions = [
|
|
|
635
723
|
},
|
|
636
724
|
{
|
|
637
725
|
name: "nestr_delete_nest",
|
|
638
|
-
description: "Delete a nest (use with caution)",
|
|
726
|
+
description: "Delete a nest (use with caution). GOVERNANCE RULE: To remove governance items (roles, accountabilities, domains, policies) in established workspaces, use nestr_remove_tension_part to propose deletion through the consent process instead.",
|
|
639
727
|
inputSchema: {
|
|
640
728
|
type: "object",
|
|
641
729
|
properties: {
|
|
@@ -647,12 +735,12 @@ export const toolDefinitions = [
|
|
|
647
735
|
},
|
|
648
736
|
{
|
|
649
737
|
name: "nestr_add_comment",
|
|
650
|
-
description: "Add a comment to a nest.
|
|
738
|
+
description: "Add a comment to a nest. Supports @mentions using the format @{userId|email|circle|everyone}: @{userId} mentions by user ID, @{email} mentions by any email the user is registered with in Nestr, @{circle} notifies all role fillers in the nearest ancestor circle, @{everyone} is available in the UI but not yet via the API.",
|
|
651
739
|
inputSchema: {
|
|
652
740
|
type: "object",
|
|
653
741
|
properties: {
|
|
654
742
|
nestId: { type: "string", description: "Nest ID to comment on" },
|
|
655
|
-
body: { type: "string", description: "Comment text (supports HTML and @mentions)" },
|
|
743
|
+
body: { type: "string", description: "Comment text (supports HTML and @mentions: @{userId}, @{email}, @{circle})" },
|
|
656
744
|
},
|
|
657
745
|
required: ["nestId", "body"],
|
|
658
746
|
},
|
|
@@ -660,12 +748,12 @@ export const toolDefinitions = [
|
|
|
660
748
|
},
|
|
661
749
|
{
|
|
662
750
|
name: "nestr_update_comment",
|
|
663
|
-
description: "Update an existing comment's text.",
|
|
751
|
+
description: "Update an existing comment's text. Supports @mentions using the format @{userId|email|circle|everyone}: @{userId} mentions by user ID, @{email} mentions by any email the user is registered with in Nestr, @{circle} notifies all role fillers in the nearest ancestor circle, @{everyone} is available in the UI but not yet via the API.",
|
|
664
752
|
inputSchema: {
|
|
665
753
|
type: "object",
|
|
666
754
|
properties: {
|
|
667
755
|
commentId: { type: "string", description: "Comment ID to update" },
|
|
668
|
-
body: { type: "string", description: "Updated comment text (supports HTML and @mentions)" },
|
|
756
|
+
body: { type: "string", description: "Updated comment text (supports HTML and @mentions: @{userId}, @{email}, @{circle})" },
|
|
669
757
|
},
|
|
670
758
|
required: ["commentId", "body"],
|
|
671
759
|
},
|
|
@@ -1542,8 +1630,9 @@ async function _handleToolCall(client, name, args) {
|
|
|
1542
1630
|
const nest = await client.getNest(parsed.nestId, {
|
|
1543
1631
|
cleanText: true,
|
|
1544
1632
|
fieldsMetaData: parsed.fieldsMetaData,
|
|
1633
|
+
hints: parsed.hints !== false,
|
|
1545
1634
|
});
|
|
1546
|
-
return formatResult(nest);
|
|
1635
|
+
return formatResult(enrichHints(nest));
|
|
1547
1636
|
}
|
|
1548
1637
|
case "nestr_get_nest_children": {
|
|
1549
1638
|
const parsed = schemas.getNestChildren.parse(args);
|
|
@@ -1551,8 +1640,9 @@ async function _handleToolCall(client, name, args) {
|
|
|
1551
1640
|
limit: parsed.limit,
|
|
1552
1641
|
page: parsed.page,
|
|
1553
1642
|
cleanText: true,
|
|
1643
|
+
hints: parsed.hints !== false,
|
|
1554
1644
|
});
|
|
1555
|
-
return formatResult(completableResponse(compactResponse(children), "children", parsed._listTitle || "Sub-items"));
|
|
1645
|
+
return formatResult(completableResponse(compactResponse(enrichHints(children)), "children", parsed._listTitle || "Sub-items"));
|
|
1556
1646
|
}
|
|
1557
1647
|
case "nestr_create_nest": {
|
|
1558
1648
|
const parsed = schemas.createNest.parse(args);
|
|
@@ -1860,17 +1950,30 @@ async function _handleToolCall(client, name, args) {
|
|
|
1860
1950
|
case "nestr_get_me": {
|
|
1861
1951
|
const parsed = schemas.getMe.parse(args);
|
|
1862
1952
|
try {
|
|
1863
|
-
|
|
1953
|
+
let user = await client.getCurrentUser({
|
|
1864
1954
|
fullWorkspaces: parsed.fullWorkspaces,
|
|
1865
1955
|
});
|
|
1866
|
-
|
|
1956
|
+
// Guard against oversized responses (e.g. many workspaces with large adminUsers arrays).
|
|
1957
|
+
// If the serialized user exceeds 50KB, retry without fullWorkspaces to avoid
|
|
1958
|
+
// blowing past MCP client token limits.
|
|
1959
|
+
const MAX_USER_RESPONSE_BYTES = 50_000;
|
|
1960
|
+
let droppedFullWorkspaces = false;
|
|
1961
|
+
if (parsed.fullWorkspaces && JSON.stringify(user).length > MAX_USER_RESPONSE_BYTES) {
|
|
1962
|
+
user = await client.getCurrentUser({ fullWorkspaces: false });
|
|
1963
|
+
droppedFullWorkspaces = true;
|
|
1964
|
+
}
|
|
1965
|
+
const result = {
|
|
1867
1966
|
authMode: "oauth",
|
|
1868
1967
|
user,
|
|
1869
1968
|
mode: user.bot ? "role-filler" : "assistant",
|
|
1870
1969
|
hint: user.bot
|
|
1871
1970
|
? "You are a bot energizing roles. You have no authority as an agent — only through the roles you fill. Act autonomously within your roles' accountabilities. Process tensions proactively."
|
|
1872
1971
|
: "You are assisting a human who energizes roles. Defer to them for decisions. Help them articulate tensions and navigate governance.",
|
|
1873
|
-
}
|
|
1972
|
+
};
|
|
1973
|
+
if (droppedFullWorkspaces) {
|
|
1974
|
+
result.warning = "fullWorkspaces was dropped because the response exceeded the size limit. Use nestr_list_workspaces to browse workspaces individually.";
|
|
1975
|
+
}
|
|
1976
|
+
return formatResult(result);
|
|
1874
1977
|
}
|
|
1875
1978
|
catch (err) {
|
|
1876
1979
|
// If the error is from the tokenProvider (expired OAuth session),
|