@ziggs-ai/ziggs-mcp 0.1.31 → 0.1.34
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/inboxToolResult.d.ts +3 -3
- package/dist/inboxToolResult.js +12 -10
- package/dist/toolError.js +1 -1
- package/dist/tools.js +165 -104
- package/dist/trustTools.js +4 -4
- package/package.json +2 -2
- package/skills/ziggs/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -194,7 +194,7 @@ OP_KEY_A=... AGENT_A=... USER_B=... OP_KEY_B=... AGENT_B=... \
|
|
|
194
194
|
| Tool | Maps to |
|
|
195
195
|
|------|---------|
|
|
196
196
|
| `ziggs_inbox` | `GET /inbox` + `POST /inbox/ack` |
|
|
197
|
-
| `ziggs_discover_context` | `GET /
|
|
197
|
+
| `ziggs_discover_context` | `GET /grants` (context scopes) |
|
|
198
198
|
| `ziggs_read_context` | `GET /context/read/:type` |
|
|
199
199
|
| `ziggs_record_artifact` | `POST /artifacts` |
|
|
200
200
|
| `ziggs_search_agents` | Agent search |
|
|
@@ -206,7 +206,7 @@ OP_KEY_A=... AGENT_A=... USER_B=... OP_KEY_B=... AGENT_B=... \
|
|
|
206
206
|
| `ziggs_revoke_link` | `DELETE /agreements/:agreementId` (see also `ziggs_revoke_agreement`) |
|
|
207
207
|
| `ziggs_revoke_agreement` | `DELETE /agreements/:id` — any agreement (hire/service/quest/link) |
|
|
208
208
|
| `ziggs_smoke_impersonation` | [Internal/debug] connectivity check — only when `ZIGGS_MCP_DEBUG=1`; not part of normal delegate workflow |
|
|
209
|
-
| `
|
|
209
|
+
| `ziggs_context_snapshot` | `GET /context/snapshot?via=chat:` — one-shot chat orientation (history + agreements + roster), grant-fenced |
|
|
210
210
|
| `ziggs_list_my_agreements` | `GET /agreements?scope=mine` |
|
|
211
211
|
| `ziggs_get_agreement` | `GET /agreements/:id` |
|
|
212
212
|
| `ziggs_list_chats` | `GET /chats/mine` |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GrantView, ContextReadType, InboxAckResult, InboxEnvelope, Task } from '@ziggs-ai/api-client';
|
|
2
2
|
/**
|
|
3
3
|
* ZIG-634 (Step 1): a pre-filled next call. The agent can run it verbatim
|
|
4
4
|
* instead of assembling args from the ids scattered through the response.
|
|
@@ -64,7 +64,7 @@ export interface ScopeGrantTag {
|
|
|
64
64
|
* list is already the caller's non-expired grants (holderId == principalId),
|
|
65
65
|
* so this is grant metadata the caller already holds — no protected content.
|
|
66
66
|
*/
|
|
67
|
-
export declare function indexReachByScope(reach:
|
|
67
|
+
export declare function indexReachByScope(reach: GrantView[]): Map<string, ScopeGrantTag>;
|
|
68
68
|
/**
|
|
69
69
|
* Put humanAttention first so MCP hosts surface it before counts (ZIG-482),
|
|
70
70
|
* and append readPlan last so each inbox call self-narrates the follow-up
|
|
@@ -73,4 +73,4 @@ export declare function indexReachByScope(reach: ContextReachDescriptor[]): Map<
|
|
|
73
73
|
* When `reach` (the caller's own live grants) is passed, each scope is tagged
|
|
74
74
|
* with its covering grant (ZIG-635) so the agent can pin X-Context-Grant-Id.
|
|
75
75
|
*/
|
|
76
|
-
export declare function formatInboxToolResult(inbox: InboxEnvelope, ack?: InboxAckResult | null, webOrigin?: string, activeTasks?: Task[], reach?:
|
|
76
|
+
export declare function formatInboxToolResult(inbox: InboxEnvelope, ack?: InboxAckResult | null, webOrigin?: string, activeTasks?: Task[], reach?: GrantView[], activeTasksError?: string): Record<string, unknown>;
|
package/dist/inboxToolResult.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { grantCaveat } from '@ziggs-ai/api-client';
|
|
1
2
|
import { formatPendingDecisionsPayload, resolveWebAppOrigin, } from './pendingDecisions.js';
|
|
2
3
|
/** Keep the plan bounded; the full scopes array still carries everything. */
|
|
3
4
|
const MAX_READ_PLAN = 12;
|
|
@@ -147,13 +148,14 @@ export function buildReadContextReadPlan(page, type, via, presentedGrantId) {
|
|
|
147
148
|
}
|
|
148
149
|
return plan;
|
|
149
150
|
}
|
|
150
|
-
function toScopeGrantTag(
|
|
151
|
+
function toScopeGrantTag(g) {
|
|
151
152
|
return {
|
|
152
|
-
grantId:
|
|
153
|
-
temporal
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
grantId: g.grantId,
|
|
154
|
+
// Context grants carry temporal/watermark as caveats (ZIG-646).
|
|
155
|
+
temporal: grantCaveat(g, 'temporal') ?? 'from-now',
|
|
156
|
+
watermarkAt: grantCaveat(g, 'watermark_at') ?? '',
|
|
157
|
+
expiresAt: g.expiresAt,
|
|
158
|
+
parentGrantId: g.parentGrantId,
|
|
157
159
|
};
|
|
158
160
|
}
|
|
159
161
|
/**
|
|
@@ -173,11 +175,11 @@ function isBroaderGrant(a, b) {
|
|
|
173
175
|
*/
|
|
174
176
|
export function indexReachByScope(reach) {
|
|
175
177
|
const byScope = new Map();
|
|
176
|
-
for (const
|
|
177
|
-
if (!
|
|
178
|
+
for (const g of reach) {
|
|
179
|
+
if (!g?.scope)
|
|
178
180
|
continue;
|
|
179
|
-
const key = `${
|
|
180
|
-
const tag = toScopeGrantTag(
|
|
181
|
+
const key = `${g.scope.kind}:${g.scope.id}`;
|
|
182
|
+
const tag = toScopeGrantTag(g);
|
|
181
183
|
const existing = byScope.get(key);
|
|
182
184
|
if (!existing || isBroaderGrant(tag, existing))
|
|
183
185
|
byScope.set(key, tag);
|
package/dist/toolError.js
CHANGED
|
@@ -13,7 +13,7 @@ const HTTP_STATUS = /(?:^|\s)([1-5]\d{2})(?=\s|$)/;
|
|
|
13
13
|
const SCOPE_DENIED_HINT = 'You are not authorized for this scope. To get access: ask the counterparty ' +
|
|
14
14
|
'to issue you a context grant (they run ziggs_issue_grant), or request a ' +
|
|
15
15
|
'bilateral link first (ziggs_request_link). Check what you can already ' +
|
|
16
|
-
'reach with ziggs_discover_context /
|
|
16
|
+
'reach with ziggs_discover_context / ziggs_context_snapshot.';
|
|
17
17
|
function codeForStatus(status) {
|
|
18
18
|
if (status === 401)
|
|
19
19
|
return 'NOT_AUTHENTICATED';
|
package/dist/tools.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDirectTo, proposeBroadcast, publishOffer, respondToAgreement, revokeAgreement,
|
|
3
|
+
import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDirectTo, proposeBroadcast, publishOffer, claimOffer, provisionRelayWorkers, respondToAgreement, revokeAgreement, sendChatMessage, ContextDiscoveryClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getBackendUrl, } from '@ziggs-ai/api-client';
|
|
4
4
|
import { decodeOperatorKeyClaims } from './operatorKey.js';
|
|
5
5
|
import { registerTrustTools } from './trustTools.js';
|
|
6
6
|
import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
|
|
7
7
|
import { filterTasksForDelegate, formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
|
|
8
8
|
import { PROTOCOL } from './protocol/delegateProtocol.js';
|
|
9
|
+
const RELAY_COORDINATOR_AGENT_ID = 'relay-coordinator';
|
|
10
|
+
function buildRelayCoordinatorTaskBody(opts) {
|
|
11
|
+
const title = opts.title?.trim() || 'Relay coordinator job';
|
|
12
|
+
return {
|
|
13
|
+
agreementId: opts.hireAgreementId,
|
|
14
|
+
assigneeId: RELAY_COORDINATOR_AGENT_ID,
|
|
15
|
+
description: `${title}\nrelay:v1\n${JSON.stringify(opts.payload)}`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
9
18
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
10
19
|
import { toolError } from './toolError.js';
|
|
11
20
|
// ZIG-557: the protocol sentences (loop / ack / humanAttention) are sourced
|
|
@@ -50,7 +59,6 @@ function textResult(data) {
|
|
|
50
59
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
51
60
|
};
|
|
52
61
|
}
|
|
53
|
-
const scopeKindSchema = z.enum(['chat', 'agreement', 'task', 'counterparty']);
|
|
54
62
|
const contextReadTypeSchema = z.enum([
|
|
55
63
|
'messages',
|
|
56
64
|
'artifacts',
|
|
@@ -138,24 +146,6 @@ async function proxyConnection(creds, input) {
|
|
|
138
146
|
const result = parsed?.['result'];
|
|
139
147
|
return result ?? parsed;
|
|
140
148
|
}
|
|
141
|
-
/** ZIG-640 — server-side org rebind; existing OAuth Bearer keeps working. */
|
|
142
|
-
async function rebindDelegateOrg(creds, orgId) {
|
|
143
|
-
const url = `${getBackendUrl()}/agents/claude-delegate/rebind`;
|
|
144
|
-
const res = await fetch(url, {
|
|
145
|
-
method: 'POST',
|
|
146
|
-
headers: {
|
|
147
|
-
'content-type': 'application/json',
|
|
148
|
-
Authorization: `Bearer ${creds.operatorKey}`,
|
|
149
|
-
'X-Agent-Id': creds.agentId,
|
|
150
|
-
},
|
|
151
|
-
body: JSON.stringify({ orgId }),
|
|
152
|
-
});
|
|
153
|
-
const body = await res.text().catch(() => '');
|
|
154
|
-
if (!res.ok) {
|
|
155
|
-
throw new Error(`POST /agents/claude-delegate/rebind ${res.status} ${body.slice(0, 200)}`);
|
|
156
|
-
}
|
|
157
|
-
return body ? JSON.parse(body) : {};
|
|
158
|
-
}
|
|
159
149
|
/** ZIG-640 — runtime acting org from server (self-hire / agent row). */
|
|
160
150
|
async function fetchDelegateAccess(creds) {
|
|
161
151
|
const url = `${getBackendUrl()}/agents/claude-delegate/access`;
|
|
@@ -216,35 +206,32 @@ function resolveOrgSelector(orgs, selector) {
|
|
|
216
206
|
return { status: 'not-found' };
|
|
217
207
|
}
|
|
218
208
|
/**
|
|
219
|
-
* ZIG-641 — cross-connection discovery
|
|
220
|
-
* grant
|
|
221
|
-
* connectionId/grantId no longer has to arrive out of
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* with proxyConnection.
|
|
209
|
+
* ZIG-641 / ZIG-648 — cross-connection discovery over the unified GET /grants:
|
|
210
|
+
* every connection grant this agent holds, grouped by connection so
|
|
211
|
+
* ziggs_connection_proxy's connectionId/grantId no longer has to arrive out of
|
|
212
|
+
* band. `provider` comes from the grant's resolved scope label. The response is
|
|
213
|
+
* scanned defensively for leaked secrets, as proxyConnection does.
|
|
225
214
|
*/
|
|
226
215
|
async function listConnectionsForHolder(creds) {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
'X-Agent-Id': creds.agentId,
|
|
233
|
-
},
|
|
216
|
+
const client = new GrantsClient(creds.operatorKey, creds.agentId);
|
|
217
|
+
// All pages of the agent's live connection grants (not just the first page).
|
|
218
|
+
const items = await client.listAllGrants({
|
|
219
|
+
scopeKind: 'connection',
|
|
220
|
+
health: 'active',
|
|
234
221
|
});
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
catch {
|
|
245
|
-
parsed = body;
|
|
222
|
+
const byConnection = new Map();
|
|
223
|
+
for (const g of items) {
|
|
224
|
+
const connectionId = g.scope.id;
|
|
225
|
+
let group = byConnection.get(connectionId);
|
|
226
|
+
if (!group) {
|
|
227
|
+
group = { connectionId, provider: g.scope.label ?? null, grants: [] };
|
|
228
|
+
byConnection.set(connectionId, group);
|
|
229
|
+
}
|
|
230
|
+
group.grants.push(g);
|
|
246
231
|
}
|
|
247
|
-
|
|
232
|
+
const result = [...byConnection.values()];
|
|
233
|
+
assertNoLeakedSecret(JSON.stringify(result));
|
|
234
|
+
return result;
|
|
248
235
|
}
|
|
249
236
|
/**
|
|
250
237
|
* ZIG-686 — agent-initiated MCP connection request: ask the principal to
|
|
@@ -370,9 +357,9 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
370
357
|
? 'Not connected — complete MCP OAuth consent first, then re-check actingOrgId here.'
|
|
371
358
|
: actingOrgName
|
|
372
359
|
? `This MCP session acts in **${actingOrgName}** (${actingOrgId}). Runtime org comes from server-side delegate + self-hire — not the OAuth JWT.`
|
|
373
|
-
: 'Connected but could not resolve acting org name —
|
|
360
|
+
: 'Connected but could not resolve acting org name — reconnect MCP OAuth if needed.',
|
|
374
361
|
switchOrgHint: switchOrgHint ??
|
|
375
|
-
'
|
|
362
|
+
'Org is fixed at OAuth consent. Reconnect MCP OAuth and pick the target org on the consent screen to act elsewhere.',
|
|
376
363
|
apiBase: getBackendUrl(),
|
|
377
364
|
webAppOrigin: webOrigin,
|
|
378
365
|
docs: 'https://ziggsai.com/docs',
|
|
@@ -382,7 +369,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
382
369
|
: 'Next: ziggs_inbox or ziggs_pending_decisions at session start.',
|
|
383
370
|
});
|
|
384
371
|
});
|
|
385
|
-
server.tool('ziggs_list_my_orgs', 'List every org you (the operator) belong to — { orgId, name, kind, role }. Unlike ziggs_discover_context (granted scopes only), this is your full membership
|
|
372
|
+
server.tool('ziggs_list_my_orgs', 'List every org you (the operator) belong to — { orgId, name, kind, role }. Unlike ziggs_discover_context (granted scopes only), this is your full membership — useful before OAuth reconnect when the human wants to pick a target org.', {}, READ_ONLY, async () => {
|
|
386
373
|
try {
|
|
387
374
|
const orgs = await fetchMyOrgs(creds);
|
|
388
375
|
return textResult({ count: orgs.length, orgs });
|
|
@@ -391,40 +378,24 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
391
378
|
return toolError(e.message);
|
|
392
379
|
}
|
|
393
380
|
});
|
|
394
|
-
server.tool('ziggs_switch_org', '
|
|
381
|
+
server.tool('ziggs_switch_org', 'Deprecated: org is fixed at OAuth consent. Reconnect MCP OAuth and pick the target org on the consent screen — this tool no longer switches org server-side.', {
|
|
395
382
|
org: z
|
|
396
383
|
.string()
|
|
397
|
-
.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (resolved.status === 'not-found') {
|
|
413
|
-
return toolError(`No org matches "${selector}". You belong to: ${JSON.stringify(orgs)}`);
|
|
414
|
-
}
|
|
415
|
-
const result = await rebindDelegateOrg(creds, resolved.orgId);
|
|
416
|
-
return textResult({
|
|
417
|
-
ok: true,
|
|
418
|
-
resolvedOrgId: resolved.orgId,
|
|
419
|
-
...result,
|
|
420
|
-
note: result.unchanged
|
|
421
|
-
? 'Already acting in this org — no changes made.'
|
|
422
|
-
: 'Org rebind complete. Existing OAuth token unchanged; call ziggs_auth_status to verify actingOrgId.',
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
catch (e) {
|
|
426
|
-
return toolError(e.message);
|
|
427
|
-
}
|
|
384
|
+
.optional()
|
|
385
|
+
.describe('Ignored — kept for backward compatibility with older prompts'),
|
|
386
|
+
confirm: z.literal(true).optional(),
|
|
387
|
+
}, WRITE, async () => {
|
|
388
|
+
return textResult({
|
|
389
|
+
ok: false,
|
|
390
|
+
deprecated: true,
|
|
391
|
+
message: 'ziggs_switch_org no longer switches org. Org is bound at MCP OAuth consent. Reconnect the Ziggs MCP server in your client, pick the desired org on the consent screen, then call ziggs_auth_status to verify actingOrgId.',
|
|
392
|
+
reconnectSteps: [
|
|
393
|
+
'Remove or disable the Ziggs MCP connection in your client',
|
|
394
|
+
'Re-add https://mcp.ziggsai.com/mcp (or your .mcp.json entry)',
|
|
395
|
+
'On the Ziggs consent screen, select the org you want',
|
|
396
|
+
'Call ziggs_auth_status and confirm actingOrgId',
|
|
397
|
+
],
|
|
398
|
+
});
|
|
428
399
|
});
|
|
429
400
|
server.tool('ziggs_pending_decisions', ZIGGS_PENDING_DECISIONS_DESCRIPTION, {}, READ_ONLY, async () => {
|
|
430
401
|
try {
|
|
@@ -444,21 +415,21 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
444
415
|
}
|
|
445
416
|
});
|
|
446
417
|
if (cfg.debugTools) {
|
|
447
|
-
server.tool('ziggs_smoke_impersonation', '[Internal/debug] Connectivity check for the operator-key impersonation path — lists agreements and
|
|
418
|
+
server.tool('ziggs_smoke_impersonation', '[Internal/debug] Connectivity check for the operator-key impersonation path — lists agreements and snapshots the first chat. Not part of normal delegate workflow; use ziggs_list_my_agreements / ziggs_context_snapshot instead.', {}, READ_ONLY, async () => {
|
|
448
419
|
try {
|
|
449
420
|
const agreements = await getMyAgreements({}, creds);
|
|
450
421
|
const chats = await listMyChats(creds);
|
|
451
|
-
let
|
|
422
|
+
let snapshot = null;
|
|
452
423
|
const firstChatId = chats[0]?.chatId;
|
|
453
424
|
if (firstChatId) {
|
|
454
|
-
const client = new
|
|
455
|
-
|
|
425
|
+
const client = new ContextReadClient(creds.operatorKey, creds.agentId);
|
|
426
|
+
snapshot = await client.snapshot(firstChatId, { maxMessages: 5 });
|
|
456
427
|
}
|
|
457
428
|
return textResult({
|
|
458
429
|
ok: true,
|
|
459
430
|
agreementsCount: agreements.length,
|
|
460
431
|
chatsCount: chats.length,
|
|
461
|
-
|
|
432
|
+
snapshot,
|
|
462
433
|
});
|
|
463
434
|
}
|
|
464
435
|
catch (e) {
|
|
@@ -466,13 +437,20 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
466
437
|
}
|
|
467
438
|
});
|
|
468
439
|
}
|
|
469
|
-
server.tool('
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
440
|
+
server.tool('ziggs_context_snapshot', 'One-shot orientation for a chat: history, agreements (with which party is you), and the roster of agents/users — grant-fenced. Use when entering a chat you have not read yet; follow up with ziggs_read_context forward deltas from the returned latestSequence.', {
|
|
441
|
+
chatId: z.string().describe('Chat id to snapshot'),
|
|
442
|
+
maxMessages: z.number().optional().describe('Optional message history cap'),
|
|
443
|
+
contextGrantId: z
|
|
444
|
+
.string()
|
|
445
|
+
.optional()
|
|
446
|
+
.describe('Optional grant id when reading under a context grant'),
|
|
447
|
+
}, READ_ONLY, async ({ chatId, maxMessages, contextGrantId }) => {
|
|
473
448
|
try {
|
|
474
|
-
const client = new
|
|
475
|
-
const result = await client.
|
|
449
|
+
const client = new ContextReadClient(creds.operatorKey, creds.agentId);
|
|
450
|
+
const result = await client.snapshot(chatId, {
|
|
451
|
+
maxMessages,
|
|
452
|
+
contextGrantId,
|
|
453
|
+
});
|
|
476
454
|
return textResult(result);
|
|
477
455
|
}
|
|
478
456
|
catch (e) {
|
|
@@ -560,24 +538,26 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
560
538
|
return toolError(e.message);
|
|
561
539
|
}
|
|
562
540
|
});
|
|
563
|
-
server.tool('ziggs_propose_agreement', 'Propose a direct agreement to one counterparty in a chat
|
|
541
|
+
server.tool('ziggs_propose_agreement', 'Propose a direct agreement to one counterparty (proposedTo) in a chat. The payer is always derived server-side as the non-providing side — there is no payer input. Omit providerId (or set it to proposedTo) to commission the recipient (they work, your side pays). Set providerId to your own agent id to offer (you work, proposedTo pays). Set providerId to a third-party agent id to broker (they work, proposedTo pays) — that provider must have an active published offer whose terms match this proposal (price, lifecycle, engagementKind, etc.) or the call fails naming the mismatched field. engagementKind "service" (default) = one deliverable; "hire" = ongoing engagement. price is recorded on the agreement but does not itself trigger a transfer.', {
|
|
564
542
|
proposedTo: z.string(),
|
|
565
543
|
chatId: z.string(),
|
|
566
544
|
description: z.string(),
|
|
545
|
+
providerId: z
|
|
546
|
+
.string()
|
|
547
|
+
.optional()
|
|
548
|
+
.describe('Who does the work. Omitted = proposedTo (commission). Your agent id = offer. Another agent id = broker/matchmaking.'),
|
|
567
549
|
price: z.number().optional().describe('Optional; does not trigger transfer by itself'),
|
|
568
550
|
engagementKind: z
|
|
569
551
|
.enum(['hire', 'service'])
|
|
570
552
|
.optional()
|
|
571
553
|
.describe("'service' (default) = one-off deliverable; 'hire' = ongoing engagement"),
|
|
572
|
-
}, WRITE, async ({ proposedTo, chatId, description, price, engagementKind }) => {
|
|
554
|
+
}, WRITE, async ({ proposedTo, chatId, description, providerId, price, engagementKind }) => {
|
|
573
555
|
try {
|
|
574
556
|
const agreement = await proposeDirectTo({
|
|
575
557
|
proposedTo,
|
|
576
558
|
chatId,
|
|
577
559
|
description,
|
|
578
|
-
|
|
579
|
-
// and the payer is derived server-side as the creator's side.
|
|
580
|
-
providerId: proposedTo,
|
|
560
|
+
providerId: providerId?.trim() || proposedTo,
|
|
581
561
|
price,
|
|
582
562
|
engagementKind: engagementKind ?? 'service',
|
|
583
563
|
}, creds);
|
|
@@ -637,6 +617,70 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
637
617
|
return toolError(e.message);
|
|
638
618
|
}
|
|
639
619
|
});
|
|
620
|
+
server.tool('ziggs_claim_offer', 'Claim a published standing offer (POST /marketplace/offers/claim). Use for relay worker provisioning when the worker has a marketplace offer — no worker-side approval needed.', {
|
|
621
|
+
agreementId: z.string().describe('Open offer agreementId to claim'),
|
|
622
|
+
}, WRITE, async ({ agreementId }) => {
|
|
623
|
+
try {
|
|
624
|
+
const offer = await claimOffer(agreementId, creds);
|
|
625
|
+
return textResult({ offer });
|
|
626
|
+
}
|
|
627
|
+
catch (e) {
|
|
628
|
+
return toolError(e.message);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
server.tool('ziggs_provision_relay_workers', 'Initiator path: provision per-step worker agreements before relay kickoff. Reuses active delegations under the hire, claims standing offers when available, otherwise proposes delegations (worker must approve — never impersonated). Returns relay:v1 payload and POST /tasks body when all steps are active.', {
|
|
632
|
+
hireAgreementId: z.string(),
|
|
633
|
+
chatId: z
|
|
634
|
+
.string()
|
|
635
|
+
.optional()
|
|
636
|
+
.describe('Required when a step has no standing offer and needs delegation under the hire'),
|
|
637
|
+
inputArtifactIds: z.array(z.string()).optional(),
|
|
638
|
+
steps: z.array(z.object({
|
|
639
|
+
stepId: z.string(),
|
|
640
|
+
order: z.number(),
|
|
641
|
+
assigneeId: z.string(),
|
|
642
|
+
description: z.string(),
|
|
643
|
+
offerAgreementId: z
|
|
644
|
+
.string()
|
|
645
|
+
.optional()
|
|
646
|
+
.describe('Explicit open offer to claim for this worker'),
|
|
647
|
+
})),
|
|
648
|
+
kickoff: z
|
|
649
|
+
.boolean()
|
|
650
|
+
.optional()
|
|
651
|
+
.describe('When true and readyForKickoff, also POST /tasks on the hire for relay-coordinator'),
|
|
652
|
+
}, WRITE, async ({ hireAgreementId, chatId, inputArtifactIds, steps, kickoff }) => {
|
|
653
|
+
try {
|
|
654
|
+
const result = await provisionRelayWorkers({
|
|
655
|
+
creds,
|
|
656
|
+
hireAgreementId,
|
|
657
|
+
chatId,
|
|
658
|
+
inputArtifactIds,
|
|
659
|
+
steps,
|
|
660
|
+
});
|
|
661
|
+
const relayTaskBody = buildRelayCoordinatorTaskBody({
|
|
662
|
+
hireAgreementId,
|
|
663
|
+
payload: result.payload,
|
|
664
|
+
});
|
|
665
|
+
let task;
|
|
666
|
+
if (kickoff && result.readyForKickoff) {
|
|
667
|
+
task = await createTask(relayTaskBody, creds);
|
|
668
|
+
}
|
|
669
|
+
return textResult({
|
|
670
|
+
...result,
|
|
671
|
+
relayTaskBody,
|
|
672
|
+
task,
|
|
673
|
+
nextSteps: result.readyForKickoff
|
|
674
|
+
? kickoff && task
|
|
675
|
+
? 'Relay coordinator task created — watch Execution for step progress.'
|
|
676
|
+
: 'All worker agreements active — POST relayTaskBody via createTask or set kickoff=true.'
|
|
677
|
+
: `Worker approval pending on: ${result.pendingApprovals.join(', ')}. Call ziggs_respond_to_agreement after workers approve, then re-run with kickoff=true.`,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
catch (e) {
|
|
681
|
+
return toolError(e.message);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
640
684
|
server.tool('ziggs_respond_to_agreement', 'Approve or reject a pending agreement. Uses PUT /approvals/:partyId or POST /claim for an open broadcast (public or org-scoped; org-scoped quests are claimable only by members of the agreement\'s org).', {
|
|
641
685
|
agreementId: z.string(),
|
|
642
686
|
action: z.enum(['approve', 'reject']),
|
|
@@ -689,13 +733,18 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
689
733
|
activeTasksError = e.message;
|
|
690
734
|
}
|
|
691
735
|
// ZIG-635: tag each scope with the covering grant from the caller's own
|
|
692
|
-
//
|
|
736
|
+
// held context grants. Best-effort — the inbox still works untagged.
|
|
737
|
+
// All pages of live grants only (GET /grants also returns expired/revoked
|
|
738
|
+
// and paginates), so a scope isn't left untagged behind the first page.
|
|
693
739
|
let reach = [];
|
|
694
740
|
try {
|
|
695
|
-
reach = await new
|
|
741
|
+
reach = await new GrantsClient(creds.operatorKey, creds.agentId).listAllGrants({
|
|
742
|
+
scopeKind: ['chat', 'agreement', 'org'],
|
|
743
|
+
health: 'active',
|
|
744
|
+
});
|
|
696
745
|
}
|
|
697
746
|
catch {
|
|
698
|
-
// omit grant tags when
|
|
747
|
+
// omit grant tags when the grants read fails
|
|
699
748
|
}
|
|
700
749
|
return textResult(formatInboxToolResult(inbox, acked, cfg.ZIGGS_WEB_URL, activeTasks, reach, activeTasksError));
|
|
701
750
|
}
|
|
@@ -703,11 +752,23 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
703
752
|
return toolError(e.message);
|
|
704
753
|
}
|
|
705
754
|
});
|
|
706
|
-
server.tool('ziggs_discover_context', 'List every context grant this delegate holds, as
|
|
755
|
+
server.tool('ziggs_discover_context', 'List every context grant this delegate holds, as canonical grants — grantId, scope, caveats (temporal + watermark_at), expiresAt, health (no content). The single "what is my reach" tool; pass a grantId to ziggs_read_context to pin a specific grant. Cursor-paginated: pass cursor from a prior nextCursor to page.', {
|
|
756
|
+
cursor: z
|
|
757
|
+
.string()
|
|
758
|
+
.optional()
|
|
759
|
+
.describe('Opaque cursor from a prior nextCursor'),
|
|
760
|
+
limit: z.number().optional().describe('Page size (default server-side)'),
|
|
761
|
+
}, READ_ONLY, async ({ cursor, limit }) => {
|
|
707
762
|
try {
|
|
708
|
-
const client = new
|
|
709
|
-
const
|
|
710
|
-
|
|
763
|
+
const client = new GrantsClient(creds.operatorKey, creds.agentId);
|
|
764
|
+
const { items, nextCursor } = await client.listGrants({
|
|
765
|
+
scopeKind: ['chat', 'agreement', 'org'],
|
|
766
|
+
// Reach = live grants only; revoked/expired are not reachable.
|
|
767
|
+
health: 'active',
|
|
768
|
+
cursor,
|
|
769
|
+
limit,
|
|
770
|
+
});
|
|
771
|
+
return textResult({ count: items.length, grants: items, nextCursor });
|
|
711
772
|
}
|
|
712
773
|
catch (e) {
|
|
713
774
|
return toolError(e.message);
|
|
@@ -922,7 +983,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
922
983
|
}
|
|
923
984
|
});
|
|
924
985
|
server.tool('ziggs_list_my_connections', 'Discover the third-party connections (credentials like GitHub/Jira, NOT agent-to-agent Links — see ziggs_list_links for that) you hold grants for (e.g. "is GitHub connected?") without the owner sharing connectionId/grantId out of band. ' +
|
|
925
|
-
'Returns, per connection: connectionId, provider,
|
|
986
|
+
'Returns, per connection: connectionId, provider, and the grant(s) you hold — each as the canonical grant shape (grantId, scope, caveats, and grant health active/expired/revoked). ' +
|
|
926
987
|
'Read-only — never returns credential material. Feed the connectionId + a grantId with health "active" into ziggs_connection_proxy to actually use it.', {}, READ_ONLY, async () => {
|
|
927
988
|
try {
|
|
928
989
|
const connections = await listConnectionsForHolder(creds);
|
package/dist/trustTools.js
CHANGED
|
@@ -57,7 +57,7 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
57
57
|
return toolError(e.message);
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
|
-
server.tool('ziggs_issue_grant', 'Issue bounded context access. Chat scope: admits agent via POST /chats/:id/members (agent-invite → pending_approval until humans consent). Agreement/org scope:
|
|
60
|
+
server.tool('ziggs_issue_grant', 'Issue bounded context access. Chat scope: admits agent via POST /chats/:id/members (agent-invite → pending_approval until humans consent) — this works for you as a delegate. Agreement/org scope: issuing a NEW root grant is a human-authority action; if you are acting for a principal you are denied (AGENT_LACKS_HUMAN_AUTHORITY) — instead use ziggs_delegate_grant to hand a peer a narrower slice of a grant you already hold, or ask your human to issue it. Defaults: from-now, narrow scope.', {
|
|
61
61
|
holderId: z.string().describe('Bare agent id receiving the grant'),
|
|
62
62
|
scopeKind: grantScopeKindSchema,
|
|
63
63
|
scopeId: z.string().describe('chatId, agreementId, or orgId'),
|
|
@@ -210,7 +210,7 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
210
210
|
const { agreement } = await claimAgreement(agreementId, creds);
|
|
211
211
|
return textResult({
|
|
212
212
|
status: 'linked',
|
|
213
|
-
message: 'Link invite claimed — you are now linked. A link is reach-only:
|
|
213
|
+
message: 'Link invite claimed — you are now linked. A link is reach-only: open a chat with the peer (ziggs_open_conversation) and grant it chat access with ziggs_issue_grant, or share a slice of a grant you already hold with ziggs_delegate_grant, before reading context.',
|
|
214
214
|
agreement,
|
|
215
215
|
});
|
|
216
216
|
}
|
|
@@ -252,7 +252,7 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
252
252
|
links: summaries,
|
|
253
253
|
...(hasActive
|
|
254
254
|
? {
|
|
255
|
-
nextSteps: 'A link is reach-only.
|
|
255
|
+
nextSteps: 'A link is reach-only. Open a chat with the peer (ziggs_open_conversation, participantId = peer agent id) and grant it chat access with ziggs_issue_grant, or share a slice of a grant you hold with ziggs_delegate_grant, before reading context.',
|
|
256
256
|
}
|
|
257
257
|
: {}),
|
|
258
258
|
});
|
|
@@ -279,7 +279,7 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
279
279
|
return toolError(e.message);
|
|
280
280
|
}
|
|
281
281
|
});
|
|
282
|
-
server.tool('ziggs_revoke_grant', 'Revoke a context grant and its descendants (DELETE /context/grants/:id). You can revoke (narrow) any grant you hold — this needs no special scope. Revoking a grant you issued or
|
|
282
|
+
server.tool('ziggs_revoke_grant', 'Revoke a context grant and its descendants (DELETE /context/grants/:id). You can revoke (narrow) any grant you hold — this needs no special scope. Revoking a grant you do NOT hold (one you issued, or on a scope you own) is a human-authority action: as a delegate you are limited to grants you hold; the human/owner does the rest.', {
|
|
283
283
|
grantId: z.string(),
|
|
284
284
|
}, DESTRUCTIVE, async ({ grantId }) => {
|
|
285
285
|
try {
|
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.34",
|
|
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.29",
|
|
40
40
|
"dotenv": "^16.6.1",
|
|
41
41
|
"zod": "^3.24.2"
|
|
42
42
|
},
|
package/skills/ziggs/SKILL.md
CHANGED
|
@@ -38,8 +38,8 @@ The sections below elaborate this protocol with tools, examples, and edge cases.
|
|
|
38
38
|
|
|
39
39
|
## Session start — pending decisions + inbox
|
|
40
40
|
|
|
41
|
-
1. Call **`ziggs_auth_status`** after OAuth connect — check **`actingOrgId`** / **`actingOrgName`** (runtime org, not JWT).
|
|
42
|
-
2. To
|
|
41
|
+
1. Call **`ziggs_auth_status`** after OAuth connect — check **`actingOrgId`** / **`actingOrgName`** (runtime org, not JWT). Org is fixed at consent (ZIG-852).
|
|
42
|
+
2. To act in another org: **reconnect MCP OAuth** and pick that org on the consent screen, then re-check **`ziggs_auth_status`**. Use **`ziggs_list_my_orgs`** to help the human choose a target org name before reconnecting.
|
|
43
43
|
3. Call **`ziggs_pending_decisions`** — if `pendingCount > 0`, **paste `decisionChatCard` for the human** before anything else. Wait for explicit approve/reject; then `ziggs_respond_to_agreement`.
|
|
44
44
|
3. Call **`ziggs_inbox`** (optionally pass **`ack`** for scopes you already handled in the prior turn).
|
|
45
45
|
4. Read the envelope: scope news counts, `humanAttention`, and **`decisionChatCard`** when present.
|
|
@@ -100,7 +100,7 @@ When coordinating with another org’s delegate:
|
|
|
100
100
|
|
|
101
101
|
## Boarding checklist (cold session)
|
|
102
102
|
|
|
103
|
-
1. Confirm MCP tools are available (e.g. `ziggs_list_chats` or `
|
|
103
|
+
1. Confirm MCP tools are available (e.g. `ziggs_list_chats` or `ziggs_context_snapshot`).
|
|
104
104
|
2. Run **`ziggs_inbox`** — empty inbox is fine.
|
|
105
105
|
3. Ask the human what they want to do on Ziggs before issuing grants or opening new agreements.
|
|
106
106
|
|