@ziggs-ai/ziggs-mcp 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -197,7 +197,6 @@ OP_KEY_A=... AGENT_A=... USER_B=... OP_KEY_B=... AGENT_B=... \
197
197
  | `ziggs_discover_context` | `GET /context/discovery` |
198
198
  | `ziggs_read_context` | `GET /context/read/:type` |
199
199
  | `ziggs_record_artifact` | `POST /artifacts` |
200
- | `ziggs_list_artifacts` | `GET /artifacts` |
201
200
  | `ziggs_search_agents` | Agent search (ZIG-433) |
202
201
  | `ziggs_list_my_grants` | `GET /context/grants` |
203
202
  | `ziggs_issue_grant` | Chat admission or `POST /context/grants` |
@@ -212,7 +211,6 @@ OP_KEY_A=... AGENT_A=... USER_B=... OP_KEY_B=... AGENT_B=... \
212
211
  | `ziggs_get_agreement` | `GET /agreements/:id` |
213
212
  | `ziggs_list_chats` | `GET /chats/mine` |
214
213
  | `ziggs_open_conversation` | `POST /chats` |
215
- | `ziggs_list_messages` | `GET /chats/:id/messages` |
216
214
  | `ziggs_send_message` | `POST /chats/:id/messages` |
217
215
  | `ziggs_propose_agreement` | `POST /agreements/proposals` |
218
216
  | `ziggs_respond_to_agreement` | `PUT /agreements/:id/approvals/:partyId` (owner principal; approves hire, service, and `link` proposals) |
@@ -0,0 +1,7 @@
1
+ import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
2
+ /** Reads state, never mutates. */
3
+ export declare const READ_ONLY: ToolAnnotations;
4
+ /** Writes state, but additively/reversibly (create, send, grant). */
5
+ export declare const WRITE: ToolAnnotations;
6
+ /** Mutates state irreversibly (revoke). */
7
+ export declare const DESTRUCTIVE: ToolAnnotations;
@@ -0,0 +1,16 @@
1
+ // MCP annotation hints so connector UIs (Claude, Cursor, …) can bucket Ziggs
2
+ // tools into "Read only" vs "Actions" instead of one undefined group.
3
+ // `readOnlyHint` drives that split; `destructiveHint` flags the irreversible
4
+ // ones so hosts can warn before running them.
5
+ /** Reads state, never mutates. */
6
+ export const READ_ONLY = { readOnlyHint: true };
7
+ /** Writes state, but additively/reversibly (create, send, grant). */
8
+ export const WRITE = {
9
+ readOnlyHint: false,
10
+ destructiveHint: false,
11
+ };
12
+ /** Mutates state irreversibly (revoke). */
13
+ export const DESTRUCTIVE = {
14
+ readOnlyHint: false,
15
+ destructiveHint: true,
16
+ };
package/dist/tools.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
- import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDirectTo, proposeBroadcast, publishOffer, respondToAgreement, ScopeClient, MessagesClient, sendChatMessage, ContextDiscoveryClient, ContextReadClient, InboxClient, ArtifactsClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getBackendUrl, } from '@ziggs-ai/api-client';
3
+ import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDirectTo, proposeBroadcast, publishOffer, respondToAgreement, ScopeClient, sendChatMessage, ContextDiscoveryClient, ContextReadClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getBackendUrl, } from '@ziggs-ai/api-client';
4
4
  import { decodeOperatorKeyClaims } from './operatorKey.js';
5
5
  import { registerTrustTools } from './trustTools.js';
6
6
  import { formatInboxToolResult } from './inboxToolResult.js';
