@ziggs-ai/ziggs-mcp 0.1.24 → 0.1.26
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 +2 -2
- package/dist/pendingDecisions.d.ts +28 -5
- package/dist/pendingDecisions.js +116 -19
- package/dist/toolError.d.ts +25 -0
- package/dist/toolError.js +81 -0
- package/dist/tools.js +115 -14
- package/dist/trustTools.js +44 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (stdio) server for **Claude Code**, **Cursor**, and other MCP hosts.
|
|
4
4
|
|
|
5
|
-
**In scope:** chat, agreements, scope, context discovery/reads, artifacts.
|
|
6
|
-
**Out of scope (by design):** agent `transfer`,
|
|
5
|
+
**In scope:** chat, agreements (service and hire, direct or published), scope, context discovery/reads, artifacts.
|
|
6
|
+
**Out of scope (by design):** agent `transfer`, payment grants / bounded spend.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import type { InboxEnvelope, Task } from '@ziggs-ai/api-client';
|
|
2
|
-
export type PendingDecisionKind = 'proposal' | 'link_request';
|
|
2
|
+
export type PendingDecisionKind = 'proposal' | 'link_request' | 'mcp_server_request';
|
|
3
3
|
export interface PendingDecisionItem {
|
|
4
4
|
kind: PendingDecisionKind;
|
|
5
|
+
/** Agreement id for proposals; requestId for link / MCP server requests. */
|
|
5
6
|
agreementId: string;
|
|
6
7
|
title: string;
|
|
7
8
|
subtitle: string | null;
|
|
8
9
|
proposedAt: string | null;
|
|
9
10
|
proposedAtLabel: string | null;
|
|
10
11
|
appUrl: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Null for mcp_server_request — connecting a server (OAuth) happens in the
|
|
14
|
+
* browser at /app/settings/connections; there is no MCP respond tool.
|
|
15
|
+
*/
|
|
16
|
+
respondApprove: string | null;
|
|
17
|
+
respondReject: string | null;
|
|
18
|
+
sayApprove: string | null;
|
|
19
|
+
sayReject: string | null;
|
|
15
20
|
}
|
|
16
21
|
export interface ActiveWorkItem {
|
|
17
22
|
taskId: string;
|
|
@@ -26,11 +31,28 @@ export interface ActiveWorkItem {
|
|
|
26
31
|
export declare function resolveWebAppOrigin(webUrl?: string | null): string;
|
|
27
32
|
export declare function agreementAppUrl(origin: string, agreementId: string): string;
|
|
28
33
|
export declare function agreementsListAppUrl(origin: string): string;
|
|
34
|
+
/** Where the human connects MCP servers and grants tools (ZIG-686). */
|
|
35
|
+
export declare function connectionsSettingsAppUrl(origin: string): string;
|
|
29
36
|
export declare function buildPendingDecisionItems(inbox: InboxEnvelope, webOrigin: string): PendingDecisionItem[];
|
|
37
|
+
/**
|
|
38
|
+
* ZIG-658: keep only tasks this delegate (or its principal) is expected to
|
|
39
|
+
* execute. The backend's GET /tasks returns every task reachable in the org,
|
|
40
|
+
* so the session card must not present all of them as "your work".
|
|
41
|
+
*
|
|
42
|
+
* A task is "for me" when:
|
|
43
|
+
* - it is explicitly assigned (`assigneeId`) to one of my ids, or
|
|
44
|
+
* - it has no explicit assignee and one of my ids sits on the executing side
|
|
45
|
+
* of the task/agreement (executor, agent, provider, providerAgent,
|
|
46
|
+
* proposedTo).
|
|
47
|
+
* Tasks with no assignee and no readable agreement parties are excluded —
|
|
48
|
+
* "can't tell" must not render as "yours".
|
|
49
|
+
*/
|
|
50
|
+
export declare function filterTasksForDelegate(tasks: Task[], selfIds: ReadonlySet<string>): Task[];
|
|
30
51
|
export declare function buildActiveWorkItems(tasks: Task[], webOrigin: string): ActiveWorkItem[];
|
|
31
52
|
export declare function buildDecisionChatCard(items: PendingDecisionItem[], opts: {
|
|
32
53
|
truncatedProposals?: number;
|
|
33
54
|
truncatedConnectionRequests?: number;
|
|
55
|
+
truncatedMcpServerRequests?: number;
|
|
34
56
|
agreementsListAppUrl?: string;
|
|
35
57
|
}): string;
|
|
36
58
|
export declare function buildWorkChatCard(work: ActiveWorkItem[], listUrl?: string): string;
|
|
@@ -38,6 +60,7 @@ export declare function buildWorkChatCard(work: ActiveWorkItem[], listUrl?: stri
|
|
|
38
60
|
export declare function buildSessionChatCard(decisions: PendingDecisionItem[], work: ActiveWorkItem[], opts: {
|
|
39
61
|
truncatedProposals?: number;
|
|
40
62
|
truncatedConnectionRequests?: number;
|
|
63
|
+
truncatedMcpServerRequests?: number;
|
|
41
64
|
agreementsListAppUrl?: string;
|
|
42
65
|
}): string;
|
|
43
66
|
/** Structured session payload for MCP tools (ZIG-625 + active work). */
|
package/dist/pendingDecisions.js
CHANGED
|
@@ -9,6 +9,10 @@ export function agreementAppUrl(origin, agreementId) {
|
|
|
9
9
|
export function agreementsListAppUrl(origin) {
|
|
10
10
|
return `${origin}/app/agreements`;
|
|
11
11
|
}
|
|
12
|
+
/** Where the human connects MCP servers and grants tools (ZIG-686). */
|
|
13
|
+
export function connectionsSettingsAppUrl(origin) {
|
|
14
|
+
return `${origin}/app/settings/connections`;
|
|
15
|
+
}
|
|
12
16
|
function truncateText(text, max = TITLE_MAX) {
|
|
13
17
|
const oneLine = text.replace(/\s+/g, ' ').trim();
|
|
14
18
|
if (oneLine.length <= max)
|
|
@@ -75,6 +79,23 @@ function linkToItem(c, origin) {
|
|
|
75
79
|
sayReject: `reject link ${id}`,
|
|
76
80
|
};
|
|
77
81
|
}
|
|
82
|
+
function mcpServerRequestToItem(r, origin) {
|
|
83
|
+
const tools = r.tools?.length ? r.tools.join(', ') : 'no tools listed';
|
|
84
|
+
const reason = r.reason?.trim() || null;
|
|
85
|
+
return {
|
|
86
|
+
kind: 'mcp_server_request',
|
|
87
|
+
agreementId: r.requestId,
|
|
88
|
+
title: truncateText(`Connect MCP server · ${r.serverUrl}`),
|
|
89
|
+
subtitle: truncateText(reason ? `${reason} — tools: ${tools}` : `Tools: ${tools}`, 160),
|
|
90
|
+
proposedAt: r.requestedAt,
|
|
91
|
+
proposedAtLabel: formatWhen(r.requestedAt),
|
|
92
|
+
appUrl: connectionsSettingsAppUrl(origin),
|
|
93
|
+
respondApprove: null,
|
|
94
|
+
respondReject: null,
|
|
95
|
+
sayApprove: null,
|
|
96
|
+
sayReject: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
78
99
|
export function buildPendingDecisionItems(inbox, webOrigin) {
|
|
79
100
|
const items = [];
|
|
80
101
|
for (const p of inbox.proposalsAwaitingMe ?? []) {
|
|
@@ -83,8 +104,37 @@ export function buildPendingDecisionItems(inbox, webOrigin) {
|
|
|
83
104
|
for (const c of inbox.connectionRequestsAwaitingMe ?? []) {
|
|
84
105
|
items.push(linkToItem(c, webOrigin));
|
|
85
106
|
}
|
|
107
|
+
for (const r of inbox.mcpServerRequestsAwaitingMe ?? []) {
|
|
108
|
+
items.push(mcpServerRequestToItem(r, webOrigin));
|
|
109
|
+
}
|
|
86
110
|
return items;
|
|
87
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* ZIG-658: keep only tasks this delegate (or its principal) is expected to
|
|
114
|
+
* execute. The backend's GET /tasks returns every task reachable in the org,
|
|
115
|
+
* so the session card must not present all of them as "your work".
|
|
116
|
+
*
|
|
117
|
+
* A task is "for me" when:
|
|
118
|
+
* - it is explicitly assigned (`assigneeId`) to one of my ids, or
|
|
119
|
+
* - it has no explicit assignee and one of my ids sits on the executing side
|
|
120
|
+
* of the task/agreement (executor, agent, provider, providerAgent,
|
|
121
|
+
* proposedTo).
|
|
122
|
+
* Tasks with no assignee and no readable agreement parties are excluded —
|
|
123
|
+
* "can't tell" must not render as "yours".
|
|
124
|
+
*/
|
|
125
|
+
export function filterTasksForDelegate(tasks, selfIds) {
|
|
126
|
+
const mine = (id) => typeof id === 'string' && id.length > 0 && selfIds.has(id);
|
|
127
|
+
return tasks.filter((t) => {
|
|
128
|
+
if (t.assigneeId)
|
|
129
|
+
return mine(t.assigneeId);
|
|
130
|
+
if (mine(t.executorId) || mine(t.agentId))
|
|
131
|
+
return true;
|
|
132
|
+
const p = t.agreement?.parties;
|
|
133
|
+
if (!p)
|
|
134
|
+
return false;
|
|
135
|
+
return mine(p.provider) || mine(p.providerAgent) || mine(p.proposedTo);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
88
138
|
export function buildActiveWorkItems(tasks, webOrigin) {
|
|
89
139
|
return tasks
|
|
90
140
|
.filter((t) => t.state === 'active')
|
|
@@ -105,17 +155,26 @@ export function buildActiveWorkItems(tasks, webOrigin) {
|
|
|
105
155
|
});
|
|
106
156
|
}
|
|
107
157
|
function kindLabel(kind) {
|
|
108
|
-
|
|
158
|
+
if (kind === 'proposal')
|
|
159
|
+
return 'Agreement proposal';
|
|
160
|
+
if (kind === 'link_request')
|
|
161
|
+
return 'Agent link request';
|
|
162
|
+
return 'MCP server request';
|
|
109
163
|
}
|
|
110
164
|
function kindHint(kind) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
165
|
+
if (kind === 'proposal') {
|
|
166
|
+
return 'Someone proposed work or terms — your approval opens or rejects it.';
|
|
167
|
+
}
|
|
168
|
+
if (kind === 'link_request') {
|
|
169
|
+
return 'Another agent wants to link — your approval enables cross-org reach.';
|
|
170
|
+
}
|
|
171
|
+
return 'Your agent asks you to connect an MCP server and grant it the listed tools — connect (OAuth) or reject in the browser.';
|
|
114
172
|
}
|
|
115
173
|
function buildDecisionSection(items, opts) {
|
|
116
174
|
const truncatedProposals = opts.truncatedProposals ?? 0;
|
|
117
175
|
const truncatedConnectionRequests = opts.truncatedConnectionRequests ?? 0;
|
|
118
|
-
const
|
|
176
|
+
const truncatedMcpServerRequests = opts.truncatedMcpServerRequests ?? 0;
|
|
177
|
+
const truncated = truncatedProposals + truncatedConnectionRequests + truncatedMcpServerRequests;
|
|
119
178
|
if (!items.length && truncated === 0)
|
|
120
179
|
return [];
|
|
121
180
|
const lines = [];
|
|
@@ -136,18 +195,27 @@ function buildDecisionSection(items, opts) {
|
|
|
136
195
|
lines.push(`> ${item.subtitle}`);
|
|
137
196
|
}
|
|
138
197
|
lines.push('');
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
198
|
+
if (item.sayApprove && item.respondApprove && item.sayReject && item.respondReject) {
|
|
199
|
+
lines.push(`[Review in Ziggs →](${item.appUrl})`);
|
|
200
|
+
lines.push('');
|
|
201
|
+
lines.push('| You say in chat | What the agent runs |');
|
|
202
|
+
lines.push('|:----------------|:--------------------|');
|
|
203
|
+
lines.push(`| \`${item.sayApprove}\` | \`${item.respondApprove}\` |`);
|
|
204
|
+
lines.push(`| \`${item.sayReject}\` | \`${item.respondReject}\` |`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// Browser-only decision (mcp_server_request): connecting a server runs
|
|
208
|
+
// OAuth consent — no MCP tool can approve it from chat.
|
|
209
|
+
lines.push(`[Connect or reject in Ziggs →](${item.appUrl})`);
|
|
210
|
+
lines.push('');
|
|
211
|
+
lines.push('_This one is decided in the browser — nothing to approve from chat._');
|
|
212
|
+
}
|
|
145
213
|
lines.push('');
|
|
146
214
|
}
|
|
147
215
|
if (truncated > 0) {
|
|
148
216
|
lines.push('---');
|
|
149
217
|
lines.push('');
|
|
150
|
-
lines.push(`_+${truncated} more pending (${truncatedProposals} proposal(s), ${truncatedConnectionRequests} link(s)) — not listed here._`);
|
|
218
|
+
lines.push(`_+${truncated} more pending (${truncatedProposals} proposal(s), ${truncatedConnectionRequests} link(s), ${truncatedMcpServerRequests} MCP server request(s)) — not listed here._`);
|
|
151
219
|
lines.push('');
|
|
152
220
|
}
|
|
153
221
|
return lines;
|
|
@@ -188,11 +256,13 @@ function buildWorkSection(work, startIndex = 1) {
|
|
|
188
256
|
export function buildDecisionChatCard(items, opts) {
|
|
189
257
|
const truncatedProposals = opts.truncatedProposals ?? 0;
|
|
190
258
|
const truncatedConnectionRequests = opts.truncatedConnectionRequests ?? 0;
|
|
191
|
-
const
|
|
259
|
+
const truncatedMcpServerRequests = opts.truncatedMcpServerRequests ?? 0;
|
|
260
|
+
const truncated = truncatedProposals + truncatedConnectionRequests + truncatedMcpServerRequests;
|
|
192
261
|
if (!items.length && truncated === 0)
|
|
193
262
|
return '';
|
|
194
263
|
const proposals = items.filter((i) => i.kind === 'proposal').length;
|
|
195
264
|
const links = items.filter((i) => i.kind === 'link_request').length;
|
|
265
|
+
const mcpRequests = items.filter((i) => i.kind === 'mcp_server_request').length;
|
|
196
266
|
const listedTotal = items.length + truncated;
|
|
197
267
|
const lines = [
|
|
198
268
|
`### 🔔 Ziggs — **${listedTotal}** ${listedTotal === 1 ? 'item needs' : 'items need'} your decision`,
|
|
@@ -201,9 +271,12 @@ export function buildDecisionChatCard(items, opts) {
|
|
|
201
271
|
'|:--|--:|',
|
|
202
272
|
`| Agreement proposals | **${proposals}** |`,
|
|
203
273
|
`| Agent link requests | **${links}** |`,
|
|
274
|
+
...(mcpRequests + truncatedMcpServerRequests > 0
|
|
275
|
+
? [`| MCP server requests | **${mcpRequests}** |`]
|
|
276
|
+
: []),
|
|
204
277
|
...(truncated > 0 ? [`| _Not shown (inbox cap)_ | _+${truncated}_ |`] : []),
|
|
205
278
|
'',
|
|
206
|
-
'> **Heads-up:** Ziggs MCP is pull-only — nothing pops up in Cursor until inbox is checked. **You** approve or reject in this chat; the agent calls `ziggs_respond_to_agreement` only after you say so.',
|
|
279
|
+
'> **Heads-up:** Ziggs MCP is pull-only — nothing pops up in Cursor until inbox is checked. **You** approve or reject in this chat; the agent calls `ziggs_respond_to_agreement` only after you say so. MCP server requests are the exception — those are connected or rejected in the browser.',
|
|
207
280
|
'',
|
|
208
281
|
...buildDecisionSection(items, opts),
|
|
209
282
|
];
|
|
@@ -232,12 +305,15 @@ export function buildWorkChatCard(work, listUrl) {
|
|
|
232
305
|
export function buildSessionChatCard(decisions, work, opts) {
|
|
233
306
|
const truncatedProposals = opts.truncatedProposals ?? 0;
|
|
234
307
|
const truncatedConnectionRequests = opts.truncatedConnectionRequests ?? 0;
|
|
235
|
-
const
|
|
308
|
+
const truncatedMcpServerRequests = opts.truncatedMcpServerRequests ?? 0;
|
|
309
|
+
const truncated = truncatedProposals + truncatedConnectionRequests + truncatedMcpServerRequests;
|
|
236
310
|
const decisionListed = decisions.length + truncated;
|
|
237
311
|
const workCount = work.length;
|
|
238
312
|
const total = decisionListed + workCount;
|
|
239
313
|
if (total === 0)
|
|
240
314
|
return '';
|
|
315
|
+
const mcpRequestCount = decisions.filter((d) => d.kind === 'mcp_server_request').length +
|
|
316
|
+
truncatedMcpServerRequests;
|
|
241
317
|
const lines = [
|
|
242
318
|
`### 🔔 Ziggs — **${total}** ${total === 1 ? 'thing needs' : 'things need'} you`,
|
|
243
319
|
'',
|
|
@@ -248,11 +324,16 @@ export function buildSessionChatCard(decisions, work, opts) {
|
|
|
248
324
|
`| Approve / reject | **${decisionListed}** |`,
|
|
249
325
|
`| — proposals | ${decisions.filter((d) => d.kind === 'proposal').length}${truncatedProposals ? ` (+${truncatedProposals} hidden)` : ''} |`,
|
|
250
326
|
`| — link requests | ${decisions.filter((d) => d.kind === 'link_request').length}${truncatedConnectionRequests ? ` (+${truncatedConnectionRequests} hidden)` : ''} |`,
|
|
327
|
+
...(mcpRequestCount > 0
|
|
328
|
+
? [
|
|
329
|
+
`| — MCP server requests | ${decisions.filter((d) => d.kind === 'mcp_server_request').length}${truncatedMcpServerRequests ? ` (+${truncatedMcpServerRequests} hidden)` : ''} |`,
|
|
330
|
+
]
|
|
331
|
+
: []),
|
|
251
332
|
]
|
|
252
333
|
: []),
|
|
253
334
|
...(workCount > 0 ? [`| Active tasks (your work) | **${workCount}** |`] : []),
|
|
254
335
|
'',
|
|
255
|
-
'> **Heads-up:** MCP is pull-only — check at session start. **Decisions:** you say approve/reject. **Tasks:** say `work on <taskId>` — the agent implements and reports on Ziggs.',
|
|
336
|
+
'> **Heads-up:** MCP is pull-only — check at session start. **Decisions:** you say approve/reject (MCP server requests are connected in the browser instead). **Tasks:** say `work on <taskId>` — the agent implements and reports on Ziggs.',
|
|
256
337
|
'',
|
|
257
338
|
];
|
|
258
339
|
let sectionIndex = 1;
|
|
@@ -275,13 +356,18 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
|
|
|
275
356
|
const activeWork = buildActiveWorkItems(opts?.activeTasks ?? [], webOrigin);
|
|
276
357
|
const truncatedProposals = inbox.truncatedProposals ?? 0;
|
|
277
358
|
const truncatedConnectionRequests = inbox.truncatedConnectionRequests ?? 0;
|
|
278
|
-
const
|
|
359
|
+
const truncatedMcpServerRequests = inbox.truncatedMcpServerRequests ?? 0;
|
|
360
|
+
const pendingCount = decisions.length +
|
|
361
|
+
truncatedProposals +
|
|
362
|
+
truncatedConnectionRequests +
|
|
363
|
+
truncatedMcpServerRequests;
|
|
279
364
|
const activeWorkCount = activeWork.length;
|
|
280
365
|
const actionCount = pendingCount + activeWorkCount;
|
|
281
366
|
const listUrl = agreementsListAppUrl(webOrigin);
|
|
282
367
|
const cardOpts = {
|
|
283
368
|
truncatedProposals,
|
|
284
369
|
truncatedConnectionRequests,
|
|
370
|
+
truncatedMcpServerRequests,
|
|
285
371
|
agreementsListAppUrl: listUrl,
|
|
286
372
|
};
|
|
287
373
|
const decisionChatCard = buildDecisionChatCard(decisions, cardOpts);
|
|
@@ -289,6 +375,7 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
|
|
|
289
375
|
const sessionChatCard = buildSessionChatCard(decisions, activeWork, cardOpts);
|
|
290
376
|
const proposalCount = decisions.filter((d) => d.kind === 'proposal').length;
|
|
291
377
|
const linkCount = decisions.filter((d) => d.kind === 'link_request').length;
|
|
378
|
+
const mcpServerRequestCount = decisions.filter((d) => d.kind === 'mcp_server_request').length;
|
|
292
379
|
const instruction = actionCount > 0
|
|
293
380
|
? 'Paste sessionChatCard at the top of your reply. Decisions: wait for explicit approve/reject before ziggs_respond_to_agreement. Tasks: when the human says work on <taskId>, read context and implement.'
|
|
294
381
|
: 'No pending decisions or active tasks — continue with ziggs_inbox for scope news.';
|
|
@@ -302,15 +389,20 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
|
|
|
302
389
|
summary: {
|
|
303
390
|
proposals: proposalCount + truncatedProposals,
|
|
304
391
|
linkRequests: linkCount + truncatedConnectionRequests,
|
|
392
|
+
mcpServerRequests: mcpServerRequestCount + truncatedMcpServerRequests,
|
|
305
393
|
activeTasks: activeWorkCount,
|
|
306
394
|
listed: decisions.length,
|
|
307
|
-
truncated: truncatedProposals +
|
|
395
|
+
truncated: truncatedProposals +
|
|
396
|
+
truncatedConnectionRequests +
|
|
397
|
+
truncatedMcpServerRequests,
|
|
308
398
|
},
|
|
309
399
|
decisions,
|
|
310
400
|
activeWork,
|
|
311
401
|
truncatedProposals,
|
|
312
402
|
truncatedConnectionRequests,
|
|
403
|
+
truncatedMcpServerRequests,
|
|
313
404
|
agreementsListAppUrl: listUrl,
|
|
405
|
+
connectionsSettingsAppUrl: connectionsSettingsAppUrl(webOrigin),
|
|
314
406
|
...(decisionChatCard ? { decisionChatCard } : {}),
|
|
315
407
|
...(workChatCard ? { workChatCard } : {}),
|
|
316
408
|
...(sessionChatCard ? { sessionChatCard } : {}),
|
|
@@ -327,7 +419,12 @@ export function buildPendingNextActions(decisions, work = []) {
|
|
|
327
419
|
actions.push('Wait for the human to say approve/reject (e.g. `approve agr-…`) — do not auto-respond.');
|
|
328
420
|
}
|
|
329
421
|
for (const d of decisions.slice(0, 4)) {
|
|
330
|
-
|
|
422
|
+
if (d.sayApprove && d.sayReject) {
|
|
423
|
+
actions.push(`${kindLabel(d.kind)} ${d.agreementId}: \`${d.sayApprove}\` or \`${d.sayReject}\``);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
actions.push(`${kindLabel(d.kind)} ${d.agreementId}: connect or reject in the browser → ${d.appUrl}`);
|
|
427
|
+
}
|
|
331
428
|
}
|
|
332
429
|
for (const w of work.slice(0, 4)) {
|
|
333
430
|
actions.push(`Active task ${w.taskId}: human says \`${w.sayWork}\` to start implementation.`);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-667 — one error surface for every MCP tool.
|
|
3
|
+
*
|
|
4
|
+
* Raw client exceptions read like
|
|
5
|
+
* `ContextReadClient.read messages 403 {"error":"not authorized for this scope"}`
|
|
6
|
+
* — an internal class.method name, a raw HTTP status, and a raw backend body.
|
|
7
|
+
* LLM callers stall on stack-trace prose; they recover from errors they can
|
|
8
|
+
* parse. This module strips the internals, keeps a stable machine-readable
|
|
9
|
+
* code, and for authorization denials says what to do next.
|
|
10
|
+
*/
|
|
11
|
+
export interface ToolErrorShape {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
hint?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Classify a raw client error message into a stable shape. */
|
|
17
|
+
export declare function classifyToolError(rawMessage: string): ToolErrorShape;
|
|
18
|
+
/** MCP tool error result: machine-readable code + cleaned message (+ hint). */
|
|
19
|
+
export declare function toolError(message: string): {
|
|
20
|
+
content: {
|
|
21
|
+
type: "text";
|
|
22
|
+
text: string;
|
|
23
|
+
}[];
|
|
24
|
+
isError: boolean;
|
|
25
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-667 — one error surface for every MCP tool.
|
|
3
|
+
*
|
|
4
|
+
* Raw client exceptions read like
|
|
5
|
+
* `ContextReadClient.read messages 403 {"error":"not authorized for this scope"}`
|
|
6
|
+
* — an internal class.method name, a raw HTTP status, and a raw backend body.
|
|
7
|
+
* LLM callers stall on stack-trace prose; they recover from errors they can
|
|
8
|
+
* parse. This module strips the internals, keeps a stable machine-readable
|
|
9
|
+
* code, and for authorization denials says what to do next.
|
|
10
|
+
*/
|
|
11
|
+
const CLIENT_PREFIX = /^[A-Z][A-Za-z0-9]*Client\.[A-Za-z0-9_]+\s+/;
|
|
12
|
+
const HTTP_STATUS = /(?:^|\s)([1-5]\d{2})(?=\s|$)/;
|
|
13
|
+
const SCOPE_DENIED_HINT = 'You are not authorized for this scope. To get access: ask the counterparty ' +
|
|
14
|
+
'to issue you a context grant (they run ziggs_issue_grant), or request a ' +
|
|
15
|
+
'bilateral link first (ziggs_request_link). Check what you can already ' +
|
|
16
|
+
'reach with ziggs_discover_context / ziggs_get_scope.';
|
|
17
|
+
function codeForStatus(status) {
|
|
18
|
+
if (status === 401)
|
|
19
|
+
return 'NOT_AUTHENTICATED';
|
|
20
|
+
if (status === 403)
|
|
21
|
+
return 'NOT_AUTHORIZED';
|
|
22
|
+
if (status === 404)
|
|
23
|
+
return 'NOT_FOUND';
|
|
24
|
+
if (status === 409)
|
|
25
|
+
return 'CONFLICT';
|
|
26
|
+
if (status === 429)
|
|
27
|
+
return 'RATE_LIMITED';
|
|
28
|
+
if (status >= 500)
|
|
29
|
+
return 'UPSTREAM_ERROR';
|
|
30
|
+
if (status >= 400)
|
|
31
|
+
return 'BAD_REQUEST';
|
|
32
|
+
return 'TOOL_ERROR';
|
|
33
|
+
}
|
|
34
|
+
/** Pull a human reason out of an embedded backend JSON body, if any. */
|
|
35
|
+
function extractBodyReason(raw) {
|
|
36
|
+
const start = raw.indexOf('{');
|
|
37
|
+
if (start === -1)
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(raw.slice(start));
|
|
41
|
+
const reason = parsed['error'] ?? parsed['message'];
|
|
42
|
+
return typeof reason === 'string' && reason.trim() ? reason.trim() : null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Classify a raw client error message into a stable shape. */
|
|
49
|
+
export function classifyToolError(rawMessage) {
|
|
50
|
+
const cleaned = rawMessage.replace(CLIENT_PREFIX, '').trim();
|
|
51
|
+
const statusMatch = HTTP_STATUS.exec(cleaned);
|
|
52
|
+
const status = statusMatch ? Number(statusMatch[1]) : null;
|
|
53
|
+
const bodyReason = extractBodyReason(cleaned);
|
|
54
|
+
if (status === null) {
|
|
55
|
+
return { code: 'TOOL_ERROR', message: cleaned || rawMessage };
|
|
56
|
+
}
|
|
57
|
+
const code = codeForStatus(status);
|
|
58
|
+
// Prefer the backend's own reason over the transport framing.
|
|
59
|
+
const message = bodyReason ?? cleaned;
|
|
60
|
+
// Context-scope denials (the backend says "…for this scope") get the scope
|
|
61
|
+
// code and a recovery path. Other 403s (connection grants, party checks)
|
|
62
|
+
// keep the generic code — their fixes live in other domains.
|
|
63
|
+
if (code === 'NOT_AUTHORIZED' && /\bscope\b/i.test(message)) {
|
|
64
|
+
return {
|
|
65
|
+
code: 'NOT_AUTHORIZED_FOR_SCOPE',
|
|
66
|
+
message,
|
|
67
|
+
hint: SCOPE_DENIED_HINT,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { code, message };
|
|
71
|
+
}
|
|
72
|
+
/** MCP tool error result: machine-readable code + cleaned message (+ hint). */
|
|
73
|
+
export function toolError(message) {
|
|
74
|
+
const shape = classifyToolError(message);
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{ type: 'text', text: JSON.stringify({ error: shape }, null, 2) },
|
|
78
|
+
],
|
|
79
|
+
isError: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -4,9 +4,10 @@ import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDi
|
|
|
4
4
|
import { decodeOperatorKeyClaims } from './operatorKey.js';
|
|
5
5
|
import { registerTrustTools } from './trustTools.js';
|
|
6
6
|
import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
|
|
7
|
-
import { formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
|
|
7
|
+
import { connectionsSettingsAppUrl, filterTasksForDelegate, formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
|
|
8
8
|
import { PROTOCOL } from './protocol/delegateProtocol.js';
|
|
9
9
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
10
|
+
import { toolError } from './toolError.js';
|
|
10
11
|
// ZIG-557: the protocol sentences (loop / ack / humanAttention) are sourced
|
|
11
12
|
// from the shared const so this description can't drift from SKILL / server
|
|
12
13
|
// instructions / .cursorrules.
|
|
@@ -48,12 +49,6 @@ function textResult(data) {
|
|
|
48
49
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
function toolError(message) {
|
|
52
|
-
return {
|
|
53
|
-
content: [{ type: 'text', text: message }],
|
|
54
|
-
isError: true,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
52
|
const scopeKindSchema = z.enum(['chat', 'agreement', 'task', 'counterparty']);
|
|
58
53
|
const contextReadTypeSchema = z.enum([
|
|
59
54
|
'messages',
|
|
@@ -84,6 +79,8 @@ async function writeArtifactStrict(creds, input) {
|
|
|
84
79
|
const body = await res.text().catch(() => '');
|
|
85
80
|
throw new Error(`POST /artifacts ${res.status} ${body.slice(0, 200)}`);
|
|
86
81
|
}
|
|
82
|
+
const body = (await res.json().catch(() => ({})));
|
|
83
|
+
return { artifactId: body.artifactId };
|
|
87
84
|
}
|
|
88
85
|
// ZIG-569 — defense-in-depth mirror of the backend leak-guard
|
|
89
86
|
// (assertProxyResponseDoesNotLeakTokens). The backend strips the *specific*
|
|
@@ -205,6 +202,64 @@ async function listConnectionsForHolder(creds) {
|
|
|
205
202
|
}
|
|
206
203
|
return parsed?.['connections'] ?? [];
|
|
207
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* ZIG-686 — agent-initiated MCP connection request: ask the principal to
|
|
207
|
+
* connect a remote MCP server and grant this agent the listed tools. Creates a
|
|
208
|
+
* pending decision the human resolves in the browser (OAuth consent or a grant
|
|
209
|
+
* from an existing connection) — never from chat.
|
|
210
|
+
*/
|
|
211
|
+
async function createMcpConnectionRequest(creds, input) {
|
|
212
|
+
const url = `${getBackendUrl()}/connections/mcp/requests`;
|
|
213
|
+
const res = await fetch(url, {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: {
|
|
216
|
+
'content-type': 'application/json',
|
|
217
|
+
Authorization: `Bearer ${creds.operatorKey}`,
|
|
218
|
+
'X-Agent-Id': creds.agentId,
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({
|
|
221
|
+
serverUrl: input.serverUrl,
|
|
222
|
+
tools: input.tools,
|
|
223
|
+
reason: input.reason,
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
const body = await res.text().catch(() => '');
|
|
227
|
+
if (!res.ok) {
|
|
228
|
+
throw new Error(`POST /connections/mcp/requests ${res.status} ${body.slice(0, 200)}`);
|
|
229
|
+
}
|
|
230
|
+
return body ? JSON.parse(body) : {};
|
|
231
|
+
}
|
|
232
|
+
/** ZIG-686 — the requests this agent made, each with status / connectionId / grantId. */
|
|
233
|
+
async function listMcpConnectionRequests(creds) {
|
|
234
|
+
const url = `${getBackendUrl()}/connections/mcp/requests`;
|
|
235
|
+
const res = await fetch(url, {
|
|
236
|
+
method: 'GET',
|
|
237
|
+
headers: {
|
|
238
|
+
Authorization: `Bearer ${creds.operatorKey}`,
|
|
239
|
+
'X-Agent-Id': creds.agentId,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
const body = await res.text().catch(() => '');
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
throw new Error(`GET /connections/mcp/requests ${res.status} ${body.slice(0, 200)}`);
|
|
245
|
+
}
|
|
246
|
+
const parsed = body ? JSON.parse(body) : null;
|
|
247
|
+
return parsed?.['requests'] ?? [];
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Ids this delegate answers for: its own agent id plus its principal's user
|
|
251
|
+
* id (operator-key ownerId / ZIGGS_OWNER_USER_ID). Used to decide which
|
|
252
|
+
* active tasks count as "your work" on session cards (ZIG-658).
|
|
253
|
+
*/
|
|
254
|
+
function delegateSelfIds(creds, cfg) {
|
|
255
|
+
const ids = new Set([creds.agentId]);
|
|
256
|
+
const ownerId = decodeOperatorKeyClaims(creds.operatorKey)?.ownerId;
|
|
257
|
+
if (ownerId)
|
|
258
|
+
ids.add(ownerId);
|
|
259
|
+
if (cfg.ZIGGS_OWNER_USER_ID)
|
|
260
|
+
ids.add(cfg.ZIGGS_OWNER_USER_ID);
|
|
261
|
+
return ids;
|
|
262
|
+
}
|
|
208
263
|
async function loadSessionActionsPayload(creds, cfg) {
|
|
209
264
|
const webOrigin = resolveWebAppOrigin(cfg.ZIGGS_WEB_URL);
|
|
210
265
|
const client = new InboxClient(creds.operatorKey, creds.agentId);
|
|
@@ -212,7 +267,7 @@ async function loadSessionActionsPayload(creds, cfg) {
|
|
|
212
267
|
let activeTasks = [];
|
|
213
268
|
try {
|
|
214
269
|
const listed = await listTasks({ state: 'active', limit: 20 }, creds);
|
|
215
|
-
activeTasks = listed.tasks ?? [];
|
|
270
|
+
activeTasks = filterTasksForDelegate(listed.tasks ?? [], delegateSelfIds(creds, cfg));
|
|
216
271
|
}
|
|
217
272
|
catch {
|
|
218
273
|
// Inbox is still useful when task listing fails.
|
|
@@ -287,7 +342,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
287
342
|
: 'Next: ziggs_inbox or ziggs_pending_decisions at session start.',
|
|
288
343
|
});
|
|
289
344
|
});
|
|
290
|
-
server.tool('ziggs_switch_org', '
|
|
345
|
+
server.tool('ziggs_switch_org', 'Switch which org this MCP OAuth session acts in without reconnecting. Existing Bearer unchanged; runtime org flips server-side. Requires confirm=true (party-identity change). Target org must be one you belong to. Call ziggs_auth_status after to verify actingOrgId.', {
|
|
291
346
|
orgId: z.string().describe('Organization id to act in'),
|
|
292
347
|
confirm: z
|
|
293
348
|
.literal(true)
|
|
@@ -433,7 +488,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
433
488
|
return toolError(e.message);
|
|
434
489
|
}
|
|
435
490
|
});
|
|
436
|
-
server.tool('ziggs_propose_agreement', 'Propose a direct
|
|
491
|
+
server.tool('ziggs_propose_agreement', 'Propose a direct agreement to one counterparty in a chat, as the payer-side delegate. engagementKind "service" (default) = one deliverable; "hire" = an ongoing engagement (same kinds ziggs_publish_offer accepts). Requires payerId (or ZIGGS_OWNER_USER_ID). price is recorded on the agreement but does not itself trigger a transfer — V1 has no real payment rail yet.', {
|
|
437
492
|
proposedTo: z.string(),
|
|
438
493
|
chatId: z.string(),
|
|
439
494
|
description: z.string(),
|
|
@@ -442,7 +497,11 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
442
497
|
.optional()
|
|
443
498
|
.describe('Human user id = payer (your userId)'),
|
|
444
499
|
price: z.number().optional().describe('Optional; does not trigger transfer by itself'),
|
|
445
|
-
|
|
500
|
+
engagementKind: z
|
|
501
|
+
.enum(['hire', 'service'])
|
|
502
|
+
.optional()
|
|
503
|
+
.describe("'service' (default) = one-off deliverable; 'hire' = ongoing engagement"),
|
|
504
|
+
}, WRITE, async ({ proposedTo, chatId, description, payerId, price, engagementKind }) => {
|
|
446
505
|
try {
|
|
447
506
|
const resolvedPayer = payerId ?? cfg.ZIGGS_OWNER_USER_ID;
|
|
448
507
|
if (!resolvedPayer) {
|
|
@@ -454,7 +513,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
454
513
|
description,
|
|
455
514
|
payerId: resolvedPayer,
|
|
456
515
|
price,
|
|
457
|
-
engagementKind: 'service',
|
|
516
|
+
engagementKind: engagementKind ?? 'service',
|
|
458
517
|
}, creds);
|
|
459
518
|
return textResult({ agreement });
|
|
460
519
|
}
|
|
@@ -562,7 +621,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
562
621
|
let activeTasks = [];
|
|
563
622
|
try {
|
|
564
623
|
const listed = await listTasks({ state: 'active', limit: 20 }, creds);
|
|
565
|
-
activeTasks = listed.tasks ?? [];
|
|
624
|
+
activeTasks = filterTasksForDelegate(listed.tasks ?? [], delegateSelfIds(creds, cfg));
|
|
566
625
|
}
|
|
567
626
|
catch {
|
|
568
627
|
// omit work card when tasks fail
|
|
@@ -649,7 +708,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
649
708
|
if ((chatId && agreementId) || (!chatId && !agreementId)) {
|
|
650
709
|
return toolError('Pass exactly one of chatId or agreementId');
|
|
651
710
|
}
|
|
652
|
-
await writeArtifactStrict(creds, {
|
|
711
|
+
const { artifactId } = await writeArtifactStrict(creds, {
|
|
653
712
|
text,
|
|
654
713
|
visibility,
|
|
655
714
|
chatId,
|
|
@@ -659,6 +718,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
659
718
|
});
|
|
660
719
|
return textResult({
|
|
661
720
|
ok: true,
|
|
721
|
+
artifactId,
|
|
662
722
|
visibility,
|
|
663
723
|
chatId,
|
|
664
724
|
agreementId,
|
|
@@ -796,5 +856,46 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
796
856
|
return toolError(e.message);
|
|
797
857
|
}
|
|
798
858
|
});
|
|
859
|
+
server.tool('ziggs_request_connection', 'Ask your principal (the human) to connect a remote MCP server and grant you the listed tools. ' +
|
|
860
|
+
'Creates a pending decision — the human connects (OAuth) or rejects it in the browser under Settings → Connections; there is no MCP tool to approve it, so tell them and link the returned approveUrl. ' +
|
|
861
|
+
'Check the outcome with ziggs_request_connection_status: a fulfilled request carries the connectionId + grantId to use with ziggs_connection_proxy.', {
|
|
862
|
+
serverUrl: z.string().describe('Remote MCP server URL (https)'),
|
|
863
|
+
tools: z
|
|
864
|
+
.array(z.string())
|
|
865
|
+
.describe("Tool names you want — become the grant's allowed_actions caveats"),
|
|
866
|
+
reason: z
|
|
867
|
+
.string()
|
|
868
|
+
.optional()
|
|
869
|
+
.describe('Plain-language reason shown to the human deciding'),
|
|
870
|
+
}, WRITE, async ({ serverUrl, tools, reason }) => {
|
|
871
|
+
try {
|
|
872
|
+
const result = await createMcpConnectionRequest(creds, {
|
|
873
|
+
serverUrl,
|
|
874
|
+
tools,
|
|
875
|
+
reason,
|
|
876
|
+
});
|
|
877
|
+
const approveUrl = connectionsSettingsAppUrl(resolveWebAppOrigin(cfg.ZIGGS_WEB_URL));
|
|
878
|
+
return textResult({
|
|
879
|
+
ok: true,
|
|
880
|
+
...result,
|
|
881
|
+
approveUrl,
|
|
882
|
+
note: 'Pending your principal\'s decision. Tell the human now (pull-only MCP has no push) and link the approveUrl — they connect or reject there. ' +
|
|
883
|
+
'Poll ziggs_request_connection_status for the outcome; fulfilled requests carry connectionId + grantId for ziggs_connection_proxy.',
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
catch (e) {
|
|
887
|
+
return toolError(e.message);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
server.tool('ziggs_request_connection_status', 'List the MCP server connection requests this agent made with ziggs_request_connection, each with status pending | fulfilled | rejected. ' +
|
|
891
|
+
'A fulfilled request carries the connectionId + grantId to feed into ziggs_connection_proxy (it also appears in ziggs_list_my_connections).', {}, READ_ONLY, async () => {
|
|
892
|
+
try {
|
|
893
|
+
const requests = await listMcpConnectionRequests(creds);
|
|
894
|
+
return textResult({ requests });
|
|
895
|
+
}
|
|
896
|
+
catch (e) {
|
|
897
|
+
return toolError(e.message);
|
|
898
|
+
}
|
|
899
|
+
});
|
|
799
900
|
registerTrustTools(server, creds, cfg);
|
|
800
901
|
}
|
package/dist/trustTools.js
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { AgentSearchClient, ContextGrantsClient, createAgreement, listAgreements, revokeAgreement, claimAgreement, addChatMember, } from '@ziggs-ai/api-client';
|
|
3
3
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
4
|
+
import { toolError } from './toolError.js';
|
|
4
5
|
function textResult(data) {
|
|
5
6
|
return {
|
|
6
7
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
7
8
|
};
|
|
8
9
|
}
|
|
9
|
-
function toolError(message) {
|
|
10
|
-
return {
|
|
11
|
-
content: [{ type: 'text', text: message }],
|
|
12
|
-
isError: true,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
10
|
const grantScopeKindSchema = z.enum(['chat', 'agreement', 'org']);
|
|
16
11
|
const contextTemporalSchema = z.enum(['from-now', 'from-start']);
|
|
17
12
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
@@ -29,9 +24,21 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
29
24
|
if (!result.success) {
|
|
30
25
|
return toolError(result.error ?? result.message ?? 'search failed');
|
|
31
26
|
}
|
|
27
|
+
if (!result.agents?.length) {
|
|
28
|
+
// ZIG-664: a bare {count: 0} reads as "discovery is down" to LLM
|
|
29
|
+
// callers — say what was searched and how to recover instead.
|
|
30
|
+
return textResult({
|
|
31
|
+
count: 0,
|
|
32
|
+
agents: [],
|
|
33
|
+
searched: ['published store', 'your org-mates', 'your linked delegates'],
|
|
34
|
+
hint: 'Zero hits means no agent profile matched these terms — discovery itself is up. ' +
|
|
35
|
+
'Matching is lexical against agent name/description/tags, so try shorter or different keywords. ' +
|
|
36
|
+
'If you already know the agent, pass its exact agent id as the query to resolve it directly.',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
32
39
|
return textResult({
|
|
33
|
-
count: result.agents
|
|
34
|
-
agents: result.agents
|
|
40
|
+
count: result.agents.length,
|
|
41
|
+
agents: result.agents,
|
|
35
42
|
});
|
|
36
43
|
}
|
|
37
44
|
catch (e) {
|
|
@@ -197,13 +204,38 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
197
204
|
return toolError(e.message);
|
|
198
205
|
}
|
|
199
206
|
});
|
|
200
|
-
server.tool('ziggs_list_links', 'List link agreements for this delegate — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_list_my_connections for those) (GET /agreements?engagementKind=link). Each item
|
|
207
|
+
server.tool('ziggs_list_links', 'List link agreements for this delegate — bilateral agent-to-agent trust relationships, NOT third-party service connections (see ziggs_list_my_connections for those) (GET /agreements?engagementKind=link). Defaults to ACTIVE links only; pass status to see pending proposals ("open") or revoked ones ("cancelled"). Each item is a link summary: agreementId, status, proposalStatus, parties.creatorAgent (requester), parties.providerAgent (target), parties.proposedTo (target owner). Approve pending links via ziggs_respond_to_agreement.', {
|
|
208
|
+
status: z
|
|
209
|
+
.enum(['active', 'open', 'cancelled', 'all'])
|
|
210
|
+
.optional()
|
|
211
|
+
.describe('active (default) = established links; open = pending proposals/invites awaiting approval or claim; cancelled = revoked; all = every link regardless of status'),
|
|
212
|
+
}, READ_ONLY, async ({ status }) => {
|
|
201
213
|
try {
|
|
202
|
-
const
|
|
214
|
+
const resolvedStatus = status ?? 'active';
|
|
215
|
+
const links = await listAgreements({
|
|
216
|
+
engagementKind: 'link',
|
|
217
|
+
...(resolvedStatus === 'all' ? {} : { status: resolvedStatus }),
|
|
218
|
+
}, creds);
|
|
219
|
+
// ZIG-670: link-shaped summaries, not raw agreement documents — the
|
|
220
|
+
// money block, approvals array, and Mongo internals are noise here.
|
|
221
|
+
const summaries = links.map((a) => ({
|
|
222
|
+
agreementId: a.agreementId,
|
|
223
|
+
status: a.status,
|
|
224
|
+
proposalStatus: a.proposalStatus,
|
|
225
|
+
parties: {
|
|
226
|
+
creatorAgent: a.parties?.creatorAgent ?? null,
|
|
227
|
+
providerAgent: a.parties?.providerAgent ?? null,
|
|
228
|
+
creator: a.parties?.creator ?? null,
|
|
229
|
+
proposedTo: a.parties?.proposedTo ?? null,
|
|
230
|
+
},
|
|
231
|
+
...(a.description ? { description: a.description } : {}),
|
|
232
|
+
createdAt: a.createdAt,
|
|
233
|
+
}));
|
|
203
234
|
const hasActive = links.some((a) => a.status === 'active');
|
|
204
235
|
return textResult({
|
|
205
|
-
count:
|
|
206
|
-
|
|
236
|
+
count: summaries.length,
|
|
237
|
+
status: resolvedStatus,
|
|
238
|
+
links: summaries,
|
|
207
239
|
...(hasActive
|
|
208
240
|
? {
|
|
209
241
|
nextSteps: 'A link is reach-only. Use ziggs_open_conversation (participantId = peer agent id) and/or ziggs_issue_grant before reading context.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "MCP server for Claude Code, Cursor, and other MCP hosts — act as your Ziggs delegate agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
|
-
"@ziggs-ai/api-client": "^0.1.
|
|
39
|
+
"@ziggs-ai/api-client": "^0.1.20",
|
|
40
40
|
"dotenv": "^16.6.1",
|
|
41
41
|
"zod": "^3.24.2"
|
|
42
42
|
},
|