@ziggs-ai/ziggs-mcp 0.3.1 → 0.4.0
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 +1 -1
- package/dist/capabilityAdapter.d.ts +25 -0
- package/dist/capabilityAdapter.js +75 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +11 -2
- package/dist/connectionCreds.js +1 -0
- package/dist/paymentTools.d.ts +9 -8
- package/dist/paymentTools.js +12 -248
- package/dist/tools.js +178 -441
- package/dist/trustTools.d.ts +4 -1
- package/dist/trustTools.js +21 -230
- package/package.json +3 -3
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { getAgreement, getMyAgreements, listMyChats,
|
|
3
|
+
import { getAgreement, getMyAgreements, listMyChats, proposeDirectTo, proposeBroadcast, publishOffer, claimOffer, provisionRelayWorkers, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchDelegateAccess, GRANTS_CAPABILITIES, contextReadCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, } from '@ziggs-ai/api-client';
|
|
4
4
|
import { decodeOperatorKeyClaims } from './operatorKey.js';
|
|
5
5
|
import { registerTrustTools } from './trustTools.js';
|
|
6
6
|
import { registerPaymentTools } from './paymentTools.js';
|
|
@@ -18,6 +18,7 @@ function buildRelayCoordinatorTaskBody(opts) {
|
|
|
18
18
|
}
|
|
19
19
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
20
20
|
import { toolError } from './toolError.js';
|
|
21
|
+
import { registerCapability, registerCapabilities, textResult, } from './capabilityAdapter.js';
|
|
21
22
|
// ZIG-557: the protocol sentences (loop / ack / humanAttention) are sourced
|
|
22
23
|
// from the shared const so this description can't drift from SKILL / server
|
|
23
24
|
// instructions / .cursorrules.
|
|
@@ -46,118 +47,12 @@ const ZIGGS_SEND_MESSAGE_DESCRIPTION = 'Send a chat message as the delegate agen
|
|
|
46
47
|
const ZIGGS_RECORD_ARTIFACT_DESCRIPTION = 'Write an artifact to a chat or agreement scope. Set visibility explicitly. ' +
|
|
47
48
|
'For a finished deliverable, set content_type=result and pass taskId to bind it to the task. ' +
|
|
48
49
|
PROTOCOL.reporting;
|
|
49
|
-
/** Teach the result slot on the record_artifact success path (ZIG-560). */
|
|
50
|
-
function recordArtifactReportingHint(contentType, taskId) {
|
|
51
|
-
if (contentType === 'result') {
|
|
52
|
-
return taskId
|
|
53
|
-
? 'Recorded as a task-bound result artifact. Close the task by setting its terminal result with ziggs_set_task_result ({ summary, status, links }).'
|
|
54
|
-
: 'Recorded as a result artifact, but not bound to a task — pass taskId to bind it, then close the task with ziggs_set_task_result ({ summary, status, links }).';
|
|
55
|
-
}
|
|
56
|
-
return 'Reporting finished work? Record it with content_type=result bound to the task (taskId), then ziggs_set_task_result — chat messages are conversation only.';
|
|
57
|
-
}
|
|
58
|
-
function textResult(data) {
|
|
59
|
-
return {
|
|
60
|
-
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
const contextReadTypeSchema = z.enum([
|
|
64
|
-
'messages',
|
|
65
|
-
'artifacts',
|
|
66
|
-
'agreements',
|
|
67
|
-
'tasks',
|
|
68
|
-
]);
|
|
69
|
-
const artifactVisibilitySchema = z.enum(['chat', 'agent-private']);
|
|
70
50
|
// ZIG-899 — the strict artifact write (fail loudly, return the artifactId)
|
|
71
51
|
// moved into ArtifactsClient.writeStrict, shared with the SDK's record_artifact.
|
|
72
|
-
// ZIG-894 — the leak-guard
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const url = `${getBackendUrl()}/agents/claude-delegate/access`;
|
|
77
|
-
const res = await fetch(url, {
|
|
78
|
-
method: 'GET',
|
|
79
|
-
headers: {
|
|
80
|
-
Authorization: `Bearer ${creds.operatorKey}`,
|
|
81
|
-
'X-Agent-Id': creds.agentId,
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
const body = await res.text().catch(() => '');
|
|
85
|
-
if (!res.ok) {
|
|
86
|
-
throw new Error(`GET /agents/claude-delegate/access ${res.status} ${body.slice(0, 200)}`);
|
|
87
|
-
}
|
|
88
|
-
return body ? JSON.parse(body) : {};
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* ZIG-739 — the operator's full org membership (not just granted scopes, which
|
|
92
|
-
* is all ziggs_list_grants sees). Lets the delegate resolve an org name to
|
|
93
|
-
* an id and offer a pick-list instead of demanding a pasted org_... id.
|
|
94
|
-
*/
|
|
95
|
-
async function fetchMyOrgs(creds) {
|
|
96
|
-
const url = `${getBackendUrl()}/orgs/me`;
|
|
97
|
-
const res = await fetch(url, {
|
|
98
|
-
method: 'GET',
|
|
99
|
-
headers: {
|
|
100
|
-
Authorization: `Bearer ${creds.operatorKey}`,
|
|
101
|
-
'X-Agent-Id': creds.agentId,
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
const body = await res.text().catch(() => '');
|
|
105
|
-
if (!res.ok) {
|
|
106
|
-
throw new Error(`GET /orgs/me ${res.status} ${body.slice(0, 200)}`);
|
|
107
|
-
}
|
|
108
|
-
const parsed = body ? JSON.parse(body) : {};
|
|
109
|
-
return (parsed.orgs ?? []).map((o) => ({
|
|
110
|
-
orgId: o.orgId,
|
|
111
|
-
name: o.name,
|
|
112
|
-
kind: o.kind,
|
|
113
|
-
role: o.role,
|
|
114
|
-
}));
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* ZIG-739 — resolve an org selector (exact org_... id OR a name/handle) against
|
|
118
|
-
* the operator's memberships. Exact id wins; otherwise case-insensitive name
|
|
119
|
-
* match. Ambiguous names return the candidates rather than guessing.
|
|
120
|
-
*/
|
|
121
|
-
function resolveOrgSelector(orgs, selector) {
|
|
122
|
-
const byId = orgs.find((o) => o.orgId === selector);
|
|
123
|
-
if (byId)
|
|
124
|
-
return { status: 'ok', orgId: byId.orgId };
|
|
125
|
-
const needle = selector.toLowerCase();
|
|
126
|
-
const byName = orgs.filter((o) => o.name.toLowerCase() === needle);
|
|
127
|
-
if (byName.length === 1)
|
|
128
|
-
return { status: 'ok', orgId: byName[0].orgId };
|
|
129
|
-
if (byName.length > 1)
|
|
130
|
-
return { status: 'ambiguous', matches: byName };
|
|
131
|
-
return { status: 'not-found' };
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* ZIG-641 / ZIG-648 — cross-connection discovery over the unified GET /grants:
|
|
135
|
-
* every connection grant this agent holds, grouped by connection so
|
|
136
|
-
* ziggs_connection_proxy's connectionId/grantId no longer has to arrive out of
|
|
137
|
-
* band. `provider` comes from the grant's resolved scope label. The response is
|
|
138
|
-
* scanned defensively for leaked secrets, as ConnectionsClient.proxy does.
|
|
139
|
-
*/
|
|
140
|
-
async function listConnectionsForHolder(creds) {
|
|
141
|
-
const client = new GrantsClient(creds.operatorKey, creds.agentId);
|
|
142
|
-
// All pages of the agent's live connection grants (not just the first page).
|
|
143
|
-
const items = await client.listAllGrants({
|
|
144
|
-
scopeKind: 'connection',
|
|
145
|
-
health: 'active',
|
|
146
|
-
});
|
|
147
|
-
const byConnection = new Map();
|
|
148
|
-
for (const g of items) {
|
|
149
|
-
const connectionId = g.scope.id;
|
|
150
|
-
let group = byConnection.get(connectionId);
|
|
151
|
-
if (!group) {
|
|
152
|
-
group = { connectionId, provider: g.scope.label ?? null, grants: [] };
|
|
153
|
-
byConnection.set(connectionId, group);
|
|
154
|
-
}
|
|
155
|
-
group.grants.push(g);
|
|
156
|
-
}
|
|
157
|
-
const result = [...byConnection.values()];
|
|
158
|
-
assertNoLeakedConnectionSecret(JSON.stringify(result));
|
|
159
|
-
return result;
|
|
160
|
-
}
|
|
52
|
+
// ZIG-894 / ZIG-956 — the leak-guard, the connections proxy/request calls, the
|
|
53
|
+
// grouped connection lister (ConnectionsClient.listForHolder), the org lookups
|
|
54
|
+
// (fetchMyOrgs / fetchDelegateAccess), and the whole SDK-twin tool definitions
|
|
55
|
+
// all live in @ziggs-ai/api-client now (shared with the agent SDK).
|
|
161
56
|
/**
|
|
162
57
|
* Ids this delegate answers for: its own agent id plus its principal's user
|
|
163
58
|
* id (operator-key ownerId / ZIGGS_OWNER_USER_ID). Used to decide which
|
|
@@ -205,6 +100,140 @@ async function loadSessionActionsPayload(creds, cfg, opts) {
|
|
|
205
100
|
withSessionCard: opts?.withSessionCard,
|
|
206
101
|
});
|
|
207
102
|
}
|
|
103
|
+
// ZIG-941 #7 — heavy tool groups pulled out of registerZiggsTools so the
|
|
104
|
+
// lean session-start tier (ZIGGS_MCP_CORE_ONLY) can skip registering them.
|
|
105
|
+
// Registration is otherwise identical to the previous inline definitions.
|
|
106
|
+
function registerMarketplaceTools(server, creds) {
|
|
107
|
+
server.tool('ziggs_publish_quest', 'Publish an open quest any agent can claim (buyer-broadcast): you are the buyer, and whoever claims it does the work. audience="everyone" (default) is fully public across all orgs; audience="org" scopes it to your active org — only agents in your org see it in marketplace feeds and may claim it. The payer is derived server-side as your side (the publisher); there is no payer input.', {
|
|
108
|
+
description: z.string(),
|
|
109
|
+
chatId: z.string().optional(),
|
|
110
|
+
price: z.number().optional(),
|
|
111
|
+
audience: z
|
|
112
|
+
.enum(['everyone', 'org'])
|
|
113
|
+
.optional()
|
|
114
|
+
.describe("'everyone' (default, public) or 'org' (visible/claimable only within your org)"),
|
|
115
|
+
}, WRITE, async ({ description, chatId, price, audience }) => {
|
|
116
|
+
try {
|
|
117
|
+
// Buyer-broadcast: the payer is derived server-side as the creating
|
|
118
|
+
// principal (your side), and providerId is forbidden on broadcasts —
|
|
119
|
+
// the claiming agent fills the open provider side. So we send neither.
|
|
120
|
+
// audience flows straight through; the api-client + backend map it to
|
|
121
|
+
// the proposedTo sentinel and scope on the publisher's org.
|
|
122
|
+
const agreement = await proposeBroadcast({
|
|
123
|
+
description,
|
|
124
|
+
chatId: chatId ?? '',
|
|
125
|
+
price,
|
|
126
|
+
engagementKind: 'service',
|
|
127
|
+
audience: audience ?? 'everyone',
|
|
128
|
+
}, creds);
|
|
129
|
+
return textResult({ agreement });
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
return toolError(e.message);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
server.tool('ziggs_publish_offer', 'Publish a standing offer buyers can claim (seller-broadcast). audience="everyone" (default) is public; audience="org" scopes it to your active org. Requires an active org when audience="org".', {
|
|
136
|
+
description: z.string(),
|
|
137
|
+
price: z.number().optional(),
|
|
138
|
+
engagementKind: z.enum(['hire', 'service']).optional(),
|
|
139
|
+
audience: z
|
|
140
|
+
.enum(['everyone', 'org'])
|
|
141
|
+
.optional()
|
|
142
|
+
.describe("'everyone' (default, public) or 'org' (visible/claimable only within your org)"),
|
|
143
|
+
}, WRITE, async ({ description, price, engagementKind, audience }) => {
|
|
144
|
+
try {
|
|
145
|
+
const agreement = await publishOffer({
|
|
146
|
+
description,
|
|
147
|
+
price,
|
|
148
|
+
engagementKind,
|
|
149
|
+
audience: audience ?? 'everyone',
|
|
150
|
+
}, creds);
|
|
151
|
+
return textResult({ offer: agreement });
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
return toolError(e.message);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
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.', {
|
|
158
|
+
agreementId: z.string().describe('Open offer agreementId to claim'),
|
|
159
|
+
}, WRITE, async ({ agreementId }) => {
|
|
160
|
+
try {
|
|
161
|
+
const offer = await claimOffer(agreementId, creds);
|
|
162
|
+
return textResult({ offer });
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
return toolError(e.message);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
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.', {
|
|
169
|
+
hireAgreementId: z.string(),
|
|
170
|
+
chatId: z
|
|
171
|
+
.string()
|
|
172
|
+
.optional()
|
|
173
|
+
.describe('Required when a step has no standing offer and needs delegation under the hire'),
|
|
174
|
+
inputArtifactIds: z.array(z.string()).optional(),
|
|
175
|
+
steps: z.array(z.object({
|
|
176
|
+
stepId: z.string(),
|
|
177
|
+
order: z.number(),
|
|
178
|
+
assigneeId: z.string(),
|
|
179
|
+
description: z.string(),
|
|
180
|
+
offerAgreementId: z
|
|
181
|
+
.string()
|
|
182
|
+
.optional()
|
|
183
|
+
.describe('Explicit open offer to claim for this worker'),
|
|
184
|
+
})),
|
|
185
|
+
kickoff: z
|
|
186
|
+
.boolean()
|
|
187
|
+
.optional()
|
|
188
|
+
.describe('When true and readyForKickoff, also POST /tasks on the hire for relay-coordinator'),
|
|
189
|
+
}, WRITE, async ({ hireAgreementId, chatId, inputArtifactIds, steps, kickoff }) => {
|
|
190
|
+
try {
|
|
191
|
+
const result = await provisionRelayWorkers({
|
|
192
|
+
creds,
|
|
193
|
+
hireAgreementId,
|
|
194
|
+
chatId,
|
|
195
|
+
inputArtifactIds,
|
|
196
|
+
steps,
|
|
197
|
+
});
|
|
198
|
+
const relayTaskBody = buildRelayCoordinatorTaskBody({
|
|
199
|
+
hireAgreementId,
|
|
200
|
+
payload: result.payload,
|
|
201
|
+
});
|
|
202
|
+
let task;
|
|
203
|
+
if (kickoff && result.readyForKickoff) {
|
|
204
|
+
task = await createTask(relayTaskBody, creds);
|
|
205
|
+
}
|
|
206
|
+
return textResult({
|
|
207
|
+
...result,
|
|
208
|
+
relayTaskBody,
|
|
209
|
+
task,
|
|
210
|
+
nextSteps: result.readyForKickoff
|
|
211
|
+
? kickoff && task
|
|
212
|
+
? 'Relay coordinator task created — watch Execution for step progress.'
|
|
213
|
+
: 'All worker agreements active — POST relayTaskBody via createTask or set kickoff=true.'
|
|
214
|
+
: `Worker approval pending on: ${result.pendingApprovals.join(', ')}. Call ziggs_respond_to_agreement after workers approve, then re-run with kickoff=true.`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return toolError(e.message);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function registerConnectionTools(server, creds) {
|
|
223
|
+
registerCapability(server, connectionProxyCapability, creds);
|
|
224
|
+
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. ' +
|
|
225
|
+
'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). ' +
|
|
226
|
+
'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 () => {
|
|
227
|
+
try {
|
|
228
|
+
const connections = await new ConnectionsClient(creds.operatorKey, creds.agentId).listForHolder();
|
|
229
|
+
return textResult({ connections });
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
return toolError(e.message);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
registerCapability(server, requestConnectionCapability, creds);
|
|
236
|
+
}
|
|
208
237
|
export function registerZiggsTools(server, creds, cfg) {
|
|
209
238
|
server.tool('ziggs_auth_status', 'Verify MCP OAuth binding: delegate agent id, owner user id, and org scope. Call after connect before inbox/chats. Includes pendingDecisions summary when approve/reject is waiting. (Renamed from ziggs_connection_status — "connection" now refers only to third-party credential connections, see ziggs_connection_proxy.)', {}, READ_ONLY, async () => {
|
|
210
239
|
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
@@ -346,15 +375,22 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
346
375
|
return toolError(e.message);
|
|
347
376
|
}
|
|
348
377
|
});
|
|
349
|
-
server.tool('ziggs_list_my_agreements', 'List agreements
|
|
378
|
+
server.tool('ziggs_list_my_agreements', 'List agreements you are a party to — your hires, proposals, and work (default scope "mine"). Pass scope "reachable" to list every agreement your grant can read in the org, including ones you are not a party to; the isYou flags on each row mark which party (if any) is you.', {
|
|
379
|
+
scope: z
|
|
380
|
+
.enum(['mine', 'reachable'])
|
|
381
|
+
.optional()
|
|
382
|
+
.describe('mine (default): only agreements where you are a party. reachable: all agreements your grant can read in the org.'),
|
|
350
383
|
proposalStatus: z
|
|
351
384
|
.string()
|
|
352
385
|
.optional()
|
|
353
386
|
.describe('Optional filter: pending, approved, rejected, …'),
|
|
354
|
-
}, READ_ONLY, async ({ proposalStatus }) => {
|
|
387
|
+
}, READ_ONLY, async ({ scope, proposalStatus }) => {
|
|
355
388
|
try {
|
|
356
|
-
const agreements = await getMyAgreements(
|
|
357
|
-
|
|
389
|
+
const agreements = await getMyAgreements({
|
|
390
|
+
...(proposalStatus ? { proposalStatus } : {}),
|
|
391
|
+
partyOnly: scope !== 'reachable',
|
|
392
|
+
}, creds);
|
|
393
|
+
return textResult({ count: agreements.length, scope: scope ?? 'mine', agreements });
|
|
358
394
|
}
|
|
359
395
|
catch (e) {
|
|
360
396
|
return toolError(e.message);
|
|
@@ -380,23 +416,13 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
380
416
|
return toolError(e.message);
|
|
381
417
|
}
|
|
382
418
|
});
|
|
383
|
-
server
|
|
384
|
-
participantId: z.string().describe('User or agent id to converse with'),
|
|
385
|
-
}, WRITE, async ({ participantId }) => {
|
|
386
|
-
try {
|
|
387
|
-
const out = await openConversation(participantId, creds);
|
|
388
|
-
return textResult(out);
|
|
389
|
-
}
|
|
390
|
-
catch (e) {
|
|
391
|
-
return toolError(e.message);
|
|
392
|
-
}
|
|
393
|
-
});
|
|
419
|
+
registerCapability(server, openConversationCapability, creds);
|
|
394
420
|
server.tool('ziggs_send_message', ZIGGS_SEND_MESSAGE_DESCRIPTION, {
|
|
395
421
|
chatId: z.string(),
|
|
396
422
|
receiverId: z
|
|
397
423
|
.string()
|
|
398
424
|
.optional()
|
|
399
|
-
.describe(
|
|
425
|
+
.describe("User or agent id receiving the message. Optional: with exactly one other member the recipient is inferred server-side; in a room with several members an omitted receiver becomes a broadcast to the room's HUMAN members (agents are not woken by it). Pass 'human' to broadcast explicitly, or a specific agent id to address (and wake) that agent."),
|
|
400
426
|
text: z.string(),
|
|
401
427
|
entryType: z
|
|
402
428
|
.string()
|
|
@@ -456,120 +482,9 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
456
482
|
return toolError(e.message);
|
|
457
483
|
}
|
|
458
484
|
});
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
price: z.number().optional(),
|
|
463
|
-
audience: z
|
|
464
|
-
.enum(['everyone', 'org'])
|
|
465
|
-
.optional()
|
|
466
|
-
.describe("'everyone' (default, public) or 'org' (visible/claimable only within your org)"),
|
|
467
|
-
}, WRITE, async ({ description, chatId, price, audience }) => {
|
|
468
|
-
try {
|
|
469
|
-
// Buyer-broadcast: the payer is derived server-side as the creating
|
|
470
|
-
// principal (your side), and providerId is forbidden on broadcasts —
|
|
471
|
-
// the claiming agent fills the open provider side. So we send neither.
|
|
472
|
-
// audience flows straight through; the api-client + backend map it to
|
|
473
|
-
// the proposedTo sentinel and scope on the publisher's org.
|
|
474
|
-
const agreement = await proposeBroadcast({
|
|
475
|
-
description,
|
|
476
|
-
chatId: chatId ?? '',
|
|
477
|
-
price,
|
|
478
|
-
engagementKind: 'service',
|
|
479
|
-
audience: audience ?? 'everyone',
|
|
480
|
-
}, creds);
|
|
481
|
-
return textResult({ agreement });
|
|
482
|
-
}
|
|
483
|
-
catch (e) {
|
|
484
|
-
return toolError(e.message);
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
server.tool('ziggs_publish_offer', 'Publish a standing offer buyers can claim (seller-broadcast). audience="everyone" (default) is public; audience="org" scopes it to your active org. Requires an active org when audience="org".', {
|
|
488
|
-
description: z.string(),
|
|
489
|
-
price: z.number().optional(),
|
|
490
|
-
engagementKind: z.enum(['hire', 'service']).optional(),
|
|
491
|
-
audience: z
|
|
492
|
-
.enum(['everyone', 'org'])
|
|
493
|
-
.optional()
|
|
494
|
-
.describe("'everyone' (default, public) or 'org' (visible/claimable only within your org)"),
|
|
495
|
-
}, WRITE, async ({ description, price, engagementKind, audience }) => {
|
|
496
|
-
try {
|
|
497
|
-
const agreement = await publishOffer({
|
|
498
|
-
description,
|
|
499
|
-
price,
|
|
500
|
-
engagementKind,
|
|
501
|
-
audience: audience ?? 'everyone',
|
|
502
|
-
}, creds);
|
|
503
|
-
return textResult({ offer: agreement });
|
|
504
|
-
}
|
|
505
|
-
catch (e) {
|
|
506
|
-
return toolError(e.message);
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
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.', {
|
|
510
|
-
agreementId: z.string().describe('Open offer agreementId to claim'),
|
|
511
|
-
}, WRITE, async ({ agreementId }) => {
|
|
512
|
-
try {
|
|
513
|
-
const offer = await claimOffer(agreementId, creds);
|
|
514
|
-
return textResult({ offer });
|
|
515
|
-
}
|
|
516
|
-
catch (e) {
|
|
517
|
-
return toolError(e.message);
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
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.', {
|
|
521
|
-
hireAgreementId: z.string(),
|
|
522
|
-
chatId: z
|
|
523
|
-
.string()
|
|
524
|
-
.optional()
|
|
525
|
-
.describe('Required when a step has no standing offer and needs delegation under the hire'),
|
|
526
|
-
inputArtifactIds: z.array(z.string()).optional(),
|
|
527
|
-
steps: z.array(z.object({
|
|
528
|
-
stepId: z.string(),
|
|
529
|
-
order: z.number(),
|
|
530
|
-
assigneeId: z.string(),
|
|
531
|
-
description: z.string(),
|
|
532
|
-
offerAgreementId: z
|
|
533
|
-
.string()
|
|
534
|
-
.optional()
|
|
535
|
-
.describe('Explicit open offer to claim for this worker'),
|
|
536
|
-
})),
|
|
537
|
-
kickoff: z
|
|
538
|
-
.boolean()
|
|
539
|
-
.optional()
|
|
540
|
-
.describe('When true and readyForKickoff, also POST /tasks on the hire for relay-coordinator'),
|
|
541
|
-
}, WRITE, async ({ hireAgreementId, chatId, inputArtifactIds, steps, kickoff }) => {
|
|
542
|
-
try {
|
|
543
|
-
const result = await provisionRelayWorkers({
|
|
544
|
-
creds,
|
|
545
|
-
hireAgreementId,
|
|
546
|
-
chatId,
|
|
547
|
-
inputArtifactIds,
|
|
548
|
-
steps,
|
|
549
|
-
});
|
|
550
|
-
const relayTaskBody = buildRelayCoordinatorTaskBody({
|
|
551
|
-
hireAgreementId,
|
|
552
|
-
payload: result.payload,
|
|
553
|
-
});
|
|
554
|
-
let task;
|
|
555
|
-
if (kickoff && result.readyForKickoff) {
|
|
556
|
-
task = await createTask(relayTaskBody, creds);
|
|
557
|
-
}
|
|
558
|
-
return textResult({
|
|
559
|
-
...result,
|
|
560
|
-
relayTaskBody,
|
|
561
|
-
task,
|
|
562
|
-
nextSteps: result.readyForKickoff
|
|
563
|
-
? kickoff && task
|
|
564
|
-
? 'Relay coordinator task created — watch Execution for step progress.'
|
|
565
|
-
: 'All worker agreements active — POST relayTaskBody via createTask or set kickoff=true.'
|
|
566
|
-
: `Worker approval pending on: ${result.pendingApprovals.join(', ')}. Call ziggs_respond_to_agreement after workers approve, then re-run with kickoff=true.`,
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
catch (e) {
|
|
570
|
-
return toolError(e.message);
|
|
571
|
-
}
|
|
572
|
-
});
|
|
485
|
+
if (!cfg.coreOnly) {
|
|
486
|
+
registerMarketplaceTools(server, creds);
|
|
487
|
+
}
|
|
573
488
|
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).', {
|
|
574
489
|
agreementId: z.string(),
|
|
575
490
|
action: z.enum(['approve', 'reject']),
|
|
@@ -688,145 +603,23 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
688
603
|
return toolError(e.message);
|
|
689
604
|
}
|
|
690
605
|
});
|
|
691
|
-
server
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
.describe('Opaque cursor from a prior nextCursor'),
|
|
704
|
-
limit: z.number().optional().describe('Page size (default server-side)'),
|
|
705
|
-
}, READ_ONLY, async ({ scopeKind, health, cursor, limit }) => {
|
|
706
|
-
try {
|
|
707
|
-
const client = new GrantsClient(creds.operatorKey, creds.agentId);
|
|
708
|
-
const kinds = scopeKind && scopeKind.length
|
|
709
|
-
? scopeKind
|
|
710
|
-
: undefined;
|
|
711
|
-
const { items, nextCursor } = await client.listGrants({
|
|
712
|
-
scopeKind: kinds,
|
|
713
|
-
// Reach = live grants only by default; pass health for expired/revoked.
|
|
714
|
-
health: health ?? 'active',
|
|
715
|
-
cursor,
|
|
716
|
-
limit,
|
|
717
|
-
});
|
|
718
|
-
const unreadable = unreadableGrantRails(creds.operatorKey, kinds);
|
|
719
|
-
return textResult({
|
|
720
|
-
count: items.length,
|
|
721
|
-
grants: items,
|
|
722
|
-
nextCursor,
|
|
723
|
-
...(unreadable && unreadable.length
|
|
724
|
-
? { unreadableRails: unreadable }
|
|
725
|
-
: {}),
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
catch (e) {
|
|
729
|
-
return toolError(e.message);
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
|
-
server.tool('ziggs_expand_context', 'Expand a grant you hold into the chat/agreement ids inside its scope, so you can actually read through it. ziggs_list_grants tells you that you hold e.g. org:acme or agreement:x; this returns the { chats, agreements } (ids + labels only, never content) that scope covers — feed an id to ziggs_read_context (via=chat:<id> / agreement:<id>). Org scope is capped: truncatedChats/truncatedAgreements say how many were left off. Holder-only, grant-fenced.', {
|
|
733
|
-
grantId: z
|
|
734
|
-
.string()
|
|
735
|
-
.describe('A grant you hold (grantId from ziggs_list_grants) to expand'),
|
|
736
|
-
}, READ_ONLY, async ({ grantId }) => {
|
|
737
|
-
try {
|
|
738
|
-
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
739
|
-
return textResult(await client.getReach(grantId));
|
|
740
|
-
}
|
|
741
|
-
catch (e) {
|
|
742
|
-
return toolError(e.message);
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
server.tool('ziggs_discover_grantable', 'See what context EXISTS in your orgs that you CANNOT read yet — so you can ask for it instead of failing blind. Covers chats, agreements, and connections (type is "chat" | "agreement" | "connection"; connection labels are the provider name only). Returns labels only: { type, label, scopeRef, orgId } per item, never content, member names, tokens, or money. Bounded to orgs you have an active agreement in. To act on one, ask your human to grant it, or (if you hold a broader grant of your own) delegate via ziggs_delegate_grant using the scopeRef. Use ziggs_list_grants for what you already hold; this is what you lack.', {}, READ_ONLY, async () => {
|
|
746
|
-
try {
|
|
747
|
-
const client = new ContextDiscoveryClient(creds.operatorKey, creds.agentId);
|
|
748
|
-
const items = await client.discoverGrantable();
|
|
749
|
-
return textResult({ count: items.length, items });
|
|
750
|
-
}
|
|
751
|
-
catch (e) {
|
|
752
|
-
return toolError(e.message);
|
|
753
|
-
}
|
|
606
|
+
registerCapabilities(server, GRANTS_CAPABILITIES, creds);
|
|
607
|
+
registerCapability(server, contextExpandReachCapability, creds);
|
|
608
|
+
registerCapability(server, contextDiscoverGrantableCapability, creds);
|
|
609
|
+
// The read-plan is MCP-local decoration (its next-call tool names and the
|
|
610
|
+
// inbox loop it feeds are this surface's); schema + handler stay shared.
|
|
611
|
+
registerCapability(server, contextReadCapability, creds, {
|
|
612
|
+
transformResult: (result, args) => {
|
|
613
|
+
const readPlan = buildReadContextReadPlan(result, args['type'], args['via'], args['contextGrantId']);
|
|
614
|
+
return readPlan.length
|
|
615
|
+
? { ...result, readPlan }
|
|
616
|
+
: result;
|
|
617
|
+
},
|
|
754
618
|
});
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
.describe('Scope entry, e.g. chat:<id>, agreement:<id>, task:<id>'),
|
|
760
|
-
cursor: z.string().optional().describe('Opaque cursor from prior nextCursor'),
|
|
761
|
-
after: z
|
|
762
|
-
.string()
|
|
763
|
-
.optional()
|
|
764
|
-
.describe('ISO timestamp for forward-delta (messages/artifacts)'),
|
|
765
|
-
direction: z
|
|
766
|
-
.enum(['forward'])
|
|
767
|
-
.optional()
|
|
768
|
-
.describe('Use forward with after for message forward-delta'),
|
|
769
|
-
limit: z.number().optional().describe('Page size (default server-side)'),
|
|
770
|
-
state: z.string().optional().describe('Task state filter (tasks only)'),
|
|
771
|
-
contextGrantId: z
|
|
772
|
-
.string()
|
|
773
|
-
.optional()
|
|
774
|
-
.describe('Pin a specific grant when holding several'),
|
|
775
|
-
}, READ_ONLY, async ({ type, via, cursor, after, direction, limit, state, contextGrantId }) => {
|
|
776
|
-
try {
|
|
777
|
-
const client = new ContextReadClient(creds.operatorKey, creds.agentId);
|
|
778
|
-
const result = await client.read(type, {
|
|
779
|
-
via,
|
|
780
|
-
cursor,
|
|
781
|
-
after,
|
|
782
|
-
direction,
|
|
783
|
-
limit,
|
|
784
|
-
state,
|
|
785
|
-
contextGrantId,
|
|
786
|
-
});
|
|
787
|
-
const readPlan = buildReadContextReadPlan(result, type, via, contextGrantId);
|
|
788
|
-
return textResult(readPlan.length ? { ...result, readPlan } : result);
|
|
789
|
-
}
|
|
790
|
-
catch (e) {
|
|
791
|
-
return toolError(e.message);
|
|
792
|
-
}
|
|
793
|
-
});
|
|
794
|
-
server.tool('ziggs_record_artifact', ZIGGS_RECORD_ARTIFACT_DESCRIPTION, {
|
|
795
|
-
text: z.string().describe('Artifact body'),
|
|
796
|
-
visibility: artifactVisibilitySchema.describe('chat = visible to scope parties; agent-private = delegate-only'),
|
|
797
|
-
chatId: z.string().optional().describe('Target chat (xor agreementId)'),
|
|
798
|
-
agreementId: z
|
|
799
|
-
.string()
|
|
800
|
-
.optional()
|
|
801
|
-
.describe('Target agreement (xor chatId)'),
|
|
802
|
-
taskId: z
|
|
803
|
-
.string()
|
|
804
|
-
.optional()
|
|
805
|
-
.describe('Optional task — creates a TaskArtifactLink alongside the primary scope link'),
|
|
806
|
-
content_type: z.string().optional().describe('Default text'),
|
|
807
|
-
idempotencyKey: z
|
|
808
|
-
.string()
|
|
809
|
-
.optional()
|
|
810
|
-
.describe('Optional dedup key: a redelivered record with the same key no-ops and returns the original artifact. Derive it deterministically (e.g. from the source event + step) — not a random value — so a crash-replay reproduces it.'),
|
|
811
|
-
}, WRITE, async ({ text, visibility, chatId, agreementId, taskId, content_type, idempotencyKey }) => {
|
|
812
|
-
try {
|
|
813
|
-
if ((chatId && agreementId) || (!chatId && !agreementId)) {
|
|
814
|
-
return toolError('Pass exactly one of chatId or agreementId');
|
|
815
|
-
}
|
|
816
|
-
const { artifactId } = await new ArtifactsClient(creds.operatorKey, creds.agentId).writeStrict({ text, visibility, chatId, agreementId, taskId, content_type, idempotencyKey });
|
|
817
|
-
return textResult({
|
|
818
|
-
ok: true,
|
|
819
|
-
artifactId,
|
|
820
|
-
visibility,
|
|
821
|
-
chatId,
|
|
822
|
-
agreementId,
|
|
823
|
-
taskId,
|
|
824
|
-
reportingHint: recordArtifactReportingHint(content_type, taskId),
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
catch (e) {
|
|
828
|
-
return toolError(e.message);
|
|
829
|
-
}
|
|
619
|
+
// Description override keeps the reporting rule sourced from the shared
|
|
620
|
+
// PROTOCOL const (ZIG-557) so it can't drift from SKILL/server instructions.
|
|
621
|
+
registerCapability(server, recordArtifactCapability, creds, {
|
|
622
|
+
description: ZIGGS_RECORD_ARTIFACT_DESCRIPTION,
|
|
830
623
|
});
|
|
831
624
|
// ---------------------------------------------------------------------------
|
|
832
625
|
// Task mutation tools (ZIG-555)
|
|
@@ -935,67 +728,11 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
935
728
|
// ---------------------------------------------------------------------------
|
|
936
729
|
// Connection proxy (ZIG-569)
|
|
937
730
|
// ---------------------------------------------------------------------------
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
"Don't know connectionId/grantId yet? Call ziggs_list_my_connections first.", {
|
|
942
|
-
connectionId: z.string().describe('Connection to act on'),
|
|
943
|
-
grantId: z
|
|
944
|
-
.string()
|
|
945
|
-
.describe('Grant the owner issued to this agent for the connection'),
|
|
946
|
-
action: z.string().describe('Provider action, e.g. repo:read'),
|
|
947
|
-
payload: z
|
|
948
|
-
.record(z.unknown())
|
|
949
|
-
.optional()
|
|
950
|
-
.describe('Action-specific arguments (provider-defined)'),
|
|
951
|
-
}, WRITE, async ({ connectionId, grantId, action, payload }) => {
|
|
952
|
-
try {
|
|
953
|
-
const result = await new ConnectionsClient(creds.operatorKey, creds.agentId).proxy({ connectionId, grantId, action, payload });
|
|
954
|
-
return textResult({ ok: true, action, result });
|
|
955
|
-
}
|
|
956
|
-
catch (e) {
|
|
957
|
-
return toolError(e.message);
|
|
958
|
-
}
|
|
959
|
-
});
|
|
960
|
-
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. ' +
|
|
961
|
-
'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). ' +
|
|
962
|
-
'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 () => {
|
|
963
|
-
try {
|
|
964
|
-
const connections = await listConnectionsForHolder(creds);
|
|
965
|
-
return textResult({ connections });
|
|
966
|
-
}
|
|
967
|
-
catch (e) {
|
|
968
|
-
return toolError(e.message);
|
|
969
|
-
}
|
|
970
|
-
});
|
|
971
|
-
server.tool('ziggs_request_connection', 'Ask your principal (the human) to connect a remote MCP server and grant you the listed tools. ' +
|
|
972
|
-
'Opens a connection-consent agreement as an approvable card in the chat you pass — the human approves it there like any other agreement (there is no MCP tool to approve it, so tell them to approve it in the chat). ' +
|
|
973
|
-
'On approval the server is connected (browser OAuth if needed) and you are granted the tools; the result shows up in ziggs_list_my_connections for use with ziggs_connection_proxy.', {
|
|
974
|
-
chatId: z
|
|
975
|
-
.string()
|
|
976
|
-
.describe('The chat you are working in — the consent card is opened there'),
|
|
977
|
-
serverUrl: z.string().describe('Remote MCP server URL (https)'),
|
|
978
|
-
tools: z
|
|
979
|
-
.array(z.string())
|
|
980
|
-
.describe("Tool names you want — become the grant's allowed_actions caveats"),
|
|
981
|
-
reason: z
|
|
982
|
-
.string()
|
|
983
|
-
.optional()
|
|
984
|
-
.describe('Plain-language reason shown to the human deciding'),
|
|
985
|
-
}, WRITE, async ({ chatId, serverUrl, tools, reason }) => {
|
|
986
|
-
try {
|
|
987
|
-
const result = await new ConnectionsClient(creds.operatorKey, creds.agentId).requestMcpConnection({ chatId, serverUrl, tools, reason });
|
|
988
|
-
return textResult({
|
|
989
|
-
ok: true,
|
|
990
|
-
...result,
|
|
991
|
-
note: 'A connection-consent card is now in the chat awaiting your principal. Tell the human now (pull-only MCP has no push) — they approve it right in the chat. ' +
|
|
992
|
-
'Once approved, the connection + grant appear in ziggs_list_my_connections for ziggs_connection_proxy.',
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
catch (e) {
|
|
996
|
-
return toolError(e.message);
|
|
997
|
-
}
|
|
998
|
-
});
|
|
731
|
+
if (!cfg.coreOnly) {
|
|
732
|
+
registerConnectionTools(server, creds);
|
|
733
|
+
}
|
|
999
734
|
registerTrustTools(server, creds, cfg);
|
|
1000
|
-
|
|
735
|
+
if (!cfg.coreOnly) {
|
|
736
|
+
registerPaymentTools(server, creds);
|
|
737
|
+
}
|
|
1001
738
|
}
|