@ziggs-ai/ziggs-mcp 0.3.1 → 0.3.2
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/config.d.ts +13 -0
- package/dist/config.js +11 -2
- package/dist/connectionCreds.js +1 -0
- package/dist/orgs.d.ts +28 -0
- package/dist/orgs.js +44 -0
- package/dist/tools.js +204 -224
- package/dist/trustTools.js +170 -121
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -208,7 +208,7 @@ OP_KEY_A=... AGENT_A=... USER_B=... OP_KEY_B=... AGENT_B=... \
|
|
|
208
208
|
| `ziggs_revoke_agreement` | `DELETE /agreements/:id` — any agreement (hire/service/quest/link) |
|
|
209
209
|
| `ziggs_smoke_impersonation` | [Internal/debug] connectivity check — only when `ZIGGS_MCP_DEBUG=1`; not part of normal delegate workflow |
|
|
210
210
|
| `ziggs_context_snapshot` | `GET /context/snapshot?via=chat:` — one-shot chat orientation (history + agreements + roster), grant-fenced |
|
|
211
|
-
| `ziggs_list_my_agreements` | `GET /agreements?scope=mine` |
|
|
211
|
+
| `ziggs_list_my_agreements` | `GET /agreements?scope=mine&partyOnly=true` — agreements you are a party to; `scope: "reachable"` drops `partyOnly` for every agreement your grant can read |
|
|
212
212
|
| `ziggs_get_agreement` | `GET /agreements/:id` |
|
|
213
213
|
| `ziggs_list_chats` | `GET /chats/mine` |
|
|
214
214
|
| `ziggs_open_conversation` | `POST /chats` |
|
package/dist/config.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ declare const envSchema: z.ZodObject<{
|
|
|
12
12
|
ZIGGS_OWNER_USER_ID: z.ZodOptional<z.ZodString>;
|
|
13
13
|
/** Set to 1/true/yes to register internal/debug-only MCP tools (ZIG-672). */
|
|
14
14
|
ZIGGS_MCP_DEBUG: z.ZodOptional<z.ZodString>;
|
|
15
|
+
/**
|
|
16
|
+
* Set to 1/true/yes to register only the core everyday/session-start tools,
|
|
17
|
+
* skipping the heavy groups (payments, links, marketplace, connections) to
|
|
18
|
+
* cut cold-start deferred-loading (ZIG-941 #7). Unset = all tools register.
|
|
19
|
+
*/
|
|
20
|
+
ZIGGS_MCP_CORE_ONLY: z.ZodOptional<z.ZodString>;
|
|
15
21
|
}, "strip", z.ZodTypeAny, {
|
|
16
22
|
ZIGGS_OPERATOR_KEY: string;
|
|
17
23
|
ZIGGS_API_URL?: string | undefined;
|
|
@@ -20,6 +26,7 @@ declare const envSchema: z.ZodObject<{
|
|
|
20
26
|
ZIGGS_AGENT_ID?: string | undefined;
|
|
21
27
|
ZIGGS_OWNER_USER_ID?: string | undefined;
|
|
22
28
|
ZIGGS_MCP_DEBUG?: string | undefined;
|
|
29
|
+
ZIGGS_MCP_CORE_ONLY?: string | undefined;
|
|
23
30
|
}, {
|
|
24
31
|
ZIGGS_OPERATOR_KEY: string;
|
|
25
32
|
ZIGGS_API_URL?: string | undefined;
|
|
@@ -28,6 +35,7 @@ declare const envSchema: z.ZodObject<{
|
|
|
28
35
|
ZIGGS_AGENT_ID?: string | undefined;
|
|
29
36
|
ZIGGS_OWNER_USER_ID?: string | undefined;
|
|
30
37
|
ZIGGS_MCP_DEBUG?: string | undefined;
|
|
38
|
+
ZIGGS_MCP_CORE_ONLY?: string | undefined;
|
|
31
39
|
}>;
|
|
32
40
|
type EnvConfig = z.infer<typeof envSchema>;
|
|
33
41
|
export interface ZiggsMcpConfig extends EnvConfig {
|
|
@@ -35,6 +43,11 @@ export interface ZiggsMcpConfig extends EnvConfig {
|
|
|
35
43
|
resolvedAgentId: string;
|
|
36
44
|
/** When true, register internal/debug-only tools such as ziggs_smoke_impersonation. */
|
|
37
45
|
debugTools: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* When true, register only the core everyday/session-start tool tier and skip
|
|
48
|
+
* the heavy groups (payments, links, marketplace, connections) — ZIG-941 #7.
|
|
49
|
+
*/
|
|
50
|
+
coreOnly: boolean;
|
|
38
51
|
}
|
|
39
52
|
/** Load delegate credentials from the environment (ZIG-222 / ZIG-430). */
|
|
40
53
|
export declare function loadConfig(): ZiggsMcpConfig;
|
package/dist/config.js
CHANGED
|
@@ -13,8 +13,15 @@ const envSchema = z.object({
|
|
|
13
13
|
ZIGGS_OWNER_USER_ID: z.string().optional(),
|
|
14
14
|
/** Set to 1/true/yes to register internal/debug-only MCP tools (ZIG-672). */
|
|
15
15
|
ZIGGS_MCP_DEBUG: z.string().optional(),
|
|
16
|
+
/**
|
|
17
|
+
* Set to 1/true/yes to register only the core everyday/session-start tools,
|
|
18
|
+
* skipping the heavy groups (payments, links, marketplace, connections) to
|
|
19
|
+
* cut cold-start deferred-loading (ZIG-941 #7). Unset = all tools register.
|
|
20
|
+
*/
|
|
21
|
+
ZIGGS_MCP_CORE_ONLY: z.string().optional(),
|
|
16
22
|
});
|
|
17
|
-
|
|
23
|
+
/** Parse a truthy env flag (1/true/yes) — shared by ZIGGS_MCP_DEBUG and ZIGGS_MCP_CORE_ONLY. */
|
|
24
|
+
function parseBoolFlag(raw) {
|
|
18
25
|
if (!raw)
|
|
19
26
|
return false;
|
|
20
27
|
const v = raw.trim().toLowerCase();
|
|
@@ -30,6 +37,7 @@ export function loadConfig() {
|
|
|
30
37
|
ZIGGS_AGENT_ID: process.env.ZIGGS_AGENT_ID,
|
|
31
38
|
ZIGGS_OWNER_USER_ID: process.env.ZIGGS_OWNER_USER_ID,
|
|
32
39
|
ZIGGS_MCP_DEBUG: process.env.ZIGGS_MCP_DEBUG,
|
|
40
|
+
ZIGGS_MCP_CORE_ONLY: process.env.ZIGGS_MCP_CORE_ONLY,
|
|
33
41
|
};
|
|
34
42
|
const parsed = envSchema.safeParse(raw);
|
|
35
43
|
if (!parsed.success) {
|
|
@@ -49,6 +57,7 @@ export function loadConfig() {
|
|
|
49
57
|
return {
|
|
50
58
|
...parsed.data,
|
|
51
59
|
resolvedAgentId,
|
|
52
|
-
debugTools:
|
|
60
|
+
debugTools: parseBoolFlag(parsed.data.ZIGGS_MCP_DEBUG),
|
|
61
|
+
coreOnly: parseBoolFlag(parsed.data.ZIGGS_MCP_CORE_ONLY),
|
|
53
62
|
};
|
|
54
63
|
}
|
package/dist/connectionCreds.js
CHANGED
package/dist/orgs.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Creds } from '@ziggs-ai/api-client';
|
|
2
|
+
export interface MyOrg {
|
|
3
|
+
orgId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* ZIG-739 — the operator's full org membership (not just granted scopes, which
|
|
10
|
+
* is all ziggs_list_grants sees). Lets the delegate resolve an org name to
|
|
11
|
+
* an id and offer a pick-list instead of demanding a pasted org_... id.
|
|
12
|
+
*/
|
|
13
|
+
export declare function fetchMyOrgs(creds: Creds): Promise<MyOrg[]>;
|
|
14
|
+
export type OrgResolution = {
|
|
15
|
+
status: 'ok';
|
|
16
|
+
orgId: string;
|
|
17
|
+
} | {
|
|
18
|
+
status: 'ambiguous';
|
|
19
|
+
matches: MyOrg[];
|
|
20
|
+
} | {
|
|
21
|
+
status: 'not-found';
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* ZIG-739 — resolve an org selector (exact org_... id OR a name/handle) against
|
|
25
|
+
* the operator's memberships. Exact id wins; otherwise case-insensitive name
|
|
26
|
+
* match. Ambiguous names return the candidates rather than guessing.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveOrgSelector(orgs: MyOrg[], selector: string): OrgResolution;
|
package/dist/orgs.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getBackendUrl } from '@ziggs-ai/api-client';
|
|
2
|
+
/**
|
|
3
|
+
* ZIG-739 — the operator's full org membership (not just granted scopes, which
|
|
4
|
+
* is all ziggs_list_grants sees). Lets the delegate resolve an org name to
|
|
5
|
+
* an id and offer a pick-list instead of demanding a pasted org_... id.
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchMyOrgs(creds) {
|
|
8
|
+
const url = `${getBackendUrl()}/orgs/me`;
|
|
9
|
+
const res = await fetch(url, {
|
|
10
|
+
method: 'GET',
|
|
11
|
+
headers: {
|
|
12
|
+
Authorization: `Bearer ${creds.operatorKey}`,
|
|
13
|
+
'X-Agent-Id': creds.agentId,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const body = await res.text().catch(() => '');
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
throw new Error(`GET /orgs/me ${res.status} ${body.slice(0, 200)}`);
|
|
19
|
+
}
|
|
20
|
+
const parsed = body ? JSON.parse(body) : {};
|
|
21
|
+
return (parsed.orgs ?? []).map((o) => ({
|
|
22
|
+
orgId: o.orgId,
|
|
23
|
+
name: o.name,
|
|
24
|
+
kind: o.kind,
|
|
25
|
+
role: o.role,
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* ZIG-739 — resolve an org selector (exact org_... id OR a name/handle) against
|
|
30
|
+
* the operator's memberships. Exact id wins; otherwise case-insensitive name
|
|
31
|
+
* match. Ambiguous names return the candidates rather than guessing.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveOrgSelector(orgs, selector) {
|
|
34
|
+
const byId = orgs.find((o) => o.orgId === selector);
|
|
35
|
+
if (byId)
|
|
36
|
+
return { status: 'ok', orgId: byId.orgId };
|
|
37
|
+
const needle = selector.toLowerCase();
|
|
38
|
+
const byName = orgs.filter((o) => o.name.toLowerCase() === needle);
|
|
39
|
+
if (byName.length === 1)
|
|
40
|
+
return { status: 'ok', orgId: byName[0].orgId };
|
|
41
|
+
if (byName.length > 1)
|
|
42
|
+
return { status: 'ambiguous', matches: byName };
|
|
43
|
+
return { status: 'not-found' };
|
|
44
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDirectTo, proposeBroadcast, publishOffer, claimOffer, provisionRelayWorkers, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, assertNoLeakedConnectionSecret, ContextDiscoveryClient, ContextReadClient, ContextGrantsClient, GrantsClient, unreadableGrantRails, InboxClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getTask, getBackendUrl, ArtifactsClient, } from '@ziggs-ai/api-client';
|
|
4
4
|
import { decodeOperatorKeyClaims } from './operatorKey.js';
|
|
5
|
+
import { fetchMyOrgs } from './orgs.js';
|
|
5
6
|
import { registerTrustTools } from './trustTools.js';
|
|
6
7
|
import { registerPaymentTools } from './paymentTools.js';
|
|
7
8
|
import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
|
|
@@ -87,49 +88,6 @@ async function fetchDelegateAccess(creds) {
|
|
|
87
88
|
}
|
|
88
89
|
return body ? JSON.parse(body) : {};
|
|
89
90
|
}
|
|
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
91
|
/**
|
|
134
92
|
* ZIG-641 / ZIG-648 — cross-connection discovery over the unified GET /grants:
|
|
135
93
|
* every connection grant this agent holds, grouped by connection so
|
|
@@ -205,6 +163,188 @@ async function loadSessionActionsPayload(creds, cfg, opts) {
|
|
|
205
163
|
withSessionCard: opts?.withSessionCard,
|
|
206
164
|
});
|
|
207
165
|
}
|
|
166
|
+
// ZIG-941 #7 — heavy tool groups pulled out of registerZiggsTools so the
|
|
167
|
+
// lean session-start tier (ZIGGS_MCP_CORE_ONLY) can skip registering them.
|
|
168
|
+
// Registration is otherwise identical to the previous inline definitions.
|
|
169
|
+
function registerMarketplaceTools(server, creds) {
|
|
170
|
+
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.', {
|
|
171
|
+
description: z.string(),
|
|
172
|
+
chatId: z.string().optional(),
|
|
173
|
+
price: z.number().optional(),
|
|
174
|
+
audience: z
|
|
175
|
+
.enum(['everyone', 'org'])
|
|
176
|
+
.optional()
|
|
177
|
+
.describe("'everyone' (default, public) or 'org' (visible/claimable only within your org)"),
|
|
178
|
+
}, WRITE, async ({ description, chatId, price, audience }) => {
|
|
179
|
+
try {
|
|
180
|
+
// Buyer-broadcast: the payer is derived server-side as the creating
|
|
181
|
+
// principal (your side), and providerId is forbidden on broadcasts —
|
|
182
|
+
// the claiming agent fills the open provider side. So we send neither.
|
|
183
|
+
// audience flows straight through; the api-client + backend map it to
|
|
184
|
+
// the proposedTo sentinel and scope on the publisher's org.
|
|
185
|
+
const agreement = await proposeBroadcast({
|
|
186
|
+
description,
|
|
187
|
+
chatId: chatId ?? '',
|
|
188
|
+
price,
|
|
189
|
+
engagementKind: 'service',
|
|
190
|
+
audience: audience ?? 'everyone',
|
|
191
|
+
}, creds);
|
|
192
|
+
return textResult({ agreement });
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
return toolError(e.message);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
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".', {
|
|
199
|
+
description: z.string(),
|
|
200
|
+
price: z.number().optional(),
|
|
201
|
+
engagementKind: z.enum(['hire', 'service']).optional(),
|
|
202
|
+
audience: z
|
|
203
|
+
.enum(['everyone', 'org'])
|
|
204
|
+
.optional()
|
|
205
|
+
.describe("'everyone' (default, public) or 'org' (visible/claimable only within your org)"),
|
|
206
|
+
}, WRITE, async ({ description, price, engagementKind, audience }) => {
|
|
207
|
+
try {
|
|
208
|
+
const agreement = await publishOffer({
|
|
209
|
+
description,
|
|
210
|
+
price,
|
|
211
|
+
engagementKind,
|
|
212
|
+
audience: audience ?? 'everyone',
|
|
213
|
+
}, creds);
|
|
214
|
+
return textResult({ offer: agreement });
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
return toolError(e.message);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
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.', {
|
|
221
|
+
agreementId: z.string().describe('Open offer agreementId to claim'),
|
|
222
|
+
}, WRITE, async ({ agreementId }) => {
|
|
223
|
+
try {
|
|
224
|
+
const offer = await claimOffer(agreementId, creds);
|
|
225
|
+
return textResult({ offer });
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
return toolError(e.message);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
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.', {
|
|
232
|
+
hireAgreementId: z.string(),
|
|
233
|
+
chatId: z
|
|
234
|
+
.string()
|
|
235
|
+
.optional()
|
|
236
|
+
.describe('Required when a step has no standing offer and needs delegation under the hire'),
|
|
237
|
+
inputArtifactIds: z.array(z.string()).optional(),
|
|
238
|
+
steps: z.array(z.object({
|
|
239
|
+
stepId: z.string(),
|
|
240
|
+
order: z.number(),
|
|
241
|
+
assigneeId: z.string(),
|
|
242
|
+
description: z.string(),
|
|
243
|
+
offerAgreementId: z
|
|
244
|
+
.string()
|
|
245
|
+
.optional()
|
|
246
|
+
.describe('Explicit open offer to claim for this worker'),
|
|
247
|
+
})),
|
|
248
|
+
kickoff: z
|
|
249
|
+
.boolean()
|
|
250
|
+
.optional()
|
|
251
|
+
.describe('When true and readyForKickoff, also POST /tasks on the hire for relay-coordinator'),
|
|
252
|
+
}, WRITE, async ({ hireAgreementId, chatId, inputArtifactIds, steps, kickoff }) => {
|
|
253
|
+
try {
|
|
254
|
+
const result = await provisionRelayWorkers({
|
|
255
|
+
creds,
|
|
256
|
+
hireAgreementId,
|
|
257
|
+
chatId,
|
|
258
|
+
inputArtifactIds,
|
|
259
|
+
steps,
|
|
260
|
+
});
|
|
261
|
+
const relayTaskBody = buildRelayCoordinatorTaskBody({
|
|
262
|
+
hireAgreementId,
|
|
263
|
+
payload: result.payload,
|
|
264
|
+
});
|
|
265
|
+
let task;
|
|
266
|
+
if (kickoff && result.readyForKickoff) {
|
|
267
|
+
task = await createTask(relayTaskBody, creds);
|
|
268
|
+
}
|
|
269
|
+
return textResult({
|
|
270
|
+
...result,
|
|
271
|
+
relayTaskBody,
|
|
272
|
+
task,
|
|
273
|
+
nextSteps: result.readyForKickoff
|
|
274
|
+
? kickoff && task
|
|
275
|
+
? 'Relay coordinator task created — watch Execution for step progress.'
|
|
276
|
+
: 'All worker agreements active — POST relayTaskBody via createTask or set kickoff=true.'
|
|
277
|
+
: `Worker approval pending on: ${result.pendingApprovals.join(', ')}. Call ziggs_respond_to_agreement after workers approve, then re-run with kickoff=true.`,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
return toolError(e.message);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function registerConnectionTools(server, creds) {
|
|
286
|
+
server.tool('ziggs_connection_proxy', "Use a stored connection (a third-party credential, e.g. the owner's GitHub/Jira — NOT an agent-to-agent Link, see ziggs_list_links for that) without ever seeing the credential. " +
|
|
287
|
+
'Calls the backend connections proxy with a grant the owner issued to this agent: the proxy enforces the grant, decrypts the token server-side, makes the upstream provider call, and returns the result (token-leak guarded on both sides). ' +
|
|
288
|
+
'Provide connectionId, grantId, the provider action (e.g. repo:read), and an optional action-specific payload. ' +
|
|
289
|
+
"Don't know connectionId/grantId yet? Call ziggs_list_my_connections first.", {
|
|
290
|
+
connectionId: z.string().describe('Connection to act on'),
|
|
291
|
+
grantId: z
|
|
292
|
+
.string()
|
|
293
|
+
.describe('Grant the owner issued to this agent for the connection'),
|
|
294
|
+
action: z.string().describe('Provider action, e.g. repo:read'),
|
|
295
|
+
payload: z
|
|
296
|
+
.record(z.unknown())
|
|
297
|
+
.optional()
|
|
298
|
+
.describe('Action-specific arguments (provider-defined)'),
|
|
299
|
+
}, WRITE, async ({ connectionId, grantId, action, payload }) => {
|
|
300
|
+
try {
|
|
301
|
+
const result = await new ConnectionsClient(creds.operatorKey, creds.agentId).proxy({ connectionId, grantId, action, payload });
|
|
302
|
+
return textResult({ ok: true, action, result });
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
return toolError(e.message);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
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. ' +
|
|
309
|
+
'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). ' +
|
|
310
|
+
'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 () => {
|
|
311
|
+
try {
|
|
312
|
+
const connections = await listConnectionsForHolder(creds);
|
|
313
|
+
return textResult({ connections });
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
return toolError(e.message);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
server.tool('ziggs_request_connection', 'Ask your principal (the human) to connect a remote MCP server and grant you the listed tools. ' +
|
|
320
|
+
'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). ' +
|
|
321
|
+
'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.', {
|
|
322
|
+
chatId: z
|
|
323
|
+
.string()
|
|
324
|
+
.describe('The chat you are working in — the consent card is opened there'),
|
|
325
|
+
serverUrl: z.string().describe('Remote MCP server URL (https)'),
|
|
326
|
+
tools: z
|
|
327
|
+
.array(z.string())
|
|
328
|
+
.describe("Tool names you want — become the grant's allowed_actions caveats"),
|
|
329
|
+
reason: z
|
|
330
|
+
.string()
|
|
331
|
+
.optional()
|
|
332
|
+
.describe('Plain-language reason shown to the human deciding'),
|
|
333
|
+
}, WRITE, async ({ chatId, serverUrl, tools, reason }) => {
|
|
334
|
+
try {
|
|
335
|
+
const result = await new ConnectionsClient(creds.operatorKey, creds.agentId).requestMcpConnection({ chatId, serverUrl, tools, reason });
|
|
336
|
+
return textResult({
|
|
337
|
+
ok: true,
|
|
338
|
+
...result,
|
|
339
|
+
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. ' +
|
|
340
|
+
'Once approved, the connection + grant appear in ziggs_list_my_connections for ziggs_connection_proxy.',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch (e) {
|
|
344
|
+
return toolError(e.message);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
208
348
|
export function registerZiggsTools(server, creds, cfg) {
|
|
209
349
|
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
350
|
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
@@ -346,15 +486,22 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
346
486
|
return toolError(e.message);
|
|
347
487
|
}
|
|
348
488
|
});
|
|
349
|
-
server.tool('ziggs_list_my_agreements', 'List agreements
|
|
489
|
+
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.', {
|
|
490
|
+
scope: z
|
|
491
|
+
.enum(['mine', 'reachable'])
|
|
492
|
+
.optional()
|
|
493
|
+
.describe('mine (default): only agreements where you are a party. reachable: all agreements your grant can read in the org.'),
|
|
350
494
|
proposalStatus: z
|
|
351
495
|
.string()
|
|
352
496
|
.optional()
|
|
353
497
|
.describe('Optional filter: pending, approved, rejected, …'),
|
|
354
|
-
}, READ_ONLY, async ({ proposalStatus }) => {
|
|
498
|
+
}, READ_ONLY, async ({ scope, proposalStatus }) => {
|
|
355
499
|
try {
|
|
356
|
-
const agreements = await getMyAgreements(
|
|
357
|
-
|
|
500
|
+
const agreements = await getMyAgreements({
|
|
501
|
+
...(proposalStatus ? { proposalStatus } : {}),
|
|
502
|
+
partyOnly: scope !== 'reachable',
|
|
503
|
+
}, creds);
|
|
504
|
+
return textResult({ count: agreements.length, scope: scope ?? 'mine', agreements });
|
|
358
505
|
}
|
|
359
506
|
catch (e) {
|
|
360
507
|
return toolError(e.message);
|
|
@@ -396,7 +543,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
396
543
|
receiverId: z
|
|
397
544
|
.string()
|
|
398
545
|
.optional()
|
|
399
|
-
.describe(
|
|
546
|
+
.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
547
|
text: z.string(),
|
|
401
548
|
entryType: z
|
|
402
549
|
.string()
|
|
@@ -456,120 +603,9 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
456
603
|
return toolError(e.message);
|
|
457
604
|
}
|
|
458
605
|
});
|
|
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
|
-
});
|
|
606
|
+
if (!cfg.coreOnly) {
|
|
607
|
+
registerMarketplaceTools(server, creds);
|
|
608
|
+
}
|
|
573
609
|
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
610
|
agreementId: z.string(),
|
|
575
611
|
action: z.enum(['approve', 'reject']),
|
|
@@ -935,67 +971,11 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
935
971
|
// ---------------------------------------------------------------------------
|
|
936
972
|
// Connection proxy (ZIG-569)
|
|
937
973
|
// ---------------------------------------------------------------------------
|
|
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
|
-
});
|
|
974
|
+
if (!cfg.coreOnly) {
|
|
975
|
+
registerConnectionTools(server, creds);
|
|
976
|
+
}
|
|
999
977
|
registerTrustTools(server, creds, cfg);
|
|
1000
|
-
|
|
978
|
+
if (!cfg.coreOnly) {
|
|
979
|
+
registerPaymentTools(server, creds);
|
|
980
|
+
}
|
|
1001
981
|
}
|
package/dist/trustTools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { AgentSearchClient, ContextGrantsClient, createAgreement, listAgreements, revokeAgreement, claimAgreement, addChatMember, grantCaveat, } from '@ziggs-ai/api-client';
|
|
3
|
+
import { fetchMyOrgs, resolveOrgSelector } from './orgs.js';
|
|
3
4
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
4
5
|
import { toolError } from './toolError.js';
|
|
5
6
|
function textResult(data) {
|
|
@@ -22,6 +23,154 @@ function contextBounds(grant) {
|
|
|
22
23
|
const grantScopeKindSchema = z.enum(['chat', 'agreement', 'org']);
|
|
23
24
|
const contextTemporalSchema = z.enum(['from-now', 'from-start']);
|
|
24
25
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
26
|
+
/**
|
|
27
|
+
* ZIG-941 #6 — org-scoped grants may name the org instead of pasting its opaque
|
|
28
|
+
* org_... id. Resolve the selector against the operator's memberships via the
|
|
29
|
+
* (previously unused) resolveOrgSelector: exact id or case-insensitive name.
|
|
30
|
+
* Ambiguous names return the candidate list rather than guessing; a name that
|
|
31
|
+
* matched nothing errors with a pointer to ziggs_list_my_orgs. An org_... id
|
|
32
|
+
* that is not a membership passes through unchanged — you may hold a grant on
|
|
33
|
+
* an org you do not belong to, so the server stays the authority on the id.
|
|
34
|
+
*/
|
|
35
|
+
async function resolveOrgScope(creds, scopeId) {
|
|
36
|
+
const resolution = resolveOrgSelector(await fetchMyOrgs(creds), scopeId);
|
|
37
|
+
if (resolution.status === 'ok')
|
|
38
|
+
return { scopeId: resolution.orgId };
|
|
39
|
+
if (resolution.status === 'ambiguous') {
|
|
40
|
+
return {
|
|
41
|
+
error: toolError(`Org name "${scopeId}" matches ${resolution.matches.length} of your orgs — pass the org id: ` +
|
|
42
|
+
resolution.matches.map((m) => `${m.name} (${m.orgId})`).join(', ')),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (scopeId.startsWith('org_'))
|
|
46
|
+
return { scopeId };
|
|
47
|
+
return {
|
|
48
|
+
error: toolError(`No org named "${scopeId}" in your memberships — use ziggs_list_my_orgs to see them, or pass the org id.`),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// ZIG-941 #7 — the link tool group, pulled out so the lean session-start
|
|
52
|
+
// tier (ZIGGS_MCP_CORE_ONLY) can skip it. Registration is unchanged.
|
|
53
|
+
function registerLinkTools(server, creds, webUrl) {
|
|
54
|
+
server.tool('ziggs_request_link', 'Request a bilateral trust link with another agent before cross-org reach (party-to-party, NOT a third-party service connection — see ziggs_list_my_connections for that). A link is just an agreement (POST /agreements {engagementKind:"link"}). The target OWNER must approve it (via ziggs_respond_to_agreement) before unpublished delegates can message each other.', {
|
|
55
|
+
providerId: z
|
|
56
|
+
.string()
|
|
57
|
+
.describe('Bare agent id to link with (the target delegate). Use ziggs_search_agents or a known delegate id — do not guess.'),
|
|
58
|
+
message: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Optional note shown to the counterparty human on approval (agreement description)'),
|
|
62
|
+
}, WRITE, async ({ providerId, message }) => {
|
|
63
|
+
try {
|
|
64
|
+
const { agreement } = await createAgreement({ engagementKind: 'link', providerId, description: message }, creds);
|
|
65
|
+
return textResult({
|
|
66
|
+
status: 'pending',
|
|
67
|
+
message: 'Link agreement created — the counterparty owner must approve (ziggs_respond_to_agreement) before cross-org reach. Surface pending state to the human.',
|
|
68
|
+
agreement,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return toolError(e.message);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
server.tool('ziggs_create_link_invite', 'Create a shareable OPEN link invite (bilateral agent-to-agent trust, NOT a third-party service connection — see ziggs_list_my_connections for that) when you do NOT have the counterparty\'s agent id (e.g. connecting across orgs). Creates an open link agreement (POST /agreements {engagementKind:"link"}, proposedTo:"everyone"). Share the returned inviteId (agreementId) out-of-band; the recipient forms the link by calling ziggs_claim_link_invite — neither side pastes an agent id. Revoke via ziggs_revoke_link to disable.', {
|
|
76
|
+
message: z
|
|
77
|
+
.string()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe('Optional note shown to whoever opens the invite (agreement description)'),
|
|
80
|
+
}, WRITE, async ({ message }) => {
|
|
81
|
+
try {
|
|
82
|
+
const { agreement } = await createAgreement({ engagementKind: 'link', description: message }, creds);
|
|
83
|
+
return textResult({
|
|
84
|
+
status: 'open',
|
|
85
|
+
inviteId: agreement.agreementId,
|
|
86
|
+
claimUrl: `${webUrl}/app/link-invites/${agreement.agreementId}`,
|
|
87
|
+
message: 'Open link invite created. Share claimUrl with the counterparty — they open it in the Ziggs web app to claim and activate the link. No agent id needed on either side.',
|
|
88
|
+
agreement,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
return toolError(e.message);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
server.tool('ziggs_claim_link_invite', 'Claim an open link invite by its id to form a bilateral link (agent-to-agent trust, NOT a third-party service connection — see ziggs_list_my_connections for that) (POST /agreements/:id/claim). You become the counterparty and the link activates immediately (cross-org reach + bilateral context grants). You cannot claim your own invite.', {
|
|
96
|
+
agreementId: z
|
|
97
|
+
.string()
|
|
98
|
+
.describe('The invite id (agreementId) shared by the issuer'),
|
|
99
|
+
}, WRITE, async ({ agreementId }) => {
|
|
100
|
+
try {
|
|
101
|
+
const { agreement } = await claimAgreement(agreementId, creds);
|
|
102
|
+
return textResult({
|
|
103
|
+
status: 'linked',
|
|
104
|
+
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.',
|
|
105
|
+
agreement,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
return toolError(e.message);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
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.', {
|
|
113
|
+
status: z
|
|
114
|
+
.enum(['active', 'open', 'cancelled', 'all'])
|
|
115
|
+
.optional()
|
|
116
|
+
.describe('active (default) = established links; open = pending proposals/invites awaiting approval or claim; cancelled = revoked; all = every link regardless of status'),
|
|
117
|
+
}, READ_ONLY, async ({ status }) => {
|
|
118
|
+
try {
|
|
119
|
+
const resolvedStatus = status ?? 'active';
|
|
120
|
+
const links = await listAgreements({
|
|
121
|
+
engagementKind: 'link',
|
|
122
|
+
...(resolvedStatus === 'all' ? {} : { status: resolvedStatus }),
|
|
123
|
+
}, creds);
|
|
124
|
+
// ZIG-670: link-shaped summaries, not raw agreement documents — the
|
|
125
|
+
// money block, approvals array, and Mongo internals are noise here.
|
|
126
|
+
const summaries = links.map((a) => ({
|
|
127
|
+
agreementId: a.agreementId,
|
|
128
|
+
status: a.status,
|
|
129
|
+
proposalStatus: a.proposalStatus,
|
|
130
|
+
parties: {
|
|
131
|
+
creatorAgent: a.parties?.creatorAgent ?? null,
|
|
132
|
+
providerAgent: a.parties?.providerAgent ?? null,
|
|
133
|
+
creator: a.parties?.creator ?? null,
|
|
134
|
+
proposedTo: a.parties?.proposedTo ?? null,
|
|
135
|
+
},
|
|
136
|
+
...(a.description ? { description: a.description } : {}),
|
|
137
|
+
createdAt: a.createdAt,
|
|
138
|
+
}));
|
|
139
|
+
const hasActive = links.some((a) => a.status === 'active');
|
|
140
|
+
return textResult({
|
|
141
|
+
count: summaries.length,
|
|
142
|
+
status: resolvedStatus,
|
|
143
|
+
links: summaries,
|
|
144
|
+
...(hasActive
|
|
145
|
+
? {
|
|
146
|
+
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.',
|
|
147
|
+
}
|
|
148
|
+
: {}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
return toolError(e.message);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
server.tool('ziggs_revoke_link', 'Revoke a bilateral link agreement — agent-to-agent trust, NOT a third-party service connection (DELETE /agreements/:agreementId). Either party may revoke; cross-org reach ends immediately. For non-link agreements (hire/service/quest), use ziggs_revoke_agreement — same endpoint, different messaging.', {
|
|
156
|
+
agreementId: z
|
|
157
|
+
.string()
|
|
158
|
+
.describe('agreementId of the link agreement (from ziggs_list_links)'),
|
|
159
|
+
}, DESTRUCTIVE, async ({ agreementId }) => {
|
|
160
|
+
try {
|
|
161
|
+
const result = await revokeAgreement(agreementId, creds);
|
|
162
|
+
return textResult({
|
|
163
|
+
status: 'revoked',
|
|
164
|
+
message: 'Link revoked — unpublished cross-org reach to this peer is blocked again.',
|
|
165
|
+
agreementId,
|
|
166
|
+
agreement: result.agreement,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
return toolError(e.message);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
25
174
|
/** ZIG-433 — agent search + context grant management through MCP. */
|
|
26
175
|
export function registerTrustTools(server, creds, cfg) {
|
|
27
176
|
const webUrl = cfg?.ZIGGS_WEB_URL?.replace(/\/$/, '') ?? DEFAULT_WEB_URL;
|
|
@@ -117,10 +266,18 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
117
266
|
chat: 'chat' in result ? result.chat : undefined,
|
|
118
267
|
});
|
|
119
268
|
}
|
|
269
|
+
// ZIG-941 #6 — an org scope may be named rather than pasted as org_… id.
|
|
270
|
+
let resolvedScopeId = scopeId;
|
|
271
|
+
if (scopeKind === 'org') {
|
|
272
|
+
const resolved = await resolveOrgScope(creds, scopeId);
|
|
273
|
+
if ('error' in resolved)
|
|
274
|
+
return resolved.error;
|
|
275
|
+
resolvedScopeId = resolved.scopeId;
|
|
276
|
+
}
|
|
120
277
|
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
121
278
|
const grant = await client.issueGrant({
|
|
122
279
|
holderId,
|
|
123
|
-
scope: { kind: scopeKind, id:
|
|
280
|
+
scope: { kind: scopeKind, id: resolvedScopeId },
|
|
124
281
|
temporal: resolvedTemporal,
|
|
125
282
|
expiresAt,
|
|
126
283
|
});
|
|
@@ -147,10 +304,18 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
147
304
|
.describe('from-now watermark ISO-8601 (optional; server may default)'),
|
|
148
305
|
}, WRITE, async ({ parentGrantId, holderId, scopeKind, scopeId, temporal, expiresAt, watermarkAt, }) => {
|
|
149
306
|
try {
|
|
307
|
+
// ZIG-941 #6 — an org scope may be named rather than pasted as org_… id.
|
|
308
|
+
let resolvedScopeId = scopeId;
|
|
309
|
+
if (scopeKind === 'org') {
|
|
310
|
+
const resolved = await resolveOrgScope(creds, scopeId);
|
|
311
|
+
if ('error' in resolved)
|
|
312
|
+
return resolved.error;
|
|
313
|
+
resolvedScopeId = resolved.scopeId;
|
|
314
|
+
}
|
|
150
315
|
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
151
316
|
const result = await client.delegateGrant(parentGrantId, {
|
|
152
317
|
holderId,
|
|
153
|
-
scope: { kind: scopeKind, id:
|
|
318
|
+
scope: { kind: scopeKind, id: resolvedScopeId },
|
|
154
319
|
temporal,
|
|
155
320
|
expiresAt,
|
|
156
321
|
watermarkAt,
|
|
@@ -176,125 +341,9 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
176
341
|
return toolError(e.message);
|
|
177
342
|
}
|
|
178
343
|
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.describe('Bare agent id to link with (the target delegate). Use ziggs_search_agents or a known delegate id — do not guess.'),
|
|
183
|
-
message: z
|
|
184
|
-
.string()
|
|
185
|
-
.optional()
|
|
186
|
-
.describe('Optional note shown to the counterparty human on approval (agreement description)'),
|
|
187
|
-
}, WRITE, async ({ providerId, message }) => {
|
|
188
|
-
try {
|
|
189
|
-
const { agreement } = await createAgreement({ engagementKind: 'link', providerId, description: message }, creds);
|
|
190
|
-
return textResult({
|
|
191
|
-
status: 'pending',
|
|
192
|
-
message: 'Link agreement created — the counterparty owner must approve (ziggs_respond_to_agreement) before cross-org reach. Surface pending state to the human.',
|
|
193
|
-
agreement,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
catch (e) {
|
|
197
|
-
return toolError(e.message);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
server.tool('ziggs_create_link_invite', 'Create a shareable OPEN link invite (bilateral agent-to-agent trust, NOT a third-party service connection — see ziggs_list_my_connections for that) when you do NOT have the counterparty\'s agent id (e.g. connecting across orgs). Creates an open link agreement (POST /agreements {engagementKind:"link"}, proposedTo:"everyone"). Share the returned inviteId (agreementId) out-of-band; the recipient forms the link by calling ziggs_claim_link_invite — neither side pastes an agent id. Revoke via ziggs_revoke_link to disable.', {
|
|
201
|
-
message: z
|
|
202
|
-
.string()
|
|
203
|
-
.optional()
|
|
204
|
-
.describe('Optional note shown to whoever opens the invite (agreement description)'),
|
|
205
|
-
}, WRITE, async ({ message }) => {
|
|
206
|
-
try {
|
|
207
|
-
const { agreement } = await createAgreement({ engagementKind: 'link', description: message }, creds);
|
|
208
|
-
return textResult({
|
|
209
|
-
status: 'open',
|
|
210
|
-
inviteId: agreement.agreementId,
|
|
211
|
-
claimUrl: `${webUrl}/app/link-invites/${agreement.agreementId}`,
|
|
212
|
-
message: 'Open link invite created. Share claimUrl with the counterparty — they open it in the Ziggs web app to claim and activate the link. No agent id needed on either side.',
|
|
213
|
-
agreement,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
catch (e) {
|
|
217
|
-
return toolError(e.message);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
server.tool('ziggs_claim_link_invite', 'Claim an open link invite by its id to form a bilateral link (agent-to-agent trust, NOT a third-party service connection — see ziggs_list_my_connections for that) (POST /agreements/:id/claim). You become the counterparty and the link activates immediately (cross-org reach + bilateral context grants). You cannot claim your own invite.', {
|
|
221
|
-
agreementId: z
|
|
222
|
-
.string()
|
|
223
|
-
.describe('The invite id (agreementId) shared by the issuer'),
|
|
224
|
-
}, WRITE, async ({ agreementId }) => {
|
|
225
|
-
try {
|
|
226
|
-
const { agreement } = await claimAgreement(agreementId, creds);
|
|
227
|
-
return textResult({
|
|
228
|
-
status: 'linked',
|
|
229
|
-
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.',
|
|
230
|
-
agreement,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
catch (e) {
|
|
234
|
-
return toolError(e.message);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
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.', {
|
|
238
|
-
status: z
|
|
239
|
-
.enum(['active', 'open', 'cancelled', 'all'])
|
|
240
|
-
.optional()
|
|
241
|
-
.describe('active (default) = established links; open = pending proposals/invites awaiting approval or claim; cancelled = revoked; all = every link regardless of status'),
|
|
242
|
-
}, READ_ONLY, async ({ status }) => {
|
|
243
|
-
try {
|
|
244
|
-
const resolvedStatus = status ?? 'active';
|
|
245
|
-
const links = await listAgreements({
|
|
246
|
-
engagementKind: 'link',
|
|
247
|
-
...(resolvedStatus === 'all' ? {} : { status: resolvedStatus }),
|
|
248
|
-
}, creds);
|
|
249
|
-
// ZIG-670: link-shaped summaries, not raw agreement documents — the
|
|
250
|
-
// money block, approvals array, and Mongo internals are noise here.
|
|
251
|
-
const summaries = links.map((a) => ({
|
|
252
|
-
agreementId: a.agreementId,
|
|
253
|
-
status: a.status,
|
|
254
|
-
proposalStatus: a.proposalStatus,
|
|
255
|
-
parties: {
|
|
256
|
-
creatorAgent: a.parties?.creatorAgent ?? null,
|
|
257
|
-
providerAgent: a.parties?.providerAgent ?? null,
|
|
258
|
-
creator: a.parties?.creator ?? null,
|
|
259
|
-
proposedTo: a.parties?.proposedTo ?? null,
|
|
260
|
-
},
|
|
261
|
-
...(a.description ? { description: a.description } : {}),
|
|
262
|
-
createdAt: a.createdAt,
|
|
263
|
-
}));
|
|
264
|
-
const hasActive = links.some((a) => a.status === 'active');
|
|
265
|
-
return textResult({
|
|
266
|
-
count: summaries.length,
|
|
267
|
-
status: resolvedStatus,
|
|
268
|
-
links: summaries,
|
|
269
|
-
...(hasActive
|
|
270
|
-
? {
|
|
271
|
-
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.',
|
|
272
|
-
}
|
|
273
|
-
: {}),
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
catch (e) {
|
|
277
|
-
return toolError(e.message);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
server.tool('ziggs_revoke_link', 'Revoke a bilateral link agreement — agent-to-agent trust, NOT a third-party service connection (DELETE /agreements/:agreementId). Either party may revoke; cross-org reach ends immediately. For non-link agreements (hire/service/quest), use ziggs_revoke_agreement — same endpoint, different messaging.', {
|
|
281
|
-
agreementId: z
|
|
282
|
-
.string()
|
|
283
|
-
.describe('agreementId of the link agreement (from ziggs_list_links)'),
|
|
284
|
-
}, DESTRUCTIVE, async ({ agreementId }) => {
|
|
285
|
-
try {
|
|
286
|
-
const result = await revokeAgreement(agreementId, creds);
|
|
287
|
-
return textResult({
|
|
288
|
-
status: 'revoked',
|
|
289
|
-
message: 'Link revoked — unpublished cross-org reach to this peer is blocked again.',
|
|
290
|
-
agreementId,
|
|
291
|
-
agreement: result.agreement,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
catch (e) {
|
|
295
|
-
return toolError(e.message);
|
|
296
|
-
}
|
|
297
|
-
});
|
|
344
|
+
if (!cfg?.coreOnly) {
|
|
345
|
+
registerLinkTools(server, creds, webUrl);
|
|
346
|
+
}
|
|
298
347
|
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.', {
|
|
299
348
|
grantId: z.string(),
|
|
300
349
|
}, DESTRUCTIVE, async ({ grantId }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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,12 +36,12 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
|
-
"@ziggs-ai/api-client": "^0.3.
|
|
39
|
+
"@ziggs-ai/api-client": "^0.3.1",
|
|
40
40
|
"dotenv": "^16.6.1",
|
|
41
41
|
"zod": "^3.24.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@ziggs-ai/agent-sdk": "^0.4.
|
|
44
|
+
"@ziggs-ai/agent-sdk": "^0.4.1"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=20"
|