@ziggs-ai/ziggs-mcp 0.3.2 → 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/dist/capabilityAdapter.d.ts +25 -0
- package/dist/capabilityAdapter.js +75 -0
- package/dist/paymentTools.d.ts +9 -8
- package/dist/paymentTools.js +12 -248
- package/dist/tools.js +26 -269
- package/dist/trustTools.d.ts +4 -1
- package/dist/trustTools.js +14 -272
- package/package.json +2 -2
- package/dist/orgs.d.ts +0 -28
- package/dist/orgs.js +0 -44
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type ZodRawShape } from 'zod';
|
|
3
|
+
import type { CapabilityDefinition, CapabilityParam, Creds } from '@ziggs-ai/api-client';
|
|
4
|
+
/** One JSON text-content result shape for every MCP tool (was copied 3×). */
|
|
5
|
+
export declare function textResult(data: unknown): {
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
};
|
|
11
|
+
export declare function toZodShape(params: Record<string, CapabilityParam>): ZodRawShape;
|
|
12
|
+
export interface RegisterCapabilityOptions {
|
|
13
|
+
/** Web-app origin for human-facing URLs (claim links etc.). */
|
|
14
|
+
webUrl?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Description override for wording assembled from MCP-local shared consts
|
|
17
|
+
* (e.g. the delegate-protocol reporting rule, ZIG-557) — schema and handler
|
|
18
|
+
* still come from the shared definition.
|
|
19
|
+
*/
|
|
20
|
+
description?: string;
|
|
21
|
+
/** Surface-local response decoration (e.g. the read-plan on ziggs_read_context). */
|
|
22
|
+
transformResult?: (result: unknown, args: Record<string, unknown>) => unknown;
|
|
23
|
+
}
|
|
24
|
+
export declare function registerCapability(server: McpServer, cap: CapabilityDefinition, creds: Creds, opts?: RegisterCapabilityOptions): void;
|
|
25
|
+
export declare function registerCapabilities(server: McpServer, caps: CapabilityDefinition[], creds: Creds, opts?: RegisterCapabilityOptions): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
3
|
+
import { toolError } from './toolError.js';
|
|
4
|
+
/** One JSON text-content result shape for every MCP tool (was copied 3×). */
|
|
5
|
+
export function textResult(data) {
|
|
6
|
+
return {
|
|
7
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* ZIG-956 — the MCP surface adapter for shared capability definitions
|
|
12
|
+
* (api-client `capabilities/`). Lowers the neutral param DSL to zod v3 (the
|
|
13
|
+
* MCP SDK's ZodRawShape; api-client itself carries no zod, and agent-sdk is on
|
|
14
|
+
* zod v4, so the schema is defined once and lowered per surface).
|
|
15
|
+
*/
|
|
16
|
+
function zodParam(param) {
|
|
17
|
+
let t;
|
|
18
|
+
switch (param.type) {
|
|
19
|
+
case 'string':
|
|
20
|
+
t = param.enum ? z.enum(param.enum) : z.string();
|
|
21
|
+
break;
|
|
22
|
+
case 'number':
|
|
23
|
+
t = z.number();
|
|
24
|
+
break;
|
|
25
|
+
case 'boolean':
|
|
26
|
+
t = z.boolean();
|
|
27
|
+
break;
|
|
28
|
+
case 'object':
|
|
29
|
+
t = z.record(z.unknown());
|
|
30
|
+
break;
|
|
31
|
+
case 'array': {
|
|
32
|
+
const items = param.items;
|
|
33
|
+
const inner = items?.type === 'string'
|
|
34
|
+
? items.enum
|
|
35
|
+
? z.enum(items.enum)
|
|
36
|
+
: z.string()
|
|
37
|
+
: z.record(z.unknown());
|
|
38
|
+
t = z.array(inner);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (param.description)
|
|
43
|
+
t = t.describe(param.description);
|
|
44
|
+
if (!param.required)
|
|
45
|
+
t = t.optional();
|
|
46
|
+
return t;
|
|
47
|
+
}
|
|
48
|
+
export function toZodShape(params) {
|
|
49
|
+
const shape = {};
|
|
50
|
+
for (const [name, param] of Object.entries(params)) {
|
|
51
|
+
shape[name] = zodParam(param);
|
|
52
|
+
}
|
|
53
|
+
return shape;
|
|
54
|
+
}
|
|
55
|
+
export function registerCapability(server, cap, creds, opts = {}) {
|
|
56
|
+
const annotations = cap.annotation === 'read-only'
|
|
57
|
+
? READ_ONLY
|
|
58
|
+
: cap.annotation === 'destructive'
|
|
59
|
+
? DESTRUCTIVE
|
|
60
|
+
: WRITE;
|
|
61
|
+
server.tool(cap.names.mcp, opts.description ?? cap.descriptions.mcp, toZodShape(cap.params), annotations, async (args) => {
|
|
62
|
+
try {
|
|
63
|
+
const env = { creds, webUrl: opts.webUrl, surface: 'mcp' };
|
|
64
|
+
const result = await cap.handler(args, env);
|
|
65
|
+
return textResult(opts.transformResult ? opts.transformResult(result, args) : result);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
return toolError(e.message);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export function registerCapabilities(server, caps, creds, opts = {}) {
|
|
73
|
+
for (const cap of caps)
|
|
74
|
+
registerCapability(server, cap, creds, opts);
|
|
75
|
+
}
|
package/dist/paymentTools.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type Creds } from '@ziggs-ai/api-client';
|
|
3
3
|
/**
|
|
4
|
-
* ZIG-896 — the wallet toolset on the MCP surface, same base names
|
|
5
|
-
* PAYMENT_TOOLS (payment_* → ziggs_payment_*),
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* ZIG-896 / ZIG-956 — the wallet toolset on the MCP surface, same base names
|
|
5
|
+
* as the SDK's PAYMENT_TOOLS (payment_* → ziggs_payment_*), now registered
|
|
6
|
+
* from the shared capability definitions in api-client (one schema + handler,
|
|
7
|
+
* two thin surface adapters). Safety is unchanged: money-moving calls are
|
|
8
|
+
* policy-gated server-side (transfers above threshold return
|
|
9
|
+
* `approval_required`; the human decides on the wallet page — surfaced by
|
|
10
|
+
* ziggs_pending_decisions), and rails stay gated by the operator key's
|
|
11
|
+
* payments scopes. There is deliberately NO approve/decide tool on either
|
|
12
|
+
* agent surface — an agent must not approve its own spend.
|
|
12
13
|
*/
|
|
13
14
|
export declare function registerPaymentTools(server: McpServer, creds: Creds): void;
|
package/dist/paymentTools.js
CHANGED
|
@@ -1,252 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
4
|
-
import { toolError } from './toolError.js';
|
|
5
|
-
function textResult(data) {
|
|
6
|
-
return {
|
|
7
|
-
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
function buildCaveats(args) {
|
|
11
|
-
const caveats = [];
|
|
12
|
-
if (args.maxAmount != null)
|
|
13
|
-
caveats.push({ type: 'max_amount', value: args.maxAmount });
|
|
14
|
-
if (args.dailyBudget != null)
|
|
15
|
-
caveats.push({ type: 'daily_budget', value: args.dailyBudget });
|
|
16
|
-
if (args.allowedRecipients != null)
|
|
17
|
-
caveats.push({ type: 'allowed_recipients', value: args.allowedRecipients });
|
|
18
|
-
if (args.expiresInSeconds != null)
|
|
19
|
-
caveats.push({ type: 'expires_at', value: Date.now() + args.expiresInSeconds * 1000 });
|
|
20
|
-
return caveats;
|
|
21
|
-
}
|
|
1
|
+
import { PAYMENT_CAPABILITIES } from '@ziggs-ai/api-client';
|
|
2
|
+
import { registerCapabilities } from './capabilityAdapter.js';
|
|
22
3
|
/**
|
|
23
|
-
* ZIG-896 — the wallet toolset on the MCP surface, same base names
|
|
24
|
-
* PAYMENT_TOOLS (payment_* → ziggs_payment_*),
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
4
|
+
* ZIG-896 / ZIG-956 — the wallet toolset on the MCP surface, same base names
|
|
5
|
+
* as the SDK's PAYMENT_TOOLS (payment_* → ziggs_payment_*), now registered
|
|
6
|
+
* from the shared capability definitions in api-client (one schema + handler,
|
|
7
|
+
* two thin surface adapters). Safety is unchanged: money-moving calls are
|
|
8
|
+
* policy-gated server-side (transfers above threshold return
|
|
9
|
+
* `approval_required`; the human decides on the wallet page — surfaced by
|
|
10
|
+
* ziggs_pending_decisions), and rails stay gated by the operator key's
|
|
11
|
+
* payments scopes. There is deliberately NO approve/decide tool on either
|
|
12
|
+
* agent surface — an agent must not approve its own spend.
|
|
31
13
|
*/
|
|
32
14
|
export function registerPaymentTools(server, creds) {
|
|
33
|
-
|
|
34
|
-
server.tool('ziggs_payment_balance', "Check the caller's current wallet balance and available balance (total minus active holds). Use before a transfer to confirm sufficient funds.", {}, READ_ONLY, async () => {
|
|
35
|
-
try {
|
|
36
|
-
return textResult(await client().balance());
|
|
37
|
-
}
|
|
38
|
-
catch (e) {
|
|
39
|
-
return toolError(e.message);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
server.tool('ziggs_payment_resolve_wallet', 'Look up a walletId by userId or agentId. Use before a transfer when you only know the recipient by their platform ID.', {
|
|
43
|
-
userId: z.string().optional().describe('User to resolve'),
|
|
44
|
-
agentId: z.string().optional().describe('Agent to resolve'),
|
|
45
|
-
}, READ_ONLY, async ({ userId, agentId }) => {
|
|
46
|
-
if (!userId && !agentId) {
|
|
47
|
-
return toolError('Provide userId or agentId to resolve a wallet');
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
const wallet = (await client().resolve({ userId, agentId }));
|
|
51
|
-
return textResult({
|
|
52
|
-
walletId: wallet?.['walletId'] || null,
|
|
53
|
-
ownerId: wallet?.['ownerId'] || null,
|
|
54
|
-
currency: wallet?.['currency'] || 'pez',
|
|
55
|
-
status: wallet?.['status'] || null,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
catch (e) {
|
|
59
|
-
return toolError(e.message);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
server.tool('ziggs_payment_transfer', 'Transfer funds to another wallet. Amounts are integer cents. As a delegate you spend under a payment grant the wallet owner issued (paymentGrantId — find yours via ziggs_list_grants scopeKind=wallet). Transfers above the owner\'s policy pause with status "approval_required": the human approves on the wallet page (it also shows in ziggs_pending_decisions) — you can wait inline with ziggs_payment_wait_for_approval, and you must NEVER approve your own transfer.', {
|
|
63
|
-
toWalletId: z
|
|
64
|
-
.string()
|
|
65
|
-
.describe('Destination wal_... id — or a userId/agentId to auto-resolve'),
|
|
66
|
-
amount: z.number().describe('Amount in integer cents, > 0'),
|
|
67
|
-
description: z.string().optional().describe('Human-readable transfer memo'),
|
|
68
|
-
idempotencyKey: z
|
|
69
|
-
.string()
|
|
70
|
-
.optional()
|
|
71
|
-
.describe('Client-supplied key to make retries safe (auto-generated when omitted)'),
|
|
72
|
-
paymentGrantId: z
|
|
73
|
-
.string()
|
|
74
|
-
.optional()
|
|
75
|
-
.describe('Payment grant to spend under (required for agent-impersonated transfers)'),
|
|
76
|
-
}, WRITE, async ({ toWalletId, amount, description, idempotencyKey, paymentGrantId }) => {
|
|
77
|
-
if (!amount || amount <= 0)
|
|
78
|
-
return toolError('amount must be positive');
|
|
79
|
-
try {
|
|
80
|
-
const result = (await client().transfer({
|
|
81
|
-
to: toWalletId,
|
|
82
|
-
amount: Math.round(amount),
|
|
83
|
-
description: description || 'Agent-initiated transfer',
|
|
84
|
-
idempotencyKey,
|
|
85
|
-
paymentGrantId,
|
|
86
|
-
}));
|
|
87
|
-
if (result['status'] === 'approval_required') {
|
|
88
|
-
return textResult({
|
|
89
|
-
status: 'approval_required',
|
|
90
|
-
approvalId: result['approvalId'] || null,
|
|
91
|
-
expiresAt: result['expiresAt'] || null,
|
|
92
|
-
reason: result['reason'] || null,
|
|
93
|
-
amount,
|
|
94
|
-
toWalletId: result['toWalletId'],
|
|
95
|
-
note: 'Transfer paused: the wallet owner must approve this amount on the wallet page (also listed by ziggs_pending_decisions). Tell the human now (pull-only MCP has no push). ' +
|
|
96
|
-
'Wait inline with ziggs_payment_wait_for_approval when you expect a quick decision (≤2 min).',
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
return textResult({
|
|
100
|
-
status: 'transferred',
|
|
101
|
-
transactionId: result['transactionId'] || null,
|
|
102
|
-
amount,
|
|
103
|
-
toWalletId: result['toWalletId'],
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
catch (e) {
|
|
107
|
-
return toolError(e.message);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
server.tool('ziggs_payment_wait_for_approval', 'Poll a paused transfer (status "approval_required") until the human decides or the timeout passes. Returns executed | rejected | expired | timeout | gone. Use for quick decisions (≤2 min); for longer waits, stop and check again next session.', {
|
|
111
|
-
approvalId: z.string().describe('Approval to wait on (from ziggs_payment_transfer)'),
|
|
112
|
-
timeoutMs: z.number().optional().describe('Max wait, default 120000'),
|
|
113
|
-
pollMs: z.number().optional().describe('Poll interval, default 3000 (min 500)'),
|
|
114
|
-
}, READ_ONLY, async ({ approvalId, timeoutMs, pollMs }) => {
|
|
115
|
-
try {
|
|
116
|
-
const result = (await client().waitForApproval(approvalId, {
|
|
117
|
-
timeoutMs,
|
|
118
|
-
pollMs,
|
|
119
|
-
}));
|
|
120
|
-
return textResult({
|
|
121
|
-
status: result['status'],
|
|
122
|
-
approvalId,
|
|
123
|
-
transactionId: result['transactionId'] || null,
|
|
124
|
-
approval: result['approval'] || null,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
catch (e) {
|
|
128
|
-
return toolError(e.message);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
server.tool('ziggs_payment_hold', "Pre-authorize (escrow) funds without moving them. Use to reserve payment at agreement formation; release with ziggs_payment_release once work is complete, or refund if it's cancelled.", {
|
|
132
|
-
amount: z.number().describe('Amount in integer cents, > 0'),
|
|
133
|
-
description: z.string().optional().describe('Human-readable hold memo'),
|
|
134
|
-
idempotencyKey: z.string().optional().describe('Client-supplied retry-safety key'),
|
|
135
|
-
}, WRITE, async ({ amount, description, idempotencyKey }) => {
|
|
136
|
-
if (!amount || amount <= 0)
|
|
137
|
-
return toolError('amount must be positive');
|
|
138
|
-
try {
|
|
139
|
-
const result = (await client().hold({
|
|
140
|
-
amount: Math.round(amount),
|
|
141
|
-
description: description || 'Agent escrow hold',
|
|
142
|
-
idempotencyKey,
|
|
143
|
-
}));
|
|
144
|
-
return textResult({
|
|
145
|
-
status: 'held',
|
|
146
|
-
transactionId: result['transaction']?.['transactionId'] || null,
|
|
147
|
-
amount,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
catch (e) {
|
|
151
|
-
return toolError(e.message);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
server.tool('ziggs_payment_release', "Settle or refund an escrow hold. action='complete' transfers held funds to toWalletId (work done); action='refund' returns funds to the sender (work cancelled).", {
|
|
155
|
-
holdId: z.string().describe('Hold to settle (transactionId from ziggs_payment_hold)'),
|
|
156
|
-
action: z.enum(['complete', 'refund']).describe('complete = pay out, refund = return'),
|
|
157
|
-
toWalletId: z
|
|
158
|
-
.string()
|
|
159
|
-
.optional()
|
|
160
|
-
.describe('Destination wallet — required when action=complete'),
|
|
161
|
-
idempotencyKey: z.string().optional().describe('Client-supplied retry-safety key'),
|
|
162
|
-
}, WRITE, async ({ holdId, action, toWalletId, idempotencyKey }) => {
|
|
163
|
-
if (action === 'complete' && !toWalletId) {
|
|
164
|
-
return toolError('toWalletId is required when action=complete');
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
const result = (await client().release({
|
|
168
|
-
holdId,
|
|
169
|
-
action,
|
|
170
|
-
toWalletId,
|
|
171
|
-
idempotencyKey,
|
|
172
|
-
}));
|
|
173
|
-
return textResult({
|
|
174
|
-
status: action === 'complete' ? 'settled' : 'refunded',
|
|
175
|
-
transactionId: result['transaction']?.['transactionId'] || null,
|
|
176
|
-
holdId,
|
|
177
|
-
action,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
catch (e) {
|
|
181
|
-
return toolError(e.message);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
const grantCaveatArgs = {
|
|
185
|
-
maxAmount: z.number().optional().describe('Per-transfer ceiling in cents'),
|
|
186
|
-
dailyBudget: z.number().optional().describe('Rolling daily budget in cents'),
|
|
187
|
-
allowedRecipients: z
|
|
188
|
-
.array(z.string())
|
|
189
|
-
.optional()
|
|
190
|
-
.describe('Wallet ids the holder may pay'),
|
|
191
|
-
expiresInSeconds: z.number().optional().describe('Grant lifetime from now'),
|
|
192
|
-
};
|
|
193
|
-
server.tool('ziggs_payment_issue_grant', "Issue a payment grant delegating bounded spend from the operator's wallet to an agent holder. Caveats bound what the holder can do (max_amount, daily_budget, allowed_recipients, expiry). The holder spends by passing the grantId as paymentGrantId on transfers.", {
|
|
194
|
-
holderId: z.string().describe('Agent that will hold the grant'),
|
|
195
|
-
...grantCaveatArgs,
|
|
196
|
-
}, WRITE, async ({ holderId, ...caveatArgs }) => {
|
|
197
|
-
try {
|
|
198
|
-
const caveats = buildCaveats(caveatArgs);
|
|
199
|
-
const result = (await client().issueGrant({ holderId, caveats }));
|
|
200
|
-
const grant = result['grant'];
|
|
201
|
-
return textResult({
|
|
202
|
-
grantId: grant?.['grantId'] || null,
|
|
203
|
-
holderId: grant?.['holderId'] || holderId,
|
|
204
|
-
caveats: grant?.['caveats'] || caveats,
|
|
205
|
-
expiresAt: grant?.['expiresAt'] || null,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
catch (e) {
|
|
209
|
-
return toolError(e.message);
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
server.tool('ziggs_payment_attenuate_grant', 'Re-delegate a payment grant you hold to another agent with TIGHTER caveats (narrowing only — the child can never exceed the parent). Use to pass a bounded spend slice to a sub-agent.', {
|
|
213
|
-
grantId: z.string().describe('Parent grant to attenuate'),
|
|
214
|
-
holderId: z.string().describe('Agent that will hold the narrowed grant'),
|
|
215
|
-
...grantCaveatArgs,
|
|
216
|
-
}, WRITE, async ({ grantId, holderId, ...caveatArgs }) => {
|
|
217
|
-
try {
|
|
218
|
-
const caveats = buildCaveats(caveatArgs);
|
|
219
|
-
const result = (await client().attenuateGrant({
|
|
220
|
-
grantId,
|
|
221
|
-
holderId,
|
|
222
|
-
caveats,
|
|
223
|
-
}));
|
|
224
|
-
const grant = result['grant'];
|
|
225
|
-
return textResult({
|
|
226
|
-
grantId: grant?.['grantId'] || null,
|
|
227
|
-
parentGrantId: grant?.['parentGrantId'] || grantId,
|
|
228
|
-
holderId: grant?.['holderId'] || holderId,
|
|
229
|
-
caveats: grant?.['caveats'] || caveats,
|
|
230
|
-
expiresAt: grant?.['expiresAt'] || null,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
catch (e) {
|
|
234
|
-
return toolError(e.message);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
server.tool('ziggs_payment_revoke_grant', 'Revoke a payment grant (and its attenuated children). The holder can no longer spend under it.', {
|
|
238
|
-
grantId: z.string().describe('Grant to revoke'),
|
|
239
|
-
}, DESTRUCTIVE, async ({ grantId }) => {
|
|
240
|
-
try {
|
|
241
|
-
const result = (await client().revokeGrant(grantId));
|
|
242
|
-
return textResult({
|
|
243
|
-
status: 'revoked',
|
|
244
|
-
grantId,
|
|
245
|
-
revoked: result?.['revoked'] ?? null,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
catch (e) {
|
|
249
|
-
return toolError(e.message);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
15
|
+
registerCapabilities(server, PAYMENT_CAPABILITIES, creds);
|
|
252
16
|
}
|
package/dist/tools.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
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
|
-
import { fetchMyOrgs } from './orgs.js';
|
|
6
5
|
import { registerTrustTools } from './trustTools.js';
|
|
7
6
|
import { registerPaymentTools } from './paymentTools.js';
|
|
8
7
|
import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
|
|
@@ -19,6 +18,7 @@ function buildRelayCoordinatorTaskBody(opts) {
|
|
|
19
18
|
}
|
|
20
19
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
21
20
|
import { toolError } from './toolError.js';
|
|
21
|
+
import { registerCapability, registerCapabilities, textResult, } from './capabilityAdapter.js';
|
|
22
22
|
// ZIG-557: the protocol sentences (loop / ack / humanAttention) are sourced
|
|
23
23
|
// from the shared const so this description can't drift from SKILL / server
|
|
24
24
|
// instructions / .cursorrules.
|
|
@@ -47,75 +47,12 @@ const ZIGGS_SEND_MESSAGE_DESCRIPTION = 'Send a chat message as the delegate agen
|
|
|
47
47
|
const ZIGGS_RECORD_ARTIFACT_DESCRIPTION = 'Write an artifact to a chat or agreement scope. Set visibility explicitly. ' +
|
|
48
48
|
'For a finished deliverable, set content_type=result and pass taskId to bind it to the task. ' +
|
|
49
49
|
PROTOCOL.reporting;
|
|
50
|
-
/** Teach the result slot on the record_artifact success path (ZIG-560). */
|
|
51
|
-
function recordArtifactReportingHint(contentType, taskId) {
|
|
52
|
-
if (contentType === 'result') {
|
|
53
|
-
return taskId
|
|
54
|
-
? 'Recorded as a task-bound result artifact. Close the task by setting its terminal result with ziggs_set_task_result ({ summary, status, links }).'
|
|
55
|
-
: '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 }).';
|
|
56
|
-
}
|
|
57
|
-
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.';
|
|
58
|
-
}
|
|
59
|
-
function textResult(data) {
|
|
60
|
-
return {
|
|
61
|
-
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
const contextReadTypeSchema = z.enum([
|
|
65
|
-
'messages',
|
|
66
|
-
'artifacts',
|
|
67
|
-
'agreements',
|
|
68
|
-
'tasks',
|
|
69
|
-
]);
|
|
70
|
-
const artifactVisibilitySchema = z.enum(['chat', 'agent-private']);
|
|
71
50
|
// ZIG-899 — the strict artifact write (fail loudly, return the artifactId)
|
|
72
51
|
// moved into ArtifactsClient.writeStrict, shared with the SDK's record_artifact.
|
|
73
|
-
// ZIG-894 — the leak-guard
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const url = `${getBackendUrl()}/agents/claude-delegate/access`;
|
|
78
|
-
const res = await fetch(url, {
|
|
79
|
-
method: 'GET',
|
|
80
|
-
headers: {
|
|
81
|
-
Authorization: `Bearer ${creds.operatorKey}`,
|
|
82
|
-
'X-Agent-Id': creds.agentId,
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
const body = await res.text().catch(() => '');
|
|
86
|
-
if (!res.ok) {
|
|
87
|
-
throw new Error(`GET /agents/claude-delegate/access ${res.status} ${body.slice(0, 200)}`);
|
|
88
|
-
}
|
|
89
|
-
return body ? JSON.parse(body) : {};
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* ZIG-641 / ZIG-648 — cross-connection discovery over the unified GET /grants:
|
|
93
|
-
* every connection grant this agent holds, grouped by connection so
|
|
94
|
-
* ziggs_connection_proxy's connectionId/grantId no longer has to arrive out of
|
|
95
|
-
* band. `provider` comes from the grant's resolved scope label. The response is
|
|
96
|
-
* scanned defensively for leaked secrets, as ConnectionsClient.proxy does.
|
|
97
|
-
*/
|
|
98
|
-
async function listConnectionsForHolder(creds) {
|
|
99
|
-
const client = new GrantsClient(creds.operatorKey, creds.agentId);
|
|
100
|
-
// All pages of the agent's live connection grants (not just the first page).
|
|
101
|
-
const items = await client.listAllGrants({
|
|
102
|
-
scopeKind: 'connection',
|
|
103
|
-
health: 'active',
|
|
104
|
-
});
|
|
105
|
-
const byConnection = new Map();
|
|
106
|
-
for (const g of items) {
|
|
107
|
-
const connectionId = g.scope.id;
|
|
108
|
-
let group = byConnection.get(connectionId);
|
|
109
|
-
if (!group) {
|
|
110
|
-
group = { connectionId, provider: g.scope.label ?? null, grants: [] };
|
|
111
|
-
byConnection.set(connectionId, group);
|
|
112
|
-
}
|
|
113
|
-
group.grants.push(g);
|
|
114
|
-
}
|
|
115
|
-
const result = [...byConnection.values()];
|
|
116
|
-
assertNoLeakedConnectionSecret(JSON.stringify(result));
|
|
117
|
-
return result;
|
|
118
|
-
}
|
|
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).
|
|
119
56
|
/**
|
|
120
57
|
* Ids this delegate answers for: its own agent id plus its principal's user
|
|
121
58
|
* id (operator-key ownerId / ZIGGS_OWNER_USER_ID). Used to decide which
|
|
@@ -283,67 +220,19 @@ function registerMarketplaceTools(server, creds) {
|
|
|
283
220
|
});
|
|
284
221
|
}
|
|
285
222
|
function registerConnectionTools(server, creds) {
|
|
286
|
-
server
|
|
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
|
-
});
|
|
223
|
+
registerCapability(server, connectionProxyCapability, creds);
|
|
308
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. ' +
|
|
309
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). ' +
|
|
310
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 () => {
|
|
311
227
|
try {
|
|
312
|
-
const connections = await
|
|
228
|
+
const connections = await new ConnectionsClient(creds.operatorKey, creds.agentId).listForHolder();
|
|
313
229
|
return textResult({ connections });
|
|
314
230
|
}
|
|
315
231
|
catch (e) {
|
|
316
232
|
return toolError(e.message);
|
|
317
233
|
}
|
|
318
234
|
});
|
|
319
|
-
server
|
|
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
|
-
});
|
|
235
|
+
registerCapability(server, requestConnectionCapability, creds);
|
|
347
236
|
}
|
|
348
237
|
export function registerZiggsTools(server, creds, cfg) {
|
|
349
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 () => {
|
|
@@ -527,17 +416,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
527
416
|
return toolError(e.message);
|
|
528
417
|
}
|
|
529
418
|
});
|
|
530
|
-
server
|
|
531
|
-
participantId: z.string().describe('User or agent id to converse with'),
|
|
532
|
-
}, WRITE, async ({ participantId }) => {
|
|
533
|
-
try {
|
|
534
|
-
const out = await openConversation(participantId, creds);
|
|
535
|
-
return textResult(out);
|
|
536
|
-
}
|
|
537
|
-
catch (e) {
|
|
538
|
-
return toolError(e.message);
|
|
539
|
-
}
|
|
540
|
-
});
|
|
419
|
+
registerCapability(server, openConversationCapability, creds);
|
|
541
420
|
server.tool('ziggs_send_message', ZIGGS_SEND_MESSAGE_DESCRIPTION, {
|
|
542
421
|
chatId: z.string(),
|
|
543
422
|
receiverId: z
|
|
@@ -724,145 +603,23 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
724
603
|
return toolError(e.message);
|
|
725
604
|
}
|
|
726
605
|
});
|
|
727
|
-
server
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
.describe('Opaque cursor from a prior nextCursor'),
|
|
740
|
-
limit: z.number().optional().describe('Page size (default server-side)'),
|
|
741
|
-
}, READ_ONLY, async ({ scopeKind, health, cursor, limit }) => {
|
|
742
|
-
try {
|
|
743
|
-
const client = new GrantsClient(creds.operatorKey, creds.agentId);
|
|
744
|
-
const kinds = scopeKind && scopeKind.length
|
|
745
|
-
? scopeKind
|
|
746
|
-
: undefined;
|
|
747
|
-
const { items, nextCursor } = await client.listGrants({
|
|
748
|
-
scopeKind: kinds,
|
|
749
|
-
// Reach = live grants only by default; pass health for expired/revoked.
|
|
750
|
-
health: health ?? 'active',
|
|
751
|
-
cursor,
|
|
752
|
-
limit,
|
|
753
|
-
});
|
|
754
|
-
const unreadable = unreadableGrantRails(creds.operatorKey, kinds);
|
|
755
|
-
return textResult({
|
|
756
|
-
count: items.length,
|
|
757
|
-
grants: items,
|
|
758
|
-
nextCursor,
|
|
759
|
-
...(unreadable && unreadable.length
|
|
760
|
-
? { unreadableRails: unreadable }
|
|
761
|
-
: {}),
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
catch (e) {
|
|
765
|
-
return toolError(e.message);
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
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.', {
|
|
769
|
-
grantId: z
|
|
770
|
-
.string()
|
|
771
|
-
.describe('A grant you hold (grantId from ziggs_list_grants) to expand'),
|
|
772
|
-
}, READ_ONLY, async ({ grantId }) => {
|
|
773
|
-
try {
|
|
774
|
-
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
775
|
-
return textResult(await client.getReach(grantId));
|
|
776
|
-
}
|
|
777
|
-
catch (e) {
|
|
778
|
-
return toolError(e.message);
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
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 () => {
|
|
782
|
-
try {
|
|
783
|
-
const client = new ContextDiscoveryClient(creds.operatorKey, creds.agentId);
|
|
784
|
-
const items = await client.discoverGrantable();
|
|
785
|
-
return textResult({ count: items.length, items });
|
|
786
|
-
}
|
|
787
|
-
catch (e) {
|
|
788
|
-
return toolError(e.message);
|
|
789
|
-
}
|
|
790
|
-
});
|
|
791
|
-
server.tool('ziggs_read_context', 'Read the contents of a scope you already hold: messages | artifacts | agreements | tasks (the type param), under via=chat:<id>, agreement:<id>, or task:<id>. Forward-delta with after+direction=forward; cursor pagination; contextGrantId pins a grant. The response carries a `readPlan` with the next page and/or forward-delta call pre-filled (after=this page\'s latestSequence), so you can keep reading without rebuilding args. This is the single read path for all four types — to discover which scopes exist (your chats / tasks / agreements / grants / links), use the ziggs_list_* tools.', {
|
|
792
|
-
type: contextReadTypeSchema.describe('Resource type to read'),
|
|
793
|
-
via: z
|
|
794
|
-
.string()
|
|
795
|
-
.describe('Scope entry, e.g. chat:<id>, agreement:<id>, task:<id>'),
|
|
796
|
-
cursor: z.string().optional().describe('Opaque cursor from prior nextCursor'),
|
|
797
|
-
after: z
|
|
798
|
-
.string()
|
|
799
|
-
.optional()
|
|
800
|
-
.describe('ISO timestamp for forward-delta (messages/artifacts)'),
|
|
801
|
-
direction: z
|
|
802
|
-
.enum(['forward'])
|
|
803
|
-
.optional()
|
|
804
|
-
.describe('Use forward with after for message forward-delta'),
|
|
805
|
-
limit: z.number().optional().describe('Page size (default server-side)'),
|
|
806
|
-
state: z.string().optional().describe('Task state filter (tasks only)'),
|
|
807
|
-
contextGrantId: z
|
|
808
|
-
.string()
|
|
809
|
-
.optional()
|
|
810
|
-
.describe('Pin a specific grant when holding several'),
|
|
811
|
-
}, READ_ONLY, async ({ type, via, cursor, after, direction, limit, state, contextGrantId }) => {
|
|
812
|
-
try {
|
|
813
|
-
const client = new ContextReadClient(creds.operatorKey, creds.agentId);
|
|
814
|
-
const result = await client.read(type, {
|
|
815
|
-
via,
|
|
816
|
-
cursor,
|
|
817
|
-
after,
|
|
818
|
-
direction,
|
|
819
|
-
limit,
|
|
820
|
-
state,
|
|
821
|
-
contextGrantId,
|
|
822
|
-
});
|
|
823
|
-
const readPlan = buildReadContextReadPlan(result, type, via, contextGrantId);
|
|
824
|
-
return textResult(readPlan.length ? { ...result, readPlan } : result);
|
|
825
|
-
}
|
|
826
|
-
catch (e) {
|
|
827
|
-
return toolError(e.message);
|
|
828
|
-
}
|
|
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
|
+
},
|
|
829
618
|
});
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
agreementId: z
|
|
835
|
-
.string()
|
|
836
|
-
.optional()
|
|
837
|
-
.describe('Target agreement (xor chatId)'),
|
|
838
|
-
taskId: z
|
|
839
|
-
.string()
|
|
840
|
-
.optional()
|
|
841
|
-
.describe('Optional task — creates a TaskArtifactLink alongside the primary scope link'),
|
|
842
|
-
content_type: z.string().optional().describe('Default text'),
|
|
843
|
-
idempotencyKey: z
|
|
844
|
-
.string()
|
|
845
|
-
.optional()
|
|
846
|
-
.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.'),
|
|
847
|
-
}, WRITE, async ({ text, visibility, chatId, agreementId, taskId, content_type, idempotencyKey }) => {
|
|
848
|
-
try {
|
|
849
|
-
if ((chatId && agreementId) || (!chatId && !agreementId)) {
|
|
850
|
-
return toolError('Pass exactly one of chatId or agreementId');
|
|
851
|
-
}
|
|
852
|
-
const { artifactId } = await new ArtifactsClient(creds.operatorKey, creds.agentId).writeStrict({ text, visibility, chatId, agreementId, taskId, content_type, idempotencyKey });
|
|
853
|
-
return textResult({
|
|
854
|
-
ok: true,
|
|
855
|
-
artifactId,
|
|
856
|
-
visibility,
|
|
857
|
-
chatId,
|
|
858
|
-
agreementId,
|
|
859
|
-
taskId,
|
|
860
|
-
reportingHint: recordArtifactReportingHint(content_type, taskId),
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
catch (e) {
|
|
864
|
-
return toolError(e.message);
|
|
865
|
-
}
|
|
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,
|
|
866
623
|
});
|
|
867
624
|
// ---------------------------------------------------------------------------
|
|
868
625
|
// Task mutation tools (ZIG-555)
|
package/dist/trustTools.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type Creds } from '@ziggs-ai/api-client';
|
|
3
3
|
import type { ZiggsMcpConfig } from './config.js';
|
|
4
|
-
/** ZIG-433 — agent search + context grant management through MCP.
|
|
4
|
+
/** ZIG-433 / ZIG-956 — agent search + context grant management through MCP.
|
|
5
|
+
* The SDK-twin tools (search/get, links, delegate) come from the shared
|
|
6
|
+
* capability layer; only the human-authority grant tools (issue/revoke) stay
|
|
7
|
+
* MCP-local. */
|
|
5
8
|
export declare function registerTrustTools(server: McpServer, creds: Creds, cfg?: ZiggsMcpConfig): void;
|
package/dist/trustTools.js
CHANGED
|
@@ -1,227 +1,18 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
2
|
+
import { ContextGrantsClient, addChatMember, contextBounds, resolveOrgScopeId, LINK_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, } from '@ziggs-ai/api-client';
|
|
3
|
+
import { WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
5
4
|
import { toolError } from './toolError.js';
|
|
6
|
-
|
|
7
|
-
return {
|
|
8
|
-
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Human/LLM-readable bounds for a context grant. ZIG-646 folded a context
|
|
13
|
-
* grant's temporal mode + read watermark into the canonical `caveats` array,
|
|
14
|
-
* so pull them back out here to keep this tool's `bounds` summary stable.
|
|
15
|
-
*/
|
|
16
|
-
function contextBounds(grant) {
|
|
17
|
-
return {
|
|
18
|
-
temporal: grantCaveat(grant, 'temporal'),
|
|
19
|
-
watermarkAt: grantCaveat(grant, 'watermark_at'),
|
|
20
|
-
expiresAt: grant.expiresAt,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
5
|
+
import { registerCapabilities, registerCapability, textResult } from './capabilityAdapter.js';
|
|
23
6
|
const grantScopeKindSchema = z.enum(['chat', 'agreement', 'org']);
|
|
24
7
|
const contextTemporalSchema = z.enum(['from-now', 'from-start']);
|
|
25
8
|
const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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
|
-
}
|
|
174
|
-
/** ZIG-433 — agent search + context grant management through MCP. */
|
|
9
|
+
/** ZIG-433 / ZIG-956 — agent search + context grant management through MCP.
|
|
10
|
+
* The SDK-twin tools (search/get, links, delegate) come from the shared
|
|
11
|
+
* capability layer; only the human-authority grant tools (issue/revoke) stay
|
|
12
|
+
* MCP-local. */
|
|
175
13
|
export function registerTrustTools(server, creds, cfg) {
|
|
176
14
|
const webUrl = cfg?.ZIGGS_WEB_URL?.replace(/\/$/, '') ?? DEFAULT_WEB_URL;
|
|
177
|
-
server
|
|
178
|
-
query: z.string().describe('Keyword/natural-language search (published store + your org-mates + your linked delegates) OR an exact agent id (resolves that agent even if unpublished, when you can reach it)'),
|
|
179
|
-
limit: z.number().optional().describe('Max results (default server-side)'),
|
|
180
|
-
minScore: z.number().optional().describe('Minimum match score filter'),
|
|
181
|
-
}, READ_ONLY, async ({ query, limit, minScore }) => {
|
|
182
|
-
try {
|
|
183
|
-
const client = new AgentSearchClient(creds.operatorKey, creds.agentId);
|
|
184
|
-
const result = await client.searchAgents(query, { limit, minScore });
|
|
185
|
-
if (!result.success) {
|
|
186
|
-
return toolError(result.error ?? result.message ?? 'search failed');
|
|
187
|
-
}
|
|
188
|
-
if (!result.agents?.length) {
|
|
189
|
-
// ZIG-664: a bare {count: 0} reads as "discovery is down" to LLM
|
|
190
|
-
// callers — say what was searched and how to recover instead.
|
|
191
|
-
return textResult({
|
|
192
|
-
count: 0,
|
|
193
|
-
agents: [],
|
|
194
|
-
searched: ['published store', 'your org-mates', 'your linked delegates'],
|
|
195
|
-
hint: 'Zero hits means no agent profile matched these terms — discovery itself is up. ' +
|
|
196
|
-
'Matching is lexical against agent name/description/tags, so try shorter or different keywords. ' +
|
|
197
|
-
'If you already know the agent, pass its exact agent id as the query to resolve it directly.',
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
return textResult({
|
|
201
|
-
count: result.agents.length,
|
|
202
|
-
agents: result.agents,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
catch (e) {
|
|
206
|
-
return toolError(e.message);
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
server.tool('ziggs_get_agent', 'Fetch the full profile of ONE agent by its exact id (GET /agents/:id) — name, description, tags, capabilities, reachability, and reliability. Use to confirm a candidate before ziggs_propose_agreement / ziggs_request_link, when you already hold the agent id (from ziggs_search_agents, a grant, or an agreement party). Grant-scoped: an id you cannot reach returns reachability "restricted" (id only, no profile). To find an agent by keyword instead, use ziggs_search_agents.', {
|
|
210
|
-
agentId: z.string().describe('Exact agent id to fetch — do not guess'),
|
|
211
|
-
}, READ_ONLY, async ({ agentId }) => {
|
|
212
|
-
try {
|
|
213
|
-
const client = new AgentSearchClient(creds.operatorKey, creds.agentId);
|
|
214
|
-
const result = await client.getAgentById(agentId);
|
|
215
|
-
if (!result.success) {
|
|
216
|
-
return toolError(result.error ?? 'agent not found');
|
|
217
|
-
}
|
|
218
|
-
const { success: _success, ...agent } = result;
|
|
219
|
-
return textResult({ agent });
|
|
220
|
-
}
|
|
221
|
-
catch (e) {
|
|
222
|
-
return toolError(e.message);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
15
|
+
registerCapabilities(server, DISCOVERY_CAPABILITIES, creds);
|
|
225
16
|
server.tool('ziggs_issue_grant', 'Issue bounded context access. Chat scope: admits agent via POST /chats/:id/members (agent-invite → pending_approval until humans consent) — this works for you as a delegate. Agreement/org scope: issuing a NEW root grant is a human-authority action; if you are acting for a principal you are denied (AGENT_LACKS_HUMAN_AUTHORITY) — instead use ziggs_delegate_grant to hand a peer a narrower slice of a grant you already hold, or ask your human to issue it. Defaults: from-now, narrow scope.', {
|
|
226
17
|
holderId: z.string().describe('Bare agent id receiving the grant'),
|
|
227
18
|
scopeKind: grantScopeKindSchema,
|
|
@@ -269,10 +60,7 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
269
60
|
// ZIG-941 #6 — an org scope may be named rather than pasted as org_… id.
|
|
270
61
|
let resolvedScopeId = scopeId;
|
|
271
62
|
if (scopeKind === 'org') {
|
|
272
|
-
|
|
273
|
-
if ('error' in resolved)
|
|
274
|
-
return resolved.error;
|
|
275
|
-
resolvedScopeId = resolved.scopeId;
|
|
63
|
+
resolvedScopeId = await resolveOrgScopeId({ creds, surface: 'mcp' }, scopeId);
|
|
276
64
|
}
|
|
277
65
|
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
278
66
|
const grant = await client.issueGrant({
|
|
@@ -291,58 +79,12 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
291
79
|
return toolError(e.message);
|
|
292
80
|
}
|
|
293
81
|
});
|
|
294
|
-
server
|
|
295
|
-
parentGrantId: z.string(),
|
|
296
|
-
holderId: z.string().describe('Agent receiving the delegated grant'),
|
|
297
|
-
scopeKind: grantScopeKindSchema,
|
|
298
|
-
scopeId: z.string(),
|
|
299
|
-
temporal: contextTemporalSchema.describe('from-now or from-start (must be same-or-narrower)'),
|
|
300
|
-
expiresAt: z.string().optional().nullable(),
|
|
301
|
-
watermarkAt: z
|
|
302
|
-
.string()
|
|
303
|
-
.optional()
|
|
304
|
-
.describe('from-now watermark ISO-8601 (optional; server may default)'),
|
|
305
|
-
}, WRITE, async ({ parentGrantId, holderId, scopeKind, scopeId, temporal, expiresAt, watermarkAt, }) => {
|
|
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
|
-
}
|
|
315
|
-
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
316
|
-
const result = await client.delegateGrant(parentGrantId, {
|
|
317
|
-
holderId,
|
|
318
|
-
scope: { kind: scopeKind, id: resolvedScopeId },
|
|
319
|
-
temporal,
|
|
320
|
-
expiresAt,
|
|
321
|
-
watermarkAt,
|
|
322
|
-
});
|
|
323
|
-
if (result.status === 'pending_approval') {
|
|
324
|
-
return textResult({
|
|
325
|
-
status: 'pending_approval',
|
|
326
|
-
message: "This grant's original owner must approve sharing it. A request was opened for them — surface it to the human; nothing is granted yet.",
|
|
327
|
-
parentGrantId,
|
|
328
|
-
agreementId: result.agreementId,
|
|
329
|
-
ownerId: result.ownerId,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
const grant = result.grant;
|
|
333
|
-
return textResult({
|
|
334
|
-
status: 'delegated',
|
|
335
|
-
parentGrantId,
|
|
336
|
-
grant,
|
|
337
|
-
bounds: contextBounds(grant),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
catch (e) {
|
|
341
|
-
return toolError(e.message);
|
|
342
|
-
}
|
|
343
|
-
});
|
|
82
|
+
registerCapability(server, contextDelegateCapability, creds);
|
|
344
83
|
if (!cfg?.coreOnly) {
|
|
345
|
-
|
|
84
|
+
// ZIG-941 #7 — the link tool group is skipped by the lean session-start
|
|
85
|
+
// tier (ZIGGS_MCP_CORE_ONLY). Definitions live in the shared capability
|
|
86
|
+
// layer, including the linkSummary shaping the mutations now share.
|
|
87
|
+
registerCapabilities(server, LINK_CAPABILITIES, creds, { webUrl });
|
|
346
88
|
}
|
|
347
89
|
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.', {
|
|
348
90
|
grantId: z.string(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server for Claude Code, Cursor, and other MCP hosts — act as your Ziggs delegate agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
|
-
"@ziggs-ai/api-client": "^0.
|
|
39
|
+
"@ziggs-ai/api-client": "^0.4.0",
|
|
40
40
|
"dotenv": "^16.6.1",
|
|
41
41
|
"zod": "^3.24.2"
|
|
42
42
|
},
|
package/dist/orgs.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
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
|
-
}
|