@ziggs-ai/ziggs-mcp 0.9.2 → 0.9.4
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.js +2 -1
- package/dist/config.js +7 -2
- package/dist/connectionCreds.d.ts +1 -1
- package/dist/connectionCreds.js +4 -0
- package/dist/inboxToolResult.js +5 -4
- package/dist/strictParams.d.ts +24 -0
- package/dist/strictParams.js +32 -0
- package/dist/tools.js +24 -23
- package/dist/trustTools.js +3 -2
- package/package.json +3 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
3
|
+
import { registerStrictTool } from './strictParams.js';
|
|
3
4
|
import { toolError } from './toolError.js';
|
|
4
5
|
/** One JSON text-content result shape for every MCP tool (was copied 3×). */
|
|
5
6
|
export function textResult(data) {
|
|
@@ -58,7 +59,7 @@ export function registerCapability(server, cap, creds, opts = {}) {
|
|
|
58
59
|
: cap.annotation === 'destructive'
|
|
59
60
|
? DESTRUCTIVE
|
|
60
61
|
: WRITE;
|
|
61
|
-
server
|
|
62
|
+
registerStrictTool(server, cap.names.mcp, opts.description ?? cap.descriptions.mcp, toZodShape(cap.params), annotations, async (args) => {
|
|
62
63
|
try {
|
|
63
64
|
const env = { creds, webUrl: opts.webUrl, surface: 'mcp' };
|
|
64
65
|
const result = await cap.handler(args, env);
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { configureApiClient } from '@ziggs-ai/api-client';
|
|
3
4
|
import { MINT_KEY_HELP, resolveDelegateAgentId } from './operatorKey.js';
|
|
4
5
|
const envSchema = z.object({
|
|
5
6
|
ZIGGS_API_URL: z.string().optional(),
|
|
@@ -51,8 +52,12 @@ export function loadConfig() {
|
|
|
51
52
|
catch (e) {
|
|
52
53
|
throw e instanceof Error ? e : new Error(String(e));
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
// api-client reads injected config, not the environment (ZIG-652). This
|
|
56
|
+
// server owns its process, so it hands over whichever base URL it resolved.
|
|
57
|
+
const httpUrl = process.env.HTTP_URL || parsed.data.ZIGGS_API_URL;
|
|
58
|
+
if (httpUrl) {
|
|
59
|
+
process.env.HTTP_URL = httpUrl;
|
|
60
|
+
configureApiClient({ httpUrl });
|
|
56
61
|
}
|
|
57
62
|
return {
|
|
58
63
|
...parsed.data,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Creds } from '@ziggs-ai/api-client';
|
|
2
2
|
import type { ZiggsMcpConfig } from './config.js';
|
|
3
3
|
export declare function parseBearerAuthorization(header: string | string[] | undefined): string;
|
|
4
4
|
/** Per-connection credentials from HTTP Authorization (ZIG-431 / ZIG-466). */
|
package/dist/connectionCreds.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { configureApiClient } from '@ziggs-ai/api-client';
|
|
1
2
|
import { resolveDelegateAgentId, MINT_KEY_HELP } from './operatorKey.js';
|
|
2
3
|
export function parseBearerAuthorization(header) {
|
|
3
4
|
const raw = Array.isArray(header) ? header[0] : header;
|
|
@@ -15,6 +16,9 @@ export function connectionFromBearer(bearer, httpBaseUrl, ownerUserId) {
|
|
|
15
16
|
const resolvedAgentId = resolveDelegateAgentId(bearer, undefined);
|
|
16
17
|
if (httpBaseUrl) {
|
|
17
18
|
process.env.HTTP_URL = httpBaseUrl;
|
|
19
|
+
// Same global reach as the env write it replaces (ZIG-652): api-client
|
|
20
|
+
// holds one base URL per process, so the last connection wins here too.
|
|
21
|
+
configureApiClient({ httpUrl: httpBaseUrl });
|
|
18
22
|
}
|
|
19
23
|
const cfg = {
|
|
20
24
|
ZIGGS_OPERATOR_KEY: bearer,
|
package/dist/inboxToolResult.js
CHANGED
|
@@ -125,10 +125,11 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
|
|
|
125
125
|
break;
|
|
126
126
|
case 'task-state':
|
|
127
127
|
case 'agreement':
|
|
128
|
-
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
128
|
+
case 'quest':
|
|
129
|
+
// Deliberately no read call. Tasks/proposals arrive as standing state
|
|
130
|
+
// elsewhere on the envelope; quests (ZIG-1185) ride `questsAwaitingMe`
|
|
131
|
+
// and are host-triaged with a plain string compare — never an LLM read
|
|
132
|
+
// plan entry (that would recreate the per-quest token drain).
|
|
132
133
|
break;
|
|
133
134
|
default:
|
|
134
135
|
// Compile-time exhaustiveness: a kind added to the vocabulary no longer
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { z, type ZodRawShape, type ZodObject } from 'zod';
|
|
4
|
+
/**
|
|
5
|
+
* ZIG-1215 — registering a tool with a plain `ZodRawShape` lets the MCP SDK
|
|
6
|
+
* wrap it in a default `z.object()`, which *strips* unknown keys instead of
|
|
7
|
+
* rejecting them. A misspelled param (`nweChat` for `newChat`) was therefore
|
|
8
|
+
* dropped and the call ran anyway, reporting success for the opposite of what
|
|
9
|
+
* the caller asked for.
|
|
10
|
+
*
|
|
11
|
+
* A human typing that into a form gets a red field; an agent got silence, and
|
|
12
|
+
* silence reads as confirmation — there was no feedback loop to self-correct
|
|
13
|
+
* against. So every tool schema is built strict, and the rejection names both
|
|
14
|
+
* the offending key and the params the tool actually accepts.
|
|
15
|
+
*/
|
|
16
|
+
export type StrictShape<S extends ZodRawShape> = ZodObject<S, 'strict', z.ZodTypeAny>;
|
|
17
|
+
export declare function strictParams<S extends ZodRawShape>(toolName: string, shape: S): StrictShape<S>;
|
|
18
|
+
/**
|
|
19
|
+
* `server.tool(name, description, shape, annotations, cb)` with the shape made
|
|
20
|
+
* strict. The positional `tool()` overloads only accept a raw shape (a built
|
|
21
|
+
* ZodObject is parsed as annotations there), so the strict schema has to go
|
|
22
|
+
* through `registerTool`'s config object.
|
|
23
|
+
*/
|
|
24
|
+
export declare function registerStrictTool<S extends ZodRawShape>(server: McpServer, name: string, description: string, shape: S, annotations: ToolAnnotations, cb: Parameters<typeof server.registerTool<never, StrictShape<S>>>[2]): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function strictParams(toolName, shape) {
|
|
3
|
+
const accepted = Object.keys(shape);
|
|
4
|
+
return z
|
|
5
|
+
.object(shape, {
|
|
6
|
+
// zod's default is "Unrecognized key(s) in object: 'nweChat'" — true but
|
|
7
|
+
// it leaves the caller to guess what the right key was. The issue carries
|
|
8
|
+
// `keys`, so the message can name the typo and the menu next to it.
|
|
9
|
+
errorMap: (issue, ctx) => {
|
|
10
|
+
if (issue.code === z.ZodIssueCode.unrecognized_keys) {
|
|
11
|
+
const offenders = issue.keys.map((k) => `"${k}"`).join(', ');
|
|
12
|
+
const menu = accepted.length ? accepted.join(', ') : '(none)';
|
|
13
|
+
return {
|
|
14
|
+
message: `Unknown parameter(s) ${offenders} — ${toolName} did not run. ` +
|
|
15
|
+
`Accepted params: ${menu}. Check for a typo and call again; ` +
|
|
16
|
+
`nothing was changed.`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return { message: ctx.defaultError };
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
.strict();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `server.tool(name, description, shape, annotations, cb)` with the shape made
|
|
26
|
+
* strict. The positional `tool()` overloads only accept a raw shape (a built
|
|
27
|
+
* ZodObject is parsed as annotations there), so the strict schema has to go
|
|
28
|
+
* through `registerTool`'s config object.
|
|
29
|
+
*/
|
|
30
|
+
export function registerStrictTool(server, name, description, shape, annotations, cb) {
|
|
31
|
+
server.registerTool(name, { description, inputSchema: strictParams(name, shape), annotations }, cb);
|
|
32
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -17,6 +17,7 @@ function buildRelayCoordinatorTaskBody(opts) {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
20
|
+
import { registerStrictTool } from './strictParams.js';
|
|
20
21
|
import { toolError } from './toolError.js';
|
|
21
22
|
import { registerCapability, registerCapabilities, textResult, } from './capabilityAdapter.js';
|
|
22
23
|
// ZIG-557: the protocol sentences (loop / ack / humanAttention) are sourced
|
|
@@ -132,7 +133,7 @@ async function loadSessionActionsPayload(creds, cfg, opts) {
|
|
|
132
133
|
// browse view and the relay-provisioning composite.
|
|
133
134
|
function registerMarketplaceTools(server, creds) {
|
|
134
135
|
registerCapability(server, marketplaceViewCapability, creds);
|
|
135
|
-
server
|
|
136
|
+
registerStrictTool(server, '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.', {
|
|
136
137
|
hireAgreementId: z.string(),
|
|
137
138
|
chatId: z
|
|
138
139
|
.string()
|
|
@@ -188,7 +189,7 @@ function registerMarketplaceTools(server, creds) {
|
|
|
188
189
|
}
|
|
189
190
|
function registerConnectionTools(server, creds) {
|
|
190
191
|
registerCapability(server, connectionProxyCapability, creds);
|
|
191
|
-
server
|
|
192
|
+
registerStrictTool(server, 'ziggs_connection_list', 'Discover the third-party connections (credentials like GitHub/Jira, NOT agent-to-agent Links — see ziggs_link_list for that) you hold grants for (e.g. "is GitHub connected?") without the owner sharing connectionId/grantId out of band. ' +
|
|
192
193
|
'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). ' +
|
|
193
194
|
'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 () => {
|
|
194
195
|
try {
|
|
@@ -202,7 +203,7 @@ function registerConnectionTools(server, creds) {
|
|
|
202
203
|
registerCapability(server, requestConnectionCapability, creds);
|
|
203
204
|
}
|
|
204
205
|
export function registerZiggsTools(server, creds, cfg) {
|
|
205
|
-
server
|
|
206
|
+
registerStrictTool(server, '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. ("Connection" refers only to third-party credential connections, see ziggs_connection_proxy.)', {}, READ_ONLY, async () => {
|
|
206
207
|
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
207
208
|
const webOrigin = resolveWebAppOrigin(cfg.ZIGGS_WEB_URL);
|
|
208
209
|
// ZIG-1120 #4 — delegate access and session actions are independent.
|
|
@@ -276,7 +277,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
276
277
|
: 'Next: ziggs_inbox or ziggs_pending_decisions at session start.',
|
|
277
278
|
});
|
|
278
279
|
});
|
|
279
|
-
server
|
|
280
|
+
registerStrictTool(server, 'ziggs_org_list', 'List every org you (the operator) belong to — { orgId, name, kind, role }. Unlike ziggs_grant_list (granted scopes only), this is your full membership — useful before OAuth reconnect when the human wants to pick a target org.', {}, READ_ONLY, async () => {
|
|
280
281
|
try {
|
|
281
282
|
const orgs = await fetchMyOrgs(creds);
|
|
282
283
|
return textResult({ count: orgs.length, orgs });
|
|
@@ -285,7 +286,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
285
286
|
return toolError(e);
|
|
286
287
|
}
|
|
287
288
|
});
|
|
288
|
-
server
|
|
289
|
+
registerStrictTool(server, 'ziggs_pending_decisions', ZIGGS_PENDING_DECISIONS_DESCRIPTION, {}, READ_ONLY, async () => {
|
|
289
290
|
try {
|
|
290
291
|
const payload = await loadSessionActionsPayload(creds, cfg);
|
|
291
292
|
const decisions = (payload.decisions ?? []);
|
|
@@ -303,7 +304,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
303
304
|
}
|
|
304
305
|
});
|
|
305
306
|
if (cfg.debugTools) {
|
|
306
|
-
server
|
|
307
|
+
registerStrictTool(server, 'ziggs_smoke_impersonation', '[Internal/debug] Connectivity check for the operator-key impersonation path — lists agreements and snapshots the first chat. Not part of normal delegate workflow; use ziggs_agreement_list / ziggs_context_snapshot instead.', {}, READ_ONLY, async () => {
|
|
307
308
|
try {
|
|
308
309
|
const agreements = await getMyAgreements({}, creds);
|
|
309
310
|
const chats = await listMyChats(creds);
|
|
@@ -325,7 +326,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
325
326
|
}
|
|
326
327
|
});
|
|
327
328
|
}
|
|
328
|
-
server
|
|
329
|
+
registerStrictTool(server, 'ziggs_context_snapshot', 'One-shot orientation for a chat: history, agreements (with which party is you), and the roster of agents/users — grant-fenced. Use when entering a chat you have not read yet; follow up with ziggs_context_read forward deltas from the returned latestSequence.', {
|
|
329
330
|
chatId: z.string().describe('Chat id to snapshot'),
|
|
330
331
|
maxMessages: z.number().optional().describe('Optional message history cap'),
|
|
331
332
|
contextGrantId: z
|
|
@@ -345,7 +346,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
345
346
|
return toolError(e);
|
|
346
347
|
}
|
|
347
348
|
});
|
|
348
|
-
server
|
|
349
|
+
registerStrictTool(server, 'ziggs_agreement_list', '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.', {
|
|
349
350
|
scope: z
|
|
350
351
|
.enum(['mine', 'reachable'])
|
|
351
352
|
.optional()
|
|
@@ -366,7 +367,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
366
367
|
return toolError(e);
|
|
367
368
|
}
|
|
368
369
|
});
|
|
369
|
-
server
|
|
370
|
+
registerStrictTool(server, 'ziggs_agreement_get', 'Fetch a single agreement by id.', { agreementId: z.string() }, READ_ONLY, async ({ agreementId }) => {
|
|
370
371
|
try {
|
|
371
372
|
const agreement = await getAgreement(agreementId, creds);
|
|
372
373
|
if (!agreement)
|
|
@@ -377,7 +378,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
377
378
|
return toolError(e);
|
|
378
379
|
}
|
|
379
380
|
});
|
|
380
|
-
server
|
|
381
|
+
registerStrictTool(server, 'ziggs_chat_list', 'List chats the delegate agent is a member of (GET /chats/mine).', {}, READ_ONLY, async () => {
|
|
381
382
|
try {
|
|
382
383
|
const chats = await listMyChats(creds);
|
|
383
384
|
return textResult({ count: chats.length, chats });
|
|
@@ -387,7 +388,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
387
388
|
}
|
|
388
389
|
});
|
|
389
390
|
registerCapability(server, openConversationCapability, creds);
|
|
390
|
-
server
|
|
391
|
+
registerStrictTool(server, 'ziggs_chat_send', ZIGGS_SEND_MESSAGE_DESCRIPTION, {
|
|
391
392
|
chatId: z.string(),
|
|
392
393
|
receiverId: z
|
|
393
394
|
.string()
|
|
@@ -423,7 +424,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
423
424
|
return toolError(e);
|
|
424
425
|
}
|
|
425
426
|
});
|
|
426
|
-
server
|
|
427
|
+
registerStrictTool(server, 'ziggs_agreement_propose', 'Propose an agreement — direct, broadcast, hand-off, or link; there are no separate publish tools. DIRECT: proposedTo = one counterparty id, chatId required. Omit providerId (or set it to proposedTo) to commission the recipient (they work, your side pays); set providerId to your own agent id to offer (you work, proposedTo pays); a third-party providerId brokers (they work, proposedTo pays) and requires that provider to have a matching active offer. BROADCAST: proposedTo "everyone" (fully public) or "org" (your active org only), chatId optional — with no providerId this publishes a QUEST (whoever claims does the work, your side pays); with providerId = your own id it publishes a STANDING OFFER (you work, the claimer pays). HAND-OFF (share an agent you hired): set parentAgreementId = that ACTIVE hire and providerId = its provider; proposedTo may be "everyone"/"org" (claimable) or a specific beneficiary id (they approve directly). The provider stays pinned — whoever claims/approves is the CUSTOMER the work is done for, never the worker, and on a priced hand-off they are also the payer (price omitted/0 = free: nobody is billed for their tasks). Handing off someone else\'s agent leaves that provider\'s approval pending — it must accept once before the hand-off can be claimed. Claiming is ziggs_agreement_claim; browsing is ziggs_marketplace_view. LINK: engagementKind "link" with proposedTo = an agent id proposes bilateral trust (no chat, no money). The server routes parties.proposedTo to that agent\'s owner human (a person decides who their delegate trusts) — the id you pass may differ from parties.proposedTo in the response; when it does, `note` explains the rewrite. Approve via ziggs_agreement_respond. ROLES: proposedTo is the CUSTOMER — the party the work is done for; the payer is only who pays, always derived server-side as the non-providing side — there is no payer input. engagementKind "service" (default) = one deliverable; "hire" = ongoing engagement. Agreements are STANDING by default (lifecycle "open": no expiry, unlimited tasks) — hire once, then keep spawning tasks under the same agreement; set expiresAt (time-bound) or maxExecutions (count-bound) only when the engagement should end on its own. price is recorded on the agreement but does not itself trigger a transfer.', {
|
|
427
428
|
proposedTo: z
|
|
428
429
|
.string()
|
|
429
430
|
.describe('Counterparty id for a direct proposal, or "everyone"/"org" to broadcast'),
|
|
@@ -503,7 +504,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
503
504
|
}
|
|
504
505
|
});
|
|
505
506
|
registerCapability(server, agreementClaimCapability, creds);
|
|
506
|
-
server
|
|
507
|
+
registerStrictTool(server, 'ziggs_agreement_subcontract', 'Delegate part of an engagement to another agent under an existing parent agreement (a sub-agreement; the worker must approve — never impersonated). Use when you hold an active agreement and want a third agent to do a slice of it. Requires parentAgreementId and the chat you are coordinating in. Spawn tasks for the worker under the sub-agreement once it is active.', {
|
|
507
508
|
parentAgreementId: z.string().describe('The active agreement you are delegating under'),
|
|
508
509
|
executorId: z.string().describe('Agent doing the delegated work'),
|
|
509
510
|
chatId: z.string().describe('Chat the delegation is coordinated in'),
|
|
@@ -541,7 +542,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
541
542
|
if (!cfg.coreOnly) {
|
|
542
543
|
registerMarketplaceTools(server, creds);
|
|
543
544
|
}
|
|
544
|
-
server
|
|
545
|
+
registerStrictTool(server, 'ziggs_agreement_respond', 'Approve or reject a pending DIRECT agreement proposal addressed to YOU — your own party slot (PUT /approvals/:partyId, which takes a decision only from the party itself). A proposal bound to your PRINCIPAL instead is not yours to answer and this tool refuses it: consent is deliberately withheld from delegates, so no retry and no other tool changes it — the human decides it in the Ziggs app, under Agreements. ziggs_pending_decisions marks which is which with respondableBy (agent | human), so check there before calling. Open broadcasts (quests, standing offers, link invites) have no personal approval slot — claim those with ziggs_agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU (or your agent) as provider carries your pending approval even as an open broadcast — approving it consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off.', {
|
|
545
546
|
agreementId: z.string(),
|
|
546
547
|
action: z.enum(['approve', 'reject']),
|
|
547
548
|
}, WRITE, async ({ agreementId, action }) => {
|
|
@@ -559,7 +560,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
559
560
|
return toolError(e);
|
|
560
561
|
}
|
|
561
562
|
});
|
|
562
|
-
server
|
|
563
|
+
registerStrictTool(server, 'ziggs_agreement_revoke', 'Revoke any agreement you are a party to — hire, service, quest, standing offer, or link (DELETE /agreements/:id). Either party may revoke; this ends the engagement immediately. Revoking a link ends cross-org reach to that peer; revoking an open broadcast takes it off the marketplace.', {
|
|
563
564
|
agreementId: z.string().describe('Agreement to revoke'),
|
|
564
565
|
}, DESTRUCTIVE, async ({ agreementId }) => {
|
|
565
566
|
try {
|
|
@@ -578,7 +579,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
578
579
|
return toolError(e);
|
|
579
580
|
}
|
|
580
581
|
});
|
|
581
|
-
server
|
|
582
|
+
registerStrictTool(server, 'ziggs_agreement_counter', 'Counter a pending proposal with revised terms instead of approving or rejecting (POST /agreements/:id/counter). Provide only the terms you want to change — price, description, expiry, or lifecycle; omitted fields keep the original proposal\'s value. The counter goes back to the counterparty as a fresh pending proposal for them to approve/reject/counter. Read the current terms first with ziggs_agreement_get.', {
|
|
582
583
|
agreementId: z.string().describe('The pending agreement to counter'),
|
|
583
584
|
price: z
|
|
584
585
|
.number()
|
|
@@ -607,7 +608,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
607
608
|
return toolError(e);
|
|
608
609
|
}
|
|
609
610
|
});
|
|
610
|
-
server
|
|
611
|
+
registerStrictTool(server, 'ziggs_agreement_fulfill', 'END an agreement you PROVIDE — permanently (POST /agreements/:id/fulfill). Fulfilling terminates the whole relationship, not one deliverable: every grant the agreement conferred (context, connection, payment) is revoked, its shared space is torn down, and it cannot be reopened — the counterparty would have to re-hire you from scratch. Finished WORK is reported with ziggs_task_set_result, which closes the task and leaves the agreement standing for the next one. Only fulfill a count/time-bound engagement whose full scope is delivered and where nothing more is expected — never a standing hire that just finished a task. Party-gated server-side: only the providing side can fulfill.', {
|
|
611
612
|
agreementId: z.string().describe('The agreement you provide, to mark fulfilled'),
|
|
612
613
|
}, WRITE, async ({ agreementId }) => {
|
|
613
614
|
try {
|
|
@@ -618,7 +619,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
618
619
|
return toolError(e);
|
|
619
620
|
}
|
|
620
621
|
});
|
|
621
|
-
server
|
|
622
|
+
registerStrictTool(server, 'ziggs_inbox', ZIGGS_INBOX_DESCRIPTION, {
|
|
622
623
|
ack: z
|
|
623
624
|
.string()
|
|
624
625
|
.optional()
|
|
@@ -689,7 +690,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
689
690
|
// ---------------------------------------------------------------------------
|
|
690
691
|
// Task mutation tools (ZIG-555)
|
|
691
692
|
// ---------------------------------------------------------------------------
|
|
692
|
-
server
|
|
693
|
+
registerStrictTool(server, 'ziggs_task_create', 'Create a task under an agreement. Every task belongs to exactly one agreement (agreementId required). Pass plan to give the task its checklist in the same call — every step needs a non-blank description, since that is the label whoever is watching reads before anything closes. Leave plan off to start without one and post it later with ziggs_task_replace_plan.', {
|
|
693
694
|
agreementId: z.string().describe('Agreement this task belongs to'),
|
|
694
695
|
description: z.string().describe('What the task entails'),
|
|
695
696
|
parentTaskId: z.string().optional().describe('Parent task id for sub-tasks'),
|
|
@@ -744,7 +745,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
744
745
|
return toolError(e);
|
|
745
746
|
}
|
|
746
747
|
});
|
|
747
|
-
server
|
|
748
|
+
registerStrictTool(server, 'ziggs_task_set_result', 'Transition a task to a terminal state (completed / failed / cancelled) and record the result. Enforces the state machine — only active tasks can be transitioned.', {
|
|
748
749
|
taskId: z.string(),
|
|
749
750
|
state: z.enum(['completed', 'failed', 'cancelled']),
|
|
750
751
|
result: z
|
|
@@ -770,7 +771,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
770
771
|
return toolError(e);
|
|
771
772
|
}
|
|
772
773
|
});
|
|
773
|
-
server
|
|
774
|
+
registerStrictTool(server, 'ziggs_task_replace_plan', 'Replace the plan for a task with the full ordered step list you provide — existing steps are replaced wholesale, not appended to. Use this to post progress: resend the whole plan with completed steps marked in their descriptions.', {
|
|
774
775
|
taskId: z.string(),
|
|
775
776
|
steps: z
|
|
776
777
|
.array(z.object({
|
|
@@ -788,7 +789,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
788
789
|
return toolError(e);
|
|
789
790
|
}
|
|
790
791
|
});
|
|
791
|
-
server
|
|
792
|
+
registerStrictTool(server, 'ziggs_task_list', 'List tasks reachable by this delegate agent (GET /tasks). Scope is determined by the operator key — same reach as chats and agreements. Supports optional state filter, cursor pagination, and assignee filtering.', {
|
|
792
793
|
state: z
|
|
793
794
|
.string()
|
|
794
795
|
.optional()
|
|
@@ -813,7 +814,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
813
814
|
return toolError(e);
|
|
814
815
|
}
|
|
815
816
|
});
|
|
816
|
-
server
|
|
817
|
+
registerStrictTool(server, 'ziggs_task_get', 'Fetch a single task by id (GET /tasks/:id). Use this when a human hands you a taskId directly (e.g. "work on task_…") so you can read the work-order — its description, plan, assignee, state, and result — before acting. Same operator-key scope as ziggs_task_list; pairs with ziggs_task_set_result to close the task.', { taskId: z.string() }, READ_ONLY, async ({ taskId }) => {
|
|
817
818
|
try {
|
|
818
819
|
const task = await getTask(taskId, creds);
|
|
819
820
|
if (!task)
|
package/dist/trustTools.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ContextGrantsClient, addChatMember, contextBounds, resolveOrgScopeId, LINK_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, } from '@ziggs-ai/api-client';
|
|
3
3
|
import { WRITE, DESTRUCTIVE } from './toolAnnotations.js';
|
|
4
|
+
import { registerStrictTool } from './strictParams.js';
|
|
4
5
|
import { toolError } from './toolError.js';
|
|
5
6
|
import { registerCapabilities, registerCapability, textResult } from './capabilityAdapter.js';
|
|
6
7
|
// ZIG-1037: `artifact` is the narrowest context scope — one specific artifact,
|
|
@@ -15,7 +16,7 @@ const DEFAULT_WEB_URL = 'https://ziggsai.com';
|
|
|
15
16
|
export function registerTrustTools(server, creds, cfg) {
|
|
16
17
|
const webUrl = cfg?.ZIGGS_WEB_URL?.replace(/\/$/, '') ?? DEFAULT_WEB_URL;
|
|
17
18
|
registerCapabilities(server, DISCOVERY_CAPABILITIES, creds);
|
|
18
|
-
server
|
|
19
|
+
registerStrictTool(server, 'ziggs_context_issue_grant', 'Issue bounded context access. ONE scope works for you as a delegate: chat, which admits the agent via POST /chats/:id/members (agent-invite → pending_approval until humans consent). Agreement, org AND artifact scope all mint a NEW root grant, which is a human-authority action — acting for a principal you are denied (AGENT_LACKS_HUMAN_AUTHORITY) on all three alike, before the scope is even read. Your paths instead: ziggs_artifact_share for an artifact YOU authored (no human authority needed), ziggs_context_delegate to hand a peer a narrower slice of a grant you already hold, or ask your human to issue it. Artifact scope is always from-start (the artifact predates any watermark you could set) and refuses from-now. Defaults: from-now, narrow scope.', {
|
|
19
20
|
holderId: z.string().describe('Bare agent id receiving the grant'),
|
|
20
21
|
scopeKind: grantScopeKindSchema,
|
|
21
22
|
scopeId: z.string().describe('chatId, agreementId, orgId, or artifactId'),
|
|
@@ -95,7 +96,7 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
95
96
|
// layer, including the linkSummary shaping the mutations now share.
|
|
96
97
|
registerCapabilities(server, LINK_CAPABILITIES, creds, { webUrl });
|
|
97
98
|
}
|
|
98
|
-
server
|
|
99
|
+
registerStrictTool(server, 'ziggs_context_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.', {
|
|
99
100
|
grantId: z.string(),
|
|
100
101
|
}, DESTRUCTIVE, async ({ grantId }) => {
|
|
101
102
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "MCP server for Claude Code, Cursor, and other MCP hosts
|
|
3
|
+
"version": "0.9.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": {
|
|
7
7
|
"ziggs-mcp": "./dist/index.js"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
|
-
"@ziggs-ai/api-client": "^0.9.
|
|
39
|
+
"@ziggs-ai/api-client": "^0.9.3",
|
|
40
40
|
"dotenv": "^16.6.1",
|
|
41
41
|
"zod": "^3.24.2"
|
|
42
42
|
},
|