7
7
  import { formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
8
8
  import { PROTOCOL } from './protocol/delegateProtocol.js';
9
+ import { READ_ONLY, WRITE } from './toolAnnotations.js';
9
10
  // ZIG-557: the protocol sentences (loop / ack / humanAttention) are sourced
10
11
  // from the shared const so this description can't drift from SKILL / server
11
12
  // instructions / .cursorrules.
12
13
  const ZIGGS_INBOX_DESCRIPTION = "What's new since your last ack — references only, never content: scopes with new-message/artifact counts, plus agreement proposals awaiting your response. " +
13
- 'For org/agreement scopes each entry includes a `chats` breakdown (chatId + per-chat counts) so you can open the conversations behind the count — read them with ziggs_list_messages / ziggs_read_context (via=chat:<chatId>). ' +
14
+ 'For org/agreement scopes each entry includes a `chats` breakdown (chatId + per-chat counts) so you can open the conversations behind the count — read them with ziggs_read_context (type=messages, via=chat:<chatId>). ' +
14
15
  `${PROTOCOL.humanAttention} ${PROTOCOL.pendingDecisions} ` +
15
16
  'When hasActionable the response includes sessionChatCard (decisions + active tasks), decisionChatCard, workChatCard, and nextActions. ' +
16
17
  `${PROTOCOL.loop} ${PROTOCOL.ack}`;
@@ -152,7 +153,7 @@ async function loadSessionActionsPayload(creds, cfg) {
152
153
  return formatPendingDecisionsPayload(inbox, webOrigin, { activeTasks });
153
154
  }
154
155
  export function registerZiggsTools(server, creds, cfg) {
155
- server.tool('ziggs_connection_status', 'ZIG-503 — 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 (ZIG-625).', {}, async () => {
156
+ server.tool('ziggs_connection_status', 'ZIG-503 — 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 (ZIG-625).', {}, READ_ONLY, async () => {
156
157
  const claims = decodeOperatorKeyClaims(creds.operatorKey);
157
158
  const boundOrgId = claims?.boundOrgId?.trim() || null;
158
159
  const webOrigin = resolveWebAppOrigin(cfg.ZIGGS_WEB_URL);
@@ -194,7 +195,7 @@ export function registerZiggsTools(server, creds, cfg) {
194
195
  : 'Next: ziggs_inbox or ziggs_pending_decisions at session start.',
195
196
  });
196
197
  });
197
- server.tool('ziggs_pending_decisions', ZIGGS_PENDING_DECISIONS_DESCRIPTION, {}, async () => {
198
+ server.tool('ziggs_pending_decisions', ZIGGS_PENDING_DECISIONS_DESCRIPTION, {}, READ_ONLY, async () => {
198
199
  try {
199
200
  const payload = await loadSessionActionsPayload(creds, cfg);
200
201
  const decisions = (payload.decisions ?? []);
@@ -211,7 +212,7 @@ export function registerZiggsTools(server, creds, cfg) {
211
212
  return toolError(e.message);
212
213
  }
213
214
  });
214
- server.tool('ziggs_smoke_impersonation', 'ZIG-222 smoke (1): list agreements and optionally resolve scope from the first chat.', {}, async () => {
215
+ server.tool('ziggs_smoke_impersonation', 'ZIG-222 smoke (1): list agreements and optionally resolve scope from the first chat.', {}, READ_ONLY, async () => {
215
216
  try {
216
217
  const agreements = await getMyAgreements({}, creds);
217
218
  const chats = await listMyChats(creds);
@@ -235,7 +236,7 @@ export function registerZiggsTools(server, creds, cfg) {
235
236
  server.tool('ziggs_get_scope', 'Resolve the access graph for the delegate agent from a chat, agreement, task, or counterparty entry point.', {
236
237
  viaKind: scopeKindSchema.describe('Entry kind'),
237
238
  viaId: z.string().describe('Entry id'),
238
- }, async ({ viaKind, viaId }) => {
239
+ }, READ_ONLY, async ({ viaKind, viaId }) => {
239
240
  try {
240
241
  const client = new ScopeClient(creds.operatorKey, creds.agentId);
241
242
  const result = await client.get(viaKind, viaId);
@@ -250,7 +251,7 @@ export function registerZiggsTools(server, creds, cfg) {
250
251
  .string()
251
252
  .optional()
252
253
  .describe('Optional filter: pending, approved, rejected, …'),
253
- }, async ({ proposalStatus }) => {
254
+ }, READ_ONLY, async ({ proposalStatus }) => {
254
255
  try {
255
256
  const agreements = await getMyAgreements(proposalStatus ? { proposalStatus } : {}, creds);
256
257
  return textResult({ count: agreements.length, agreements });
@@ -259,7 +260,7 @@ export function registerZiggsTools(server, creds, cfg) {
259
260
  return toolError(e.message);
260
261
  }
261
262
  });
262
- server.tool('ziggs_get_agreement', 'Fetch a single agreement by id.', { agreementId: z.string() }, async ({ agreementId }) => {
263
+ server.tool('ziggs_get_agreement', 'Fetch a single agreement by id.', { agreementId: z.string() }, READ_ONLY, async ({ agreementId }) => {
263
264
  try {
264
265
  const agreement = await getAgreement(agreementId, creds);
265
266
  if (!agreement)
@@ -270,7 +271,7 @@ export function registerZiggsTools(server, creds, cfg) {
270
271
  return toolError(e.message);
271
272
  }
272
273
  });
273
- server.tool('ziggs_list_chats', 'List chats the delegate agent is a member of (GET /chats/mine).', {}, async () => {
274
+ server.tool('ziggs_list_chats', 'List chats the delegate agent is a member of (GET /chats/mine).', {}, READ_ONLY, async () => {
274
275
  try {
275
276
  const chats = await listMyChats(creds);
276
277
  return textResult({ count: chats.length, chats });
@@ -281,7 +282,7 @@ export function registerZiggsTools(server, creds, cfg) {
281
282
  });
282
283
  server.tool('ziggs_open_conversation', 'Open or reuse a chat with a user or agent participant. To reach an agent in ANOTHER org, an unpublished delegate must establish a link first — call ziggs_request_link (if you have its agent id) or ziggs_create_link_invite (if you do not) and have it approved/claimed — otherwise this fails with AGENT_NOT_PUBLISHED.', {
283
284
  participantId: z.string().describe('User or agent id to converse with'),
284
- }, async ({ participantId }) => {
285
+ }, WRITE, async ({ participantId }) => {
285
286
  try {
286
287
  const out = await openConversation(participantId, creds);
287
288
  return textResult(out);
@@ -290,26 +291,6 @@ export function registerZiggsTools(server, creds, cfg) {
290
291
  return toolError(e.message);
291
292
  }
292
293
  });
293
- server.tool('ziggs_list_messages', 'Forward-delta message read for a chat.', {
294
- chatId: z.string(),
295
- after: z
296
- .string()
297
- .optional()
298
- .describe('ISO timestamp; default epoch (all messages)'),
299
- limit: z.number().optional().describe('Max messages (default 100)'),
300
- }, async ({ chatId, after, limit }) => {
301
- try {
302
- const client = new MessagesClient(creds.operatorKey, creds.agentId);
303
- const result = await client.list(chatId, {
304
- after,
305
- limit,
306
- });
307
- return textResult(result);
308
- }
309
- catch (e) {
310
- return toolError(e.message);
311
- }
312
- });
313
294
  server.tool('ziggs_send_message', ZIGGS_SEND_MESSAGE_DESCRIPTION, {
314
295
  chatId: z.string(),
315
296
  receiverId: z
@@ -321,7 +302,7 @@ export function registerZiggsTools(server, creds, cfg) {
321
302
  .string()
322
303
  .optional()
323
304
  .describe('Usually "message" for user-visible chat'),
324
- }, async ({ chatId, receiverId, text, entryType }) => {
305
+ }, WRITE, async ({ chatId, receiverId, text, entryType }) => {
325
306
  try {
326
307
  const result = await sendChatMessage({
327
308
  chatId,
@@ -346,7 +327,7 @@ export function registerZiggsTools(server, creds, cfg) {
346
327
  .optional()
347
328
  .describe('Human user id = payer (ZIG-222: your userId)'),
348
329
  price: z.number().optional().describe('Optional; does not trigger transfer by itself'),
349
- }, async ({ proposedTo, chatId, description, payerId, price }) => {
330
+ }, WRITE, async ({ proposedTo, chatId, description, payerId, price }) => {
350
331
  try {
351
332
  const resolvedPayer = payerId ?? cfg.ZIGGS_OWNER_USER_ID;
352
333
  if (!resolvedPayer) {
@@ -378,7 +359,7 @@ export function registerZiggsTools(server, creds, cfg) {
378
359
  .enum(['everyone', 'org'])
379
360
  .optional()
380
361
  .describe("'everyone' (default, public) or 'org' (visible/claimable only within your org)"),
381
- }, async ({ description, chatId, payerId, price, audience }) => {
362
+ }, WRITE, async ({ description, chatId, payerId, price, audience }) => {
382
363
  try {
383
364
  const resolvedPayer = payerId ?? cfg.ZIGGS_OWNER_USER_ID;
384
365
  if (!resolvedPayer) {
@@ -408,7 +389,7 @@ export function registerZiggsTools(server, creds, cfg) {
408
389
  .enum(['everyone', 'org'])
409
390
  .optional()
410
391
  .describe("'everyone' (default, public) or 'org' (visible/claimable only within your org)"),
411
- }, async ({ description, price, engagementKind, audience }) => {
392
+ }, WRITE, async ({ description, price, engagementKind, audience }) => {
412
393
  try {
413
394
  const agreement = await publishOffer({
414
395
  description,
@@ -425,7 +406,7 @@ export function registerZiggsTools(server, creds, cfg) {
425
406
  server.tool('ziggs_respond_to_agreement', 'Approve or reject a pending agreement (ZIG-524). Uses PUT /approvals/:partyId or POST /claim for an open broadcast (public or org-scoped; org-scoped quests are claimable only by members of the agreement\'s org).', {
426
407
  agreementId: z.string(),
427
408
  action: z.enum(['approve', 'reject']),
428
- }, async ({ agreementId, action }) => {
409
+ }, WRITE, async ({ agreementId, action }) => {
429
410
  try {
430
411
  const claims = decodeOperatorKeyClaims(creds.operatorKey);
431
412
  const ownerId = claims?.ownerId ?? cfg.ZIGGS_OWNER_USER_ID;
@@ -447,7 +428,7 @@ export function registerZiggsTools(server, creds, cfg) {
447
428
  }))
448
429
  .optional()
449
430
  .describe('Scopes you finished handling — acked before fetching, monotonic'),
450
- }, async ({ ack }) => {
431
+ }, READ_ONLY, async ({ ack }) => {
451
432
  try {
452
433
  const client = new InboxClient(creds.operatorKey, creds.agentId);
453
434
  const acked = ack?.length ? await client.ack(ack) : null;
@@ -466,7 +447,7 @@ export function registerZiggsTools(server, creds, cfg) {
466
447
  return toolError(e.message);
467
448
  }
468
449
  });
469
- server.tool('ziggs_discover_context', 'List scope descriptors this delegate can reach (grants only — no content).', {}, async () => {
450
+ server.tool('ziggs_discover_context', 'List scope descriptors this delegate can reach (grants only — no content).', {}, READ_ONLY, async () => {
470
451
  try {
471
452
  const client = new ContextDiscoveryClient(creds.operatorKey, creds.agentId);
472
453
  const reach = await client.discover();
@@ -476,7 +457,7 @@ export function registerZiggsTools(server, creds, cfg) {
476
457
  return toolError(e.message);
477
458
  }
478
459
  });
479
- server.tool('ziggs_read_context', 'Uniform context read via GET /context/read/:type (messages, artifacts, agreements, tasks). Requires via=chat:id, agreement:id, or task:id.', {
460
+ 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. 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.', {
480
461
  type: contextReadTypeSchema.describe('Resource type to read'),
481
462
  via: z
482
463
  .string()
@@ -496,7 +477,7 @@ export function registerZiggsTools(server, creds, cfg) {
496
477
  .string()
497
478
  .optional()
498
479
  .describe('Pin a specific grant when holding several'),
499
- }, async ({ type, via, cursor, after, direction, limit, state, contextGrantId }) => {
480
+ }, READ_ONLY, async ({ type, via, cursor, after, direction, limit, state, contextGrantId }) => {
500
481
  try {
501
482
  const client = new ContextReadClient(creds.operatorKey, creds.agentId);
502
483
  const result = await client.read(type, {
@@ -527,7 +508,7 @@ export function registerZiggsTools(server, creds, cfg) {
527
508
  .optional()
528
509
  .describe('Optional task — creates a TaskArtifactLink alongside the primary scope link'),
529
510
  content_type: z.string().optional().describe('Default text'),
530
- }, async ({ text, visibility, chatId, agreementId, taskId, content_type }) => {
511
+ }, WRITE, async ({ text, visibility, chatId, agreementId, taskId, content_type }) => {
531
512
  try {
532
513
  if ((chatId && agreementId) || (!chatId && !agreementId)) {
533
514
  return toolError('Pass exactly one of chatId or agreementId');
@@ -553,24 +534,6 @@ export function registerZiggsTools(server, creds, cfg) {
553
534
  return toolError(e.message);
554
535
  }
555
536
  });
556
- server.tool('ziggs_list_artifacts', 'List artifacts for a chat or agreement (GET /artifacts). Prefer ziggs_read_context type=artifacts for grant-pinned reads.', {
557
- chatId: z.string().optional().describe('Chat scope (xor agreementId)'),
558
- agreementId: z
559
- .string()
560
- .optional()
561
- .describe('Agreement scope (xor chatId)'),
562
- after: z.string().optional().describe('ISO timestamp forward-delta'),
563
- limit: z.number().optional().describe('Max rows'),
564
- }, async ({ chatId, agreementId, after, limit }) => {
565
- try {
566
- const client = new ArtifactsClient(creds.operatorKey, creds.agentId);
567
- const result = await client.list({ chatId, agreementId }, { after, limit });
568
- return textResult(result);
569
- }
570
- catch (e) {
571
- return toolError(e.message);
572
- }
573
- });
574
537
  // ---------------------------------------------------------------------------
575
538
  // Task mutation tools (ZIG-555)
576
539
  // ---------------------------------------------------------------------------
@@ -578,7 +541,7 @@ export function registerZiggsTools(server, creds, cfg) {
578
541
  agreementId: z.string().describe('Agreement this task belongs to'),
579
542
  description: z.string().describe('What the task entails'),
580
543
  parentTaskId: z.string().optional().describe('Parent task id for sub-tasks'),
581
- }, async ({ agreementId, description, parentTaskId }) => {
544
+ }, WRITE, async ({ agreementId, description, parentTaskId }) => {
582
545
  try {
583
546
  const task = await createTask({ agreementId, description, parentTaskId }, creds);
584
547
  return textResult({ ok: true, task });
@@ -600,7 +563,7 @@ export function registerZiggsTools(server, creds, cfg) {
600
563
  .optional()
601
564
  .describe('Outcome payload (summary, status, links, …)'),
602
565
  errorMessage: z.string().optional().describe('Required when state=failed'),
603
- }, async ({ taskId, state, result, errorMessage }) => {
566
+ }, WRITE, async ({ taskId, state, result, errorMessage }) => {
604
567
  try {
605
568
  const task = await updateTaskState(taskId, state, { result, errorMessage }, creds);
606
569
  return textResult({ ok: true, task });
@@ -618,7 +581,7 @@ export function registerZiggsTools(server, creds, cfg) {
618
581
  order: z.number().int(),
619
582
  }))
620
583
  .describe('Full replacement step list (ordered)'),
621
- }, async ({ taskId, steps }) => {
584
+ }, WRITE, async ({ taskId, steps }) => {
622
585
  try {
623
586
  const task = await replaceTaskPlan(taskId, steps, creds);
624
587
  return textResult({ ok: true, task });
@@ -634,7 +597,7 @@ export function registerZiggsTools(server, creds, cfg) {
634
597
  .describe('Filter by state: active, completed, failed, cancelled'),
635
598
  cursor: z.string().optional().describe('Opaque cursor from prior nextCursor'),
636
599
  limit: z.number().optional().describe('Max rows (default server-side)'),
637
- }, async ({ state, cursor, limit }) => {
600
+ }, READ_ONLY, async ({ state, cursor, limit }) => {
638
601
  try {
639
602
  const result = await listTasks({ state, cursor, limit }, creds);
640
603
  return textResult(result);
@@ -659,7 +622,7 @@ export function registerZiggsTools(server, creds, cfg) {
659
622
  .record(z.unknown())
660
623
  .optional()
661
624
  .describe('Action-specific arguments (provider-defined)'),
662
- }, async ({ connectionId, grantId, action, payload }) => {
625
+ }, WRITE, async ({ connectionId, grantId, action, payload }) => {
663
626
  try {
664
627
  const result = await proxyConnection(creds, {
665
628
  connectionId,
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { AgentSearchClient, ContextGrantsClient, createAgreement, listAgreements, revokeAgreement, claimLink, addChatMember, } from '@ziggs-ai/api-client';
3
+ import { READ_ONLY, WRITE, DESTRUCTIVE } from './toolAnnotations.js';
3
4
  function textResult(data) {
4
5
  return {
5
6
  content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
@@ -21,7 +22,7 @@ export function registerTrustTools(server, creds, cfg) {
21
22
  query: z.string().describe('Keyword/natural-language search (published agents) OR an exact agent id (resolves that agent even if unpublished)'),
22
23
  limit: z.number().optional().describe('Max results (default server-side)'),
23
24
  minScore: z.number().optional().describe('Minimum match score filter'),
24
- }, async ({ query, limit, minScore }) => {
25
+ }, READ_ONLY, async ({ query, limit, minScore }) => {
25
26
  try {
26
27
  const client = new AgentSearchClient(creds.operatorKey, creds.agentId);
27
28
  const result = await client.searchAgents(query, { limit, minScore });
@@ -42,7 +43,7 @@ export function registerTrustTools(server, creds, cfg) {
42
43
  .string()
43
44
  .optional()
44
45
  .describe('Admin only: list grants for another agent id'),
45
- }, async ({ holderId }) => {
46
+ }, READ_ONLY, async ({ holderId }) => {
46
47
  try {
47
48
  const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
48
49
  const grants = await client.listGrants(holderId);
@@ -64,7 +65,7 @@ export function registerTrustTools(server, creds, cfg) {
64
65
  .optional()
65
66
  .nullable()
66
67
  .describe('ISO-8601 expiry; omit for platform default TTL'),
67
- }, async ({ holderId, scopeKind, scopeId, temporal, expiresAt }) => {
68
+ }, WRITE, async ({ holderId, scopeKind, scopeId, temporal, expiresAt }) => {
68
69
  const resolvedTemporal = temporal ?? 'from-now';
69
70
  try {
70
71
  if (scopeKind === 'chat') {
@@ -128,7 +129,7 @@ export function registerTrustTools(server, creds, cfg) {
128
129
  .string()
129
130
  .optional()
130
131
  .describe('from-now watermark ISO-8601 (optional; server may default)'),
131
- }, async ({ parentGrantId, holderId, scopeKind, scopeId, temporal, expiresAt, watermarkAt, }) => {
132
+ }, WRITE, async ({ parentGrantId, holderId, scopeKind, scopeId, temporal, expiresAt, watermarkAt, }) => {
132
133
  try {
133
134
  const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
134
135
  const grant = await client.delegateGrant(parentGrantId, {
@@ -161,7 +162,7 @@ export function registerTrustTools(server, creds, cfg) {
161
162
  .string()
162
163
  .optional()
163
164
  .describe('Optional note shown to the counterparty human on approval (agreement description)'),
164
- }, async ({ providerId, message }) => {
165
+ }, WRITE, async ({ providerId, message }) => {
165
166
  try {
166
167
  const { agreement } = await createAgreement({ engagementKind: 'link', providerId, description: message }, creds);
167
168
  return textResult({
@@ -179,7 +180,7 @@ export function registerTrustTools(server, creds, cfg) {
179
180
  .string()
180
181
  .optional()
181
182
  .describe('Optional note shown to whoever opens the invite (agreement description)'),
182
- }, async ({ message }) => {
183
+ }, WRITE, async ({ message }) => {
183
184
  try {
184
185
  const { agreement } = await createAgreement({ engagementKind: 'link', description: message }, creds);
185
186
  return textResult({
@@ -198,7 +199,7 @@ export function registerTrustTools(server, creds, cfg) {
198
199
  agreementId: z
199
200
  .string()
200
201
  .describe('The invite id (agreementId) shared by the issuer'),
201
- }, async ({ agreementId }) => {
202
+ }, WRITE, async ({ agreementId }) => {
202
203
  try {
203
204
  const { agreement } = await claimLink(agreementId, creds);
204
205
  return textResult({
@@ -211,7 +212,7 @@ export function registerTrustTools(server, creds, cfg) {
211
212
  return toolError(e.message);
212
213
  }
213
214
  });
214
- server.tool('ziggs_list_links', 'List link agreements for this delegate (GET /agreements?engagementKind=link, ZIG-481). Each item exposes parties.creatorAgent (requester), parties.providerAgent (target), parties.proposedTo (target owner), proposal.status and status. Approve pending links via ziggs_respond_to_agreement.', {}, async () => {
215
+ server.tool('ziggs_list_links', 'List link agreements for this delegate (GET /agreements?engagementKind=link, ZIG-481). Each item exposes parties.creatorAgent (requester), parties.providerAgent (target), parties.proposedTo (target owner), proposal.status and status. Approve pending links via ziggs_respond_to_agreement.', {}, READ_ONLY, async () => {
215
216
  try {
216
217
  const links = await listAgreements({ engagementKind: 'link' }, creds);
217
218
  const hasActive = links.some((a) => a.status === 'active');
@@ -233,7 +234,7 @@ export function registerTrustTools(server, creds, cfg) {
233
234
  agreementId: z
234
235
  .string()
235
236
  .describe('agreementId of the link agreement (from ziggs_list_links)'),
236
- }, async ({ agreementId }) => {
237
+ }, DESTRUCTIVE, async ({ agreementId }) => {
237
238
  try {
238
239
  const result = await revokeAgreement(agreementId, creds);
239
240
  return textResult({
@@ -249,7 +250,7 @@ export function registerTrustTools(server, creds, cfg) {
249
250
  });
250
251
  server.tool('ziggs_revoke_grant', 'Revoke a context grant and its descendants (DELETE /context/grants/:id). Requires context:admin on the operator key.', {
251
252
  grantId: z.string(),
252
- }, async ({ grantId }) => {
253
+ }, DESTRUCTIVE, async ({ grantId }) => {
253
254
  try {
254
255
  const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
255
256
  const result = await client.revokeGrant(grantId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
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": {
@@ -50,8 +50,8 @@ count spans many chats, so the entry includes a **`chats`** breakdown:
50
50
  { chatId: "<id>", newMessages: 5, newArtifacts: 0, latestAt: "…" }, … ] }
51
51
  ```
52
52
 
53
- Open each conversation by its `chatId` — `ziggs_list_messages` or
54
- `ziggs_read_context` (`via: chat:<chatId>`). Your org/scope grant covers those
53
+ Open each conversation by its `chatId` with `ziggs_read_context`
54
+ (`type: messages, via: chat:<chatId>`). Your org/scope grant covers those
55
55
  chats without explicit membership. If **`truncatedChats`** is set, more chats
56
56
  have news than are listed — handle and ack the listed ones, then re-run inbox.
57
57