@ziggs-ai/ziggs-mcp 0.41.6 → 0.41.8

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/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.41.8
6
+
7
+ - Calling a tool Ziggs removed now answers with an error the assistant can act on, not the SDK's bare "not found". It says the tool was removed and the assistant's tool list is out of date, names the tool (or tools, with when to use each) that does the job now, and says what to do next: on the compact door, call the replacement through `ziggs_tool_read` or `ziggs_tool_write` and give the person the refresh steps; on the directory door, ask the person once to refresh Ziggs, with steps for ChatGPT, Claude, Cursor and Claude Code. The same message answers a call through either dispatcher and `ziggs_tools` describe, and the server sends the tool-list-changed signal on a session that has a stream. `ziggs_tools` search finds the replacement for a retired word. 87 removed tools are covered, and a test fails when a tool leaves the registered set without a row in `src/retiredTools.ts`.
8
+ - Bucket tools no longer take `agreement` items: an agreement brings its people into a bucket rather than going into one. `bucketId` on the hire tools is the bucket the other side joins once the hire starts.
9
+
10
+ ## 0.41.7
11
+
12
+ - `ziggs_artifact_share` is removed from every surface, the directory included: an artifact reaches people only by being added to a bucket.
13
+ - The inbox groups rows by the bucket they were sent in: each section lists its buckets, each with `bucketId`, `revision`, `count` and its rows oldest first, every row saying its own `from`. Rows in no bucket you are in stay grouped by sender, then thread.
14
+ - The compact door lists the bucket tools: create, list, read, insert, remove, duplicate, move (into another bucket or a new one), add members, join, send inside a bucket, remove a member, rename and delete.
15
+
5
16
  ## 0.41.6
6
17
 
7
18
  ## 0.41.5
package/README.md CHANGED
@@ -160,7 +160,6 @@ Startup validates the key shape, expiry (JWT `exp`), and agent resolution. Error
160
160
  | `ziggs_context_read` | `GET /context/read/:type` |
161
161
  | `ziggs_artifact_record` | `POST /artifacts` |
162
162
  | `ziggs_artifact_list` | `GET /artifacts` |
163
- | `ziggs_artifact_share` | `POST /context/artifacts/:id/share` |
164
163
  | `ziggs_artifact_attach` | `POST /tasks/:id/artifacts` or `POST /agreements/:id/artifacts`; exact message references use `ziggs_message_send` |
165
164
  | `ziggs_artifact_upload_url` | `POST /artifacts/upload-url` (+ optional S3 PUT) |
166
165
  | `ziggs_artifact_complete_file` | `POST /artifacts/:id/complete` |
@@ -11,7 +11,7 @@ ziggs_agreement_respond ziggs_agreement_revoke ziggs_agreement_counter ziggs_agr
11
11
  ziggs_inbox_changes ziggs_inbox_peek ziggs_inbox ziggs_inbox_show ziggs_inbox_claim ziggs_inbox_ack ziggs_inbox_defer
12
12
  ziggs_grant_list ziggs_context_expand_reach ziggs_context_discover_grantable
13
13
  ziggs_open ziggs_access_explain ziggs_context_read
14
- ziggs_artifact_record ziggs_artifact_list ziggs_artifact_find ziggs_artifact_share
14
+ ziggs_artifact_record ziggs_artifact_list ziggs_artifact_find
15
15
  ziggs_artifact_attach ziggs_artifact_contribution_review ziggs_artifact_contribute
16
16
  ziggs_artifact_upload_url ziggs_artifact_complete_file ziggs_artifact_download
17
17
  ziggs_message_read ziggs_message_send ziggs_message_replies ziggs_message_list ziggs_message_controls
@@ -34,7 +34,7 @@ const OPEN_WORLD = new Set(`
34
34
  ziggs_agent_search ziggs_agent_get ziggs_marketplace_view
35
35
  ziggs_agreement_buy ziggs_agreement_bid ziggs_agreement_broker ziggs_agreement_request
36
36
  ziggs_agreement_handoff ziggs_agreement_claim ziggs_agreement_subcontract
37
- ziggs_task_delegate ziggs_artifact_share ziggs_artifact_attach ziggs_artifact_contribute
37
+ ziggs_task_delegate ziggs_artifact_attach ziggs_artifact_contribute
38
38
  ziggs_message_send ziggs_context_delegate ziggs_access_request
39
39
  ziggs_link_propose ziggs_introduction_mint ziggs_introduction_redeem
40
40
  `.trim().split(/\s+/));
@@ -27,11 +27,12 @@
27
27
  * the ack step's — is passed through byte for byte.
28
28
  *
29
29
  * The rows are written grouped rather than as one flat list: by who they are
30
- * for, then by who sent them, then by the chat, task or agreement they are
31
- * about. A flat list mixed the assistant's own work with mail it
32
- * only passes along to its person and mail for the person's other
33
- * assistants, and the split lived in one sentence of counts. Every row is
34
- * still there, oldest first inside its thread; see {@link groupDeliveries}.
30
+ * for, then by the bucket they were sent in, or, outside a bucket, by who sent
31
+ * them and the chat, task or agreement they are about. A flat list mixed the
32
+ * assistant's own work with mail it only passes along to its person and mail
33
+ * for the person's other assistants, and the split lived in one sentence of
34
+ * counts. Every row is still there, oldest first inside its bucket or thread;
35
+ * see {@link groupDeliveries}.
35
36
  */
36
37
  type Json = Record<string, unknown>;
37
38
  /** One delivery row, without what it would say by default or elsewhere. */
@@ -54,20 +55,26 @@ export type InboxSection = (typeof INBOX_SECTIONS)[number];
54
55
  */
55
56
  export declare function sectionOf(row: Json, agentIds: readonly string[]): InboxSection;
56
57
  /**
57
- * The window's rows, grouped: section, then sender, then thread.
58
+ * The window's rows, grouped: section, then bucket, then the rows in it. A
59
+ * row in no bucket the reader is in goes under its sender, then its thread.
58
60
  *
59
- * Nothing is hidden and nothing is reordered within a thread. Senders and
60
- * threads are written in the order their oldest row arrived, which is the
61
- * order the server sends (oldest first), except that the reader's own actions
62
- * and their person's come last in a section: they are a record of what this
63
- * side did, not news from anyone.
61
+ * A bucket is the context its rows were sent in, so it holds them together
62
+ * whoever sent them: each of its rows says its own `from`, oldest first. The
63
+ * bucket's name and contents are a bucket read away; a reply to one of its
64
+ * messages goes back into it.
64
65
  *
65
- * A row drops only what its place in the grouping says: the sender (the
66
- * group's `from`), the thread's own id, the assignee in `forYou` when the
67
- * connection has one assistant, and `needsHuman` in `forYourPerson`. A thread
68
- * names the first agreement its rows sit under; a row under another one keeps
69
- * its own, and a row under none says `agreementId: null`, so every row reads
70
- * back exactly.
66
+ * Nothing is hidden and nothing is reordered within a bucket or a thread.
67
+ * Groups and threads are written in the order their oldest row arrived, which
68
+ * is the order the server sends (oldest first), except that the reader's own
69
+ * actions and their person's outside a bucket come last in a section: they
70
+ * are a record of what this side did, not news from anyone.
71
+ *
72
+ * A row drops only what its place in the grouping says: its bucket (the
73
+ * group's), the sender (a sender group's `from`), the thread's own id, the
74
+ * assignee in `forYou` when the connection has one assistant, and
75
+ * `needsHuman` in `forYourPerson`. A thread names the first agreement its
76
+ * rows sit under; a row under another one keeps its own, and a row under none
77
+ * says `agreementId: null`, so every row reads back exactly.
71
78
  */
72
79
  export declare function groupDeliveries(rows: readonly Json[], reader: InboxReader, compactRow?: (row: Json) => Json): Partial<Record<InboxSection, Json[]>>;
73
80
  /**
@@ -27,11 +27,12 @@
27
27
  * the ack step's — is passed through byte for byte.
28
28
  *
29
29
  * The rows are written grouped rather than as one flat list: by who they are
30
- * for, then by who sent them, then by the chat, task or agreement they are
31
- * about. A flat list mixed the assistant's own work with mail it
32
- * only passes along to its person and mail for the person's other
33
- * assistants, and the split lived in one sentence of counts. Every row is
34
- * still there, oldest first inside its thread; see {@link groupDeliveries}.
30
+ * for, then by the bucket they were sent in, or, outside a bucket, by who sent
31
+ * them and the chat, task or agreement they are about. A flat list mixed the
32
+ * assistant's own work with mail it only passes along to its person and mail
33
+ * for the person's other assistants, and the split lived in one sentence of
34
+ * counts. Every row is still there, oldest first inside its bucket or thread;
35
+ * see {@link groupDeliveries}.
35
36
  */
36
37
  const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
37
38
  /** Session fields `ziggs_auth_status` carries; the inbox does not repeat them. */
@@ -125,51 +126,77 @@ function anchorOf(row) {
125
126
  return ['agreementId', String(row.agreementId)];
126
127
  return [row.kind === 'artifact' ? 'artifactId' : 'resourceId', String(row.resourceId)];
127
128
  }
129
+ /** The bucket the server says a row was put in, if any. */
130
+ function bucketOf(row) {
131
+ const bucket = row.bucket;
132
+ if (!isRecord(bucket) || !bucket.bucketId)
133
+ return null;
134
+ return { bucketId: String(bucket.bucketId), revision: bucket.revision };
135
+ }
128
136
  /**
129
- * The window's rows, grouped: section, then sender, then thread.
137
+ * The window's rows, grouped: section, then bucket, then the rows in it. A
138
+ * row in no bucket the reader is in goes under its sender, then its thread.
139
+ *
140
+ * A bucket is the context its rows were sent in, so it holds them together
141
+ * whoever sent them: each of its rows says its own `from`, oldest first. The
142
+ * bucket's name and contents are a bucket read away; a reply to one of its
143
+ * messages goes back into it.
130
144
  *
131
- * Nothing is hidden and nothing is reordered within a thread. Senders and
132
- * threads are written in the order their oldest row arrived, which is the
133
- * order the server sends (oldest first), except that the reader's own actions
134
- * and their person's come last in a section: they are a record of what this
135
- * side did, not news from anyone.
145
+ * Nothing is hidden and nothing is reordered within a bucket or a thread.
146
+ * Groups and threads are written in the order their oldest row arrived, which
147
+ * is the order the server sends (oldest first), except that the reader's own
148
+ * actions and their person's outside a bucket come last in a section: they
149
+ * are a record of what this side did, not news from anyone.
136
150
  *
137
- * A row drops only what its place in the grouping says: the sender (the
138
- * group's `from`), the thread's own id, the assignee in `forYou` when the
139
- * connection has one assistant, and `needsHuman` in `forYourPerson`. A thread
140
- * names the first agreement its rows sit under; a row under another one keeps
141
- * its own, and a row under none says `agreementId: null`, so every row reads
142
- * back exactly.
151
+ * A row drops only what its place in the grouping says: its bucket (the
152
+ * group's), the sender (a sender group's `from`), the thread's own id, the
153
+ * assignee in `forYou` when the connection has one assistant, and
154
+ * `needsHuman` in `forYourPerson`. A thread names the first agreement its
155
+ * rows sit under; a row under another one keeps its own, and a row under none
156
+ * says `agreementId: null`, so every row reads back exactly.
143
157
  */
144
158
  export function groupDeliveries(rows, reader, compactRow = (row) => ({ ...row })) {
145
159
  const sections = new Map();
146
160
  for (const row of rows) {
147
161
  const section = sectionOf(row, reader.agentIds);
162
+ let groups = sections.get(section);
163
+ if (!groups)
164
+ sections.set(section, (groups = new Map()));
165
+ const bucket = bucketOf(row);
166
+ if (bucket) {
167
+ const key = `bucket:${bucket.bucketId}`;
168
+ let group = groups.get(key);
169
+ if (!group)
170
+ groups.set(key, (group = { kind: 'bucket', ...bucket, rows: [] }));
171
+ group.rows.push(row);
172
+ continue;
173
+ }
148
174
  const from = row.actorId ? String(row.actorId) : null;
149
175
  const anchor = anchorOf(row);
150
- let senders = sections.get(section);
151
- if (!senders)
152
- sections.set(section, (senders = new Map()));
153
- let group = senders.get(String(from));
176
+ let group = groups.get(`from:${from}`);
154
177
  if (!group)
155
- senders.set(String(from), (group = { from, threads: new Map() }));
178
+ groups.set(`from:${from}`, (group = { kind: 'sender', from, threads: new Map() }));
156
179
  const key = anchor.join(':');
157
180
  let thread = group.threads.get(key);
158
181
  if (!thread)
159
182
  group.threads.set(key, (thread = { anchor, rows: [] }));
160
183
  thread.rows.push(row);
161
184
  }
162
- const own = (from) => from !== null && (reader.agentIds.includes(from) || from === reader.personId);
185
+ const own = (g) => g.kind === 'sender' &&
186
+ g.from !== null &&
187
+ (reader.agentIds.includes(g.from) || g.from === reader.personId);
163
188
  const out = {};
164
189
  for (const section of INBOX_SECTIONS) {
165
- const senders = sections.get(section);
166
- if (!senders)
190
+ const groups = sections.get(section);
191
+ if (!groups)
167
192
  continue;
168
- const ordered = [...senders.values()];
193
+ const ordered = [...groups.values()];
169
194
  out[section] = [
170
- ...ordered.filter((g) => !own(g.from)),
171
- ...ordered.filter((g) => own(g.from)),
195
+ ...ordered.filter((g) => !own(g)),
196
+ ...ordered.filter((g) => own(g)),
172
197
  ].map((g) => {
198
+ if (g.kind === 'bucket')
199
+ return writeBucket(g, section, reader, compactRow);
173
200
  const threads = [...g.threads.values()].map((t) => writeThread(t, section, reader, compactRow));
174
201
  const count = [...g.threads.values()].reduce((n, t) => n + t.rows.length, 0);
175
202
  return {
@@ -183,6 +210,29 @@ export function groupDeliveries(rows, reader, compactRow = (row) => ({ ...row })
183
210
  }
184
211
  return out;
185
212
  }
213
+ /** What a row's section already says about it. */
214
+ function dropWhatTheSectionSays(row, section, reader) {
215
+ if (section === 'forYou') {
216
+ if (reader.agentIds.length === 1)
217
+ delete row.assigneeId;
218
+ if (row.assignedToConnection === true)
219
+ delete row.assignedToConnection;
220
+ }
221
+ if (section === 'forYourPerson')
222
+ delete row.needsHuman;
223
+ }
224
+ function writeBucket(group, section, reader, compactRow) {
225
+ const rows = group.rows.map((original) => {
226
+ const { actorId, ...row } = compactRow(original);
227
+ // Every row of one read carries the bucket's revision at that read; one
228
+ // that somehow differs keeps its own.
229
+ if (bucketOf(row)?.revision === group.revision)
230
+ delete row.bucket;
231
+ dropWhatTheSectionSays(row, section, reader);
232
+ return actorId ? { from: actorId, ...row } : row;
233
+ });
234
+ return { bucketId: group.bucketId, revision: group.revision, count: rows.length, rows };
235
+ }
186
236
  function writeThread(thread, section, reader, compactRow) {
187
237
  const [field, id] = thread.anchor;
188
238
  const lifted = field === 'agreementId'
@@ -199,14 +249,7 @@ function writeThread(thread, section, reader, compactRow) {
199
249
  else if (!row.agreementId)
200
250
  row.agreementId = null;
201
251
  }
202
- if (section === 'forYou') {
203
- if (reader.agentIds.length === 1)
204
- delete row.assigneeId;
205
- if (row.assignedToConnection === true)
206
- delete row.assignedToConnection;
207
- }
208
- if (section === 'forYourPerson')
209
- delete row.needsHuman;
252
+ dropWhatTheSectionSays(row, section, reader);
210
253
  return row;
211
254
  });
212
255
  return { [field]: id, ...(lifted ? { agreementId: lifted } : {}), rows };
@@ -299,5 +342,5 @@ export function windowNote(deliveries, selfAgentIds) {
299
342
  const parts = INBOX_SECTIONS.filter((s) => s === 'forYou' || count(s) > 0).map((s) => `${s} ${count(s)} (${words[s]})`);
300
343
  const reach = outOfReach ? ` ${outOfReach} of them ${outOfReach === 1 ? 'is' : 'are'} out of reach.` : '';
301
344
  const total = `${rows.length} ${rows.length === 1 ? 'delivery' : 'deliveries'}`;
302
- return `Those counts are decisions and tasks, not messages. This window has ${total}, grouped by who they are for, then by sender: ${parts.join(', ')}.${reach}`;
345
+ return `Those counts are decisions and tasks, not messages. This window has ${total}, grouped by who they are for, then by bucket, or by sender outside a bucket: ${parts.join(', ')}.${reach}`;
303
346
  }
@@ -0,0 +1,12 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { type Door } from './retiredTools.js';
3
+ /**
4
+ * Answer a direct call to a removed tool with the removed-tool message, before
5
+ * the MCP SDK can say only that the tool was not found. Wraps whatever
6
+ * `tools/call` handler is installed, so call it after every other one.
7
+ *
8
+ * On a session that keeps an open stream, the tool-list-changed signal goes out
9
+ * as well, so an app that acts on it reloads its list without its person doing
10
+ * anything. A session with no stream has nobody to tell; that is not an error.
11
+ */
12
+ export declare function answerRetiredCalls(server: McpServer, door: Door): void;
@@ -0,0 +1,29 @@
1
+ import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
2
+ import { doorContext, retiredTool, retiredToolResult } from './retiredTools.js';
3
+ /**
4
+ * Answer a direct call to a removed tool with the removed-tool message, before
5
+ * the MCP SDK can say only that the tool was not found. Wraps whatever
6
+ * `tools/call` handler is installed, so call it after every other one.
7
+ *
8
+ * On a session that keeps an open stream, the tool-list-changed signal goes out
9
+ * as well, so an app that acts on it reloads its list without its person doing
10
+ * anything. A session with no stream has nobody to tell; that is not an error.
11
+ */
12
+ export function answerRetiredCalls(server, door) {
13
+ const inner = server.server;
14
+ const built = inner._requestHandlers?.get('tools/call');
15
+ if (!built)
16
+ return;
17
+ server.server.setRequestHandler(CallToolRequestSchema, (async (req, extra) => {
18
+ const retired = retiredTool(req.params.name);
19
+ if (!retired)
20
+ return built(req, extra);
21
+ try {
22
+ server.sendToolListChanged();
23
+ }
24
+ catch {
25
+ // No open stream on this session.
26
+ }
27
+ return retiredToolResult(req.params.name, retired, doorContext(server, door));
28
+ }));
29
+ }
@@ -0,0 +1,62 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * Tools we removed, and what replaced them.
4
+ *
5
+ * An assistant that loaded its Ziggs tool list before a removal still holds the
6
+ * old name. Without this table the call dies on the MCP SDK's bare "not found",
7
+ * and the assistant tells its person Ziggs is broken. Every way of reaching a
8
+ * tool by name reads this table: a direct call, both dispatchers, and describe.
9
+ *
10
+ * Retired names are never registered, so they never appear in `tools/list`.
11
+ * Removing a tool adds its row in the same change; retiredTools.test.ts fails
12
+ * when a name leaves the registered set without one.
13
+ */
14
+ export interface Replacement {
15
+ tool: string;
16
+ /** When to use this one, for a job that was split across several tools. */
17
+ when?: string;
18
+ }
19
+ export interface RetiredTool {
20
+ /** The tools that do the job now. Empty when nothing replaces it. */
21
+ use: Replacement[];
22
+ /** What changed, in one line. */
23
+ note?: string;
24
+ }
25
+ export declare const RETIRED_TOOLS: Readonly<Record<string, RetiredTool>>;
26
+ export declare function retiredTool(name: string): RetiredTool | undefined;
27
+ /** The replacement tools for a search word such as `commission` or `ziggs_quest`. */
28
+ export declare function retiredReplacementsFor(needle: string): string[];
29
+ export type Door = 'compact' | 'directory';
30
+ /** How a person refreshes Ziggs in the app the connection came from. */
31
+ export declare function refreshSteps(clientName: string | undefined): string;
32
+ /**
33
+ * The tool result for a call to a removed tool: an ordinary error the assistant
34
+ * can act on. The compact door can call the replacement through its dispatcher
35
+ * at once; the directory door cannot call a tool that is not in its list, so
36
+ * its message asks the person to refresh, once.
37
+ */
38
+ export declare function retiredToolResult(name: string, retired: RetiredTool, ctx: {
39
+ door: Door;
40
+ clientName?: string;
41
+ }): {
42
+ structuredContent: {
43
+ error: {
44
+ code: string;
45
+ message: string;
46
+ retired: {
47
+ name: string;
48
+ replacements: Replacement[];
49
+ };
50
+ };
51
+ };
52
+ content: {
53
+ type: "text";
54
+ text: string;
55
+ }[];
56
+ isError: boolean;
57
+ };
58
+ /** Which door this server was built for, and the client's name once it has connected. */
59
+ export declare function doorContext(server: McpServer, door: Door): {
60
+ door: Door;
61
+ clientName: string | undefined;
62
+ };
@@ -0,0 +1,174 @@
1
+ import { toolError } from './toolError.js';
2
+ const same = (tool, note) => ({ use: [{ tool }], ...(note ? { note } : {}) });
3
+ const gone = (note) => ({ use: [], note });
4
+ const split = (...use) => ({
5
+ use: use.map(([tool, when]) => ({ tool, when })),
6
+ });
7
+ /** One tool does the same job under a new name. */
8
+ const RENAMED = [
9
+ ['ziggs_agreement_commission', 'ziggs_agreement_buy'],
10
+ ['ziggs_agreement_quest', 'ziggs_agreement_request'],
11
+ ['ziggs_cancel_task', 'ziggs_task_cancel'],
12
+ ['ziggs_chat_open', 'ziggs_message_send', 'There is no room to open; send the message.'],
13
+ ['ziggs_chat_send', 'ziggs_message_send'],
14
+ ['ziggs_claim_link_invite', 'ziggs_agreement_claim'],
15
+ ['ziggs_claim_offer', 'ziggs_agreement_claim'],
16
+ ['ziggs_connection_status', 'ziggs_auth_status'],
17
+ ['ziggs_context_request', 'ziggs_access_request'],
18
+ ['ziggs_conversation_send', 'ziggs_message_send'],
19
+ ['ziggs_counter_agreement', 'ziggs_agreement_counter'],
20
+ ['ziggs_create_link_invite', 'ziggs_link_propose'],
21
+ ['ziggs_create_party_connection_invite', 'ziggs_link_propose'],
22
+ ['ziggs_create_task', 'ziggs_task_create'],
23
+ ['ziggs_delegate_grant', 'ziggs_context_delegate'],
24
+ ['ziggs_discover_grantable', 'ziggs_context_discover_grantable'],
25
+ ['ziggs_expand_context', 'ziggs_context_expand_reach'],
26
+ ['ziggs_fulfill_agreement', 'ziggs_agreement_fulfill'],
27
+ ['ziggs_get_active_tasks', 'ziggs_task_list'],
28
+ ['ziggs_get_agent', 'ziggs_agent_get'],
29
+ ['ziggs_get_agreement', 'ziggs_agreement_get'],
30
+ ['ziggs_get_scope', 'ziggs_context_snapshot'],
31
+ ['ziggs_get_task', 'ziggs_task_get'],
32
+ ['ziggs_inbox_park', 'ziggs_inbox_defer'],
33
+ ['ziggs_issue_grant', 'ziggs_context_delegate'],
34
+ ['ziggs_link_create_invite', 'ziggs_link_propose'],
35
+ ['ziggs_list_connections', 'ziggs_connection_list'],
36
+ ['ziggs_list_grants', 'ziggs_grant_list'],
37
+ ['ziggs_list_links', 'ziggs_link_list'],
38
+ ['ziggs_list_messages', 'ziggs_message_list'],
39
+ ['ziggs_list_my_agreements', 'ziggs_agreement_list'],
40
+ ['ziggs_list_my_connections', 'ziggs_connection_list'],
41
+ ['ziggs_list_my_grants', 'ziggs_grant_list'],
42
+ ['ziggs_list_my_orgs', 'ziggs_org_list'],
43
+ ['ziggs_list_party_connections', 'ziggs_link_list'],
44
+ ['ziggs_list_tasks', 'ziggs_task_list'],
45
+ ['ziggs_open_conversation', 'ziggs_message_send'],
46
+ ['ziggs_pending_decisions', 'ziggs_inbox', 'A fresh inbox read carries the decisions.'],
47
+ ['ziggs_publish_quest', 'ziggs_agreement_request'],
48
+ ['ziggs_read_context', 'ziggs_context_read'],
49
+ ['ziggs_record_artifact', 'ziggs_artifact_record'],
50
+ ['ziggs_redeem_party_connection_invite', 'ziggs_agreement_claim'],
51
+ ['ziggs_request_connection_status', 'ziggs_connection_list'],
52
+ ['ziggs_request_link', 'ziggs_link_propose'],
53
+ ['ziggs_request_party_connection', 'ziggs_link_propose'],
54
+ ['ziggs_respond_to_agreement', 'ziggs_agreement_respond'],
55
+ ['ziggs_respond_to_party_connection', 'ziggs_agreement_respond'],
56
+ ['ziggs_revoke_agreement', 'ziggs_agreement_revoke'],
57
+ ['ziggs_revoke_grant', 'ziggs_context_revoke_grant'],
58
+ ['ziggs_revoke_link', 'ziggs_agreement_revoke'],
59
+ ['ziggs_revoke_party_connection', 'ziggs_agreement_revoke'],
60
+ ['ziggs_search_agents', 'ziggs_agent_search'],
61
+ ['ziggs_send_message', 'ziggs_message_send'],
62
+ ['ziggs_set_task_result', 'ziggs_task_set_result'],
63
+ ];
64
+ const PROPOSE = split(['ziggs_agreement_buy', 'you pay a named party'], ['ziggs_agreement_bid', 'you offer your work to a named party'], ['ziggs_agreement_request', 'an open call for anyone'], ['ziggs_agreement_broker', 'you arrange a deal between others'], ['ziggs_agreement_handoff', 'you pass your work on to someone else'], ['ziggs_link_propose', 'you want a link']);
65
+ const LISTING = gone("Posting and pausing a listing is the person's job in the web app. Claim someone's listing with ziggs_agreement_claim; browse with ziggs_marketplace_view.");
66
+ const ROOMS = gone('Rooms are gone. Read with ziggs_inbox or ziggs_message_list.');
67
+ const ANSWERS = gone('Messages are fyi or request now; there are no answer requests. Read replies with ziggs_message_replies.');
68
+ const PAYMENT = gone('Agents do not move points. ziggs_payment_balance is the only payment tool.');
69
+ const OTHER = [
70
+ ['ziggs_agreement_propose', PROPOSE],
71
+ ['ziggs_propose_agreement', PROPOSE],
72
+ ['ziggs_context_issue_grant', split(['ziggs_context_delegate', 'a slice of a grant you hold'], ['ziggs_bucket_insert', 'one file: add it to a bucket'])],
73
+ ['ziggs_conversation_read', split(['ziggs_message_read', 'one message'], ['ziggs_message_list', 'the whole thread, with conversationOf'])],
74
+ ['ziggs_discover_context', split(['ziggs_grant_list', 'what you hold'], ['ziggs_context_discover_grantable', 'what exists that you cannot read yet'])],
75
+ ['ziggs_list_artifacts', split(['ziggs_artifact_list', 'what you wrote'], ['ziggs_context_read', 'what is in a scope, with type artifacts'])],
76
+ ['ziggs_post_task_plan_step', split(['ziggs_task_replace_plan', 'the whole plan'], ['ziggs_task_update_steps', 'progress only'])],
77
+ ['ziggs_tool', split(['ziggs_tool_read', 'reads'], ['ziggs_tool_write', 'writes'])],
78
+ ['ziggs_agreement_offer', LISTING],
79
+ ['ziggs_publish_offer', LISTING],
80
+ ['ziggs_listing_pause', LISTING],
81
+ ['ziggs_listing_resume', LISTING],
82
+ ['ziggs_chat_list', ROOMS],
83
+ ['ziggs_list_chats', ROOMS],
84
+ ['ziggs_message_answers', ANSWERS],
85
+ ['ziggs_message_answer_cancel', ANSWERS],
86
+ ['ziggs_payment_attenuate_grant', PAYMENT],
87
+ ['ziggs_payment_hold', PAYMENT],
88
+ ['ziggs_payment_issue_grant', PAYMENT],
89
+ ['ziggs_payment_release', PAYMENT],
90
+ ['ziggs_payment_resolve_wallet', PAYMENT],
91
+ ['ziggs_payment_revoke_grant', PAYMENT],
92
+ ['ziggs_payment_transfer', PAYMENT],
93
+ ['ziggs_payment_wait_for_approval', PAYMENT],
94
+ ['ziggs_context_record_message', gone('Send messages with ziggs_message_send.')],
95
+ ['ziggs_context_record_thought', gone('Nothing replaces it. Save finished work with ziggs_artifact_record.')],
96
+ ['ziggs_get_subtasks', gone('Nothing replaces it. Nearest: ziggs_task_list, ziggs_task_get.')],
97
+ ['ziggs_mark_task_processing', gone('Nothing replaces it. Nearest: ziggs_inbox_claim.')],
98
+ ['ziggs_update_task_state', gone('Nothing replaces it. Finish with ziggs_task_set_result or stop with ziggs_task_cancel.')],
99
+ ['ziggs_switch_org', gone('The workspace is chosen when Ziggs is connected. See them with ziggs_org_list.')],
100
+ ['ziggs_request_connection', gone('Requesting a connection is not a Ziggs MCP tool any more. See yours with ziggs_connection_list.')],
101
+ ['ziggs_artifact_reextract', gone('Not an agent tool any more.')],
102
+ ['ziggs_provision_relay_workers', gone('Not an agent tool any more. Engage agents with the agreement tools and ziggs_task_create.')],
103
+ ];
104
+ export const RETIRED_TOOLS = Object.freeze({
105
+ ...Object.fromEntries(RENAMED.map(([name, tool, note]) => [name, same(tool, note)])),
106
+ ...Object.fromEntries(OTHER),
107
+ });
108
+ export function retiredTool(name) {
109
+ return Object.hasOwn(RETIRED_TOOLS, name) ? RETIRED_TOOLS[name] : undefined;
110
+ }
111
+ /** The replacement tools for a search word such as `commission` or `ziggs_quest`. */
112
+ export function retiredReplacementsFor(needle) {
113
+ const hits = new Set();
114
+ for (const candidate of [needle, `ziggs_${needle}`, `ziggs_agreement_${needle}`]) {
115
+ for (const entry of retiredTool(candidate)?.use ?? [])
116
+ hits.add(entry.tool);
117
+ }
118
+ return [...hits];
119
+ }
120
+ /** How a person refreshes Ziggs in the app the connection came from. */
121
+ export function refreshSteps(clientName) {
122
+ const name = (clientName ?? '').toLowerCase();
123
+ if (name.includes('chatgpt') || name.includes('openai'))
124
+ return 'In ChatGPT: Settings → Apps & Connectors → Ziggs → Refresh. ChatGPT never refreshes by itself. On Business and Enterprise plans a workspace admin refreshes or republishes it.';
125
+ if (name.includes('cursor'))
126
+ return 'In Cursor: turn Ziggs off and on in MCP settings, or restart Cursor. Cursor ignores the list-changed signal.';
127
+ if (name.includes('claude-code') || name.includes('claude code'))
128
+ return 'In Claude Code: run /mcp and reconnect Ziggs, or start a new session.';
129
+ if (name.includes('claude'))
130
+ return 'In Claude: Settings → Connectors → Ziggs → ⋯ → refresh the tool list, or remove Ziggs and add it again.';
131
+ return "Reconnect Ziggs in the app's connector settings, or start a new conversation.";
132
+ }
133
+ /**
134
+ * The tool result for a call to a removed tool: an ordinary error the assistant
135
+ * can act on. The compact door can call the replacement through its dispatcher
136
+ * at once; the directory door cannot call a tool that is not in its list, so
137
+ * its message asks the person to refresh, once.
138
+ */
139
+ export function retiredToolResult(name, retired, ctx) {
140
+ const lines = [`${name} was removed from Ziggs, so your list of Ziggs tools is out of date.`];
141
+ if (retired.note)
142
+ lines.push(retired.note);
143
+ if (retired.use.length === 1) {
144
+ lines.push(`Use ${retired.use[0].tool} for this now.`);
145
+ }
146
+ else if (retired.use.length > 1) {
147
+ lines.push(`This job is now done by more than one tool: ${retired.use.map((u) => `${u.tool} (${u.when})`).join('; ')}.`);
148
+ }
149
+ if (ctx.door === 'compact') {
150
+ lines.push(retired.use.length
151
+ ? 'Call the replacement now through ziggs_tool_read (read-only tools) or ziggs_tool_write (tools that change something), and also give your person these steps so your tool list is current:'
152
+ : 'Give your person these steps so your tool list is current:');
153
+ }
154
+ else {
155
+ lines.push('You cannot call a tool that is not in your list. Ask your person, once per conversation, to refresh Ziggs:');
156
+ }
157
+ lines.push(refreshSteps(ctx.clientName));
158
+ const result = toolError(lines.join(' '));
159
+ const error = {
160
+ ...result.structuredContent.error,
161
+ code: 'TOOL_RETIRED',
162
+ message: lines.join(' '),
163
+ retired: { name, replacements: retired.use },
164
+ };
165
+ return {
166
+ structuredContent: { error },
167
+ content: [{ type: 'text', text: JSON.stringify({ error }, null, 2) }],
168
+ isError: true,
169
+ };
170
+ }
171
+ /** Which door this server was built for, and the client's name once it has connected. */
172
+ export function doorContext(server, door) {
173
+ return { door, clientName: server.server.getClientVersion()?.name };
174
+ }
package/dist/server.js CHANGED
@@ -10,6 +10,7 @@ import { loadConfig } from './config.js';
10
10
  import { credsFromConfig } from './creds.js';
11
11
  import { registerZiggsTools } from './tools.js';
12
12
  import { applySurfacePolicy, listWithoutSchemaDialect } from './surface.js';
13
+ import { answerRetiredCalls } from './retiredCalls.js';
13
14
  import { backendToolPick } from './toolPick.js';
14
15
  import { renderConnectText } from './protocol/connectText.js';
15
16
  import { CHANNEL_CAPABILITY, observeFor, startInboxChannel } from './inboxChannel.js';
@@ -44,6 +45,7 @@ export function createZiggsMcpServer(creds, cfg) {
44
45
  if (cfg.directConnection !== false)
45
46
  applySurfacePolicy(server, { pickTool: backendToolPick(creds) });
46
47
  listWithoutSchemaDialect(server);
48
+ answerRetiredCalls(server, cfg.directConnection === false ? 'directory' : 'compact');
47
49
  return server;
48
50
  }
49
51
  /** Drop this process's inbox host. Best-effort — shutdown must not fail on it. */
package/dist/surface.js CHANGED
@@ -6,6 +6,7 @@ import { registerStrictTool } from './strictParams.js';
6
6
  import { toolError } from './toolError.js';
7
7
  import { textResult } from './capabilityAdapter.js';
8
8
  import { aliasMatches } from './toolAliases.js';
9
+ import { doorContext, retiredReplacementsFor, retiredTool, retiredToolResult } from './retiredTools.js';
9
10
  import { pickLine } from './toolPick.js';
10
11
  import { catalogEntry, catalogFor, catalogRow, describeEntry, removeCatalogEntry, } from './toolCatalog.js';
11
12
  /**
@@ -141,6 +142,9 @@ function registerDispatcher(server, lane) {
141
142
  }, lane.annotations, async ({ tool, args }) => {
142
143
  const entry = catalogEntry(server, tool);
143
144
  if (!entry) {
145
+ const retired = retiredTool(tool);
146
+ if (retired)
147
+ return retiredToolResult(tool, retired, doorContext(server, 'compact'));
144
148
  return toolError(`No such tool: ${tool}. Call ${TOOLS_CATALOG_NAME} for the full list of names.`);
145
149
  }
146
150
  // Each lane carries one kind of tool, so its annotation is true of every
@@ -218,6 +222,9 @@ export function applySurfacePolicy(server, opts = {}) {
218
222
  unknown.push(name);
219
223
  }
220
224
  if (!found.length) {
225
+ const retired = unknown.map((name) => [name, retiredTool(name)]).find(([, r]) => r);
226
+ if (retired?.[1])
227
+ return retiredToolResult(retired[0], retired[1], doorContext(server, 'compact'));
221
228
  return toolError(`No such tool: ${unknown.join(', ')}. Call ${TOOLS_CATALOG_NAME} with no arguments for the full list of names.`);
222
229
  }
223
230
  return textResult({
@@ -250,7 +257,8 @@ export function applySurfacePolicy(server, opts = {}) {
250
257
  const rows = (needle
251
258
  ? all.filter((e) => e.name.toLowerCase().includes(needle) ||
252
259
  e.title.toLowerCase().includes(needle) ||
253
- aliasMatches(e.name, needle))
260
+ aliasMatches(e.name, needle) ||
261
+ retiredReplacementsFor(needle).includes(e.name))
254
262
  : all).map((entry) => withCall(entry, catalogRow(entry)));
255
263
  return textResult({
256
264
  count: rows.length,
@@ -13,15 +13,12 @@
13
13
  * appear in a catalog row, and cost nothing at connect time because the search
14
14
  * runs server-side.
15
15
  *
16
- * Two kinds of word belong here:
16
+ * The word belonging here is the plain-English word for the intent, when our
17
+ * name is a term of art: "hire" for claim (the default) and for buy (a direct
18
+ * proposal), "subcontract" for the parent-rail verb.
17
19
  *
18
- * 1. The plain-English word for the intent, when our name is a term of art.
19
- * "hire" for claim (the default) and for buy (a direct proposal),
20
- * "subcontract" for the parent-rail verb.
21
- * 2. A name we retired. `agreement_commission` and `agreement_quest` were
22
- * renamed before the surface went public; anything still holding the old
23
- * word searches it and lands on the tool that replaced it, rather than on
24
- * nothing.
20
+ * A retired name is not an alias: retiredTools.ts holds those, and search
21
+ * reads it, so anything still holding an old name lands on its replacement.
25
22
  */
26
23
  export declare const SEARCH_ALIASES: Readonly<Record<string, readonly string[]>>;
27
24
  /** Does `needle` match one of this tool's aliases? Substring, both ways. */
@@ -13,15 +13,12 @@
13
13
  * appear in a catalog row, and cost nothing at connect time because the search
14
14
  * runs server-side.
15
15
  *
16
- * Two kinds of word belong here:
16
+ * The word belonging here is the plain-English word for the intent, when our
17
+ * name is a term of art: "hire" for claim (the default) and for buy (a direct
18
+ * proposal), "subcontract" for the parent-rail verb.
17
19
  *
18
- * 1. The plain-English word for the intent, when our name is a term of art.
19
- * "hire" for claim (the default) and for buy (a direct proposal),
20
- * "subcontract" for the parent-rail verb.
21
- * 2. A name we retired. `agreement_commission` and `agreement_quest` were
22
- * renamed before the surface went public; anything still holding the old
23
- * word searches it and lands on the tool that replaced it, rather than on
24
- * nothing.
20
+ * A retired name is not an alias: retiredTools.ts holds those, and search
21
+ * reads it, so anything still holding an old name lands on its replacement.
25
22
  */
26
23
  export const SEARCH_ALIASES = {
27
24
  ziggs_message_send: ['contact', 'message someone', 'teammate', 'group'],
@@ -30,14 +27,12 @@ export const SEARCH_ALIASES = {
30
27
  ziggs_agreement_claim: ['hire'],
31
28
  ziggs_agreement_buy: [
32
29
  'hire',
33
- 'commission', // retired name
34
30
  'procure',
35
31
  'outsource',
36
32
  'engage',
37
33
  ],
38
34
  ziggs_agreement_bid: ['sell', 'pitch', 'apply', 'tender', 'quote'],
39
35
  ziggs_agreement_request: [
40
- 'quest', // retired name
41
36
  'wanted',
42
37
  'open call',
43
38
  'rfp',
package/dist/tools.js CHANGED
@@ -2,7 +2,7 @@ import { MESSAGING_CAPABILITIES } from '@ziggs-ai/api-client';
2
2
  import { isDirectorySurface } from './directorySurface.js';
3
3
  import { INBOX_CARD_TOOL_META, inboxCardResult, registerInboxCardResource, } from './inboxCard.js';
4
4
  import { z } from 'zod';
5
- import { presentResourceActions, getAgreement, getMyAgreements, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, ConnectionsClient, ContextReadClient, GrantsClient, InboxClient, SessionsClient, AssistantConnectionClient, createTask, updateTaskState, acknowledgeTaskControl, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateSession, GRANTS_CAPABILITIES, AGREEMENT_TERMS_PARAMS, AGREEMENT_VERB_CAPABILITIES, LISTING_DOOR_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, openCapability, accessExplainCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, reviewArtifactContributionCapability, contributeArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, agreementSubcontractCapability, listTasksCapability, cancelTaskCapability, delegateTaskCapability, marketplaceViewCapability, parseListFields, pickListedRows, listedFieldNote, sessionOrientation, presentTaskOutcome, explainRefusedRead, RESOURCE_HIDDEN_MESSAGE, SOURCES_DESCRIPTION, } from '@ziggs-ai/api-client';
5
+ import { presentResourceActions, getAgreement, getMyAgreements, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, ConnectionsClient, ContextReadClient, GrantsClient, InboxClient, SessionsClient, AssistantConnectionClient, createTask, updateTaskState, acknowledgeTaskControl, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateSession, GRANTS_CAPABILITIES, BUCKET_CAPABILITIES, AGREEMENT_TERMS_PARAMS, AGREEMENT_VERB_CAPABILITIES, LISTING_DOOR_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, openCapability, accessExplainCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, attachArtifactCapability, reviewArtifactContributionCapability, contributeArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, agreementSubcontractCapability, listTasksCapability, cancelTaskCapability, delegateTaskCapability, marketplaceViewCapability, parseListFields, pickListedRows, listedFieldNote, sessionOrientation, presentTaskOutcome, explainRefusedRead, RESOURCE_HIDDEN_MESSAGE, SOURCES_DESCRIPTION, } from '@ziggs-ai/api-client';
6
6
  import { decodeOperatorKeyClaims } from './operatorKey.js';
7
7
  import { registerTrustTools } from './trustTools.js';
8
8
  import { registerPaymentTools } from './paymentTools.js';
@@ -801,6 +801,9 @@ export function registerZiggsTools(server, creds, cfg) {
801
801
  }
802
802
  });
803
803
  registerCapabilities(server, GRANTS_CAPABILITIES, creds);
804
+ // Buckets: compact door only until runs work in buckets; the directory
805
+ // allow-list leaves them out.
806
+ registerCapabilities(server, BUCKET_CAPABILITIES, creds);
804
807
  registerCapability(server, contextExpandReachCapability, creds);
805
808
  registerCapability(server, contextDiscoverGrantableCapability, creds);
806
809
  registerCapability(server, openCapability, creds);
@@ -827,7 +830,6 @@ export function registerZiggsTools(server, creds, cfg) {
827
830
  // question and carries its own honesty: what it searched, and what it could
828
831
  // not.
829
832
  registerCapability(server, findArtifactsCapability, creds);
830
- registerCapability(server, shareArtifactCapability, creds);
831
833
  registerCapability(server, attachArtifactCapability, creds);
832
834
  registerCapability(server, reviewArtifactContributionCapability, creds);
833
835
  registerCapability(server, contributeArtifactCapability, creds);
@@ -5,6 +5,6 @@ import type { ZiggsMcpConfig } from './config.js';
5
5
  * The SDK-twin tools (search/get, links, delegate) come from the shared
6
6
  * capability layer; only revoking a grant stays MCP-local. Issuing one is a
7
7
  * person's act (POST /context/grants takes a signed-in session), so there is
8
- * no agent tool for it: an agent shares with artifact_share, a chat_open
9
- * invite, or context_delegate. */
8
+ * no agent tool for it: an artifact reaches people through a bucket, and an
9
+ * agent passes on a slice of its own grant with context_delegate. */
10
10
  export declare function registerTrustTools(server: McpServer, creds: Creds, cfg?: ZiggsMcpConfig): void;
@@ -9,8 +9,8 @@ const DEFAULT_WEB_URL = 'https://ziggsai.com';
9
9
  * The SDK-twin tools (search/get, links, delegate) come from the shared
10
10
  * capability layer; only revoking a grant stays MCP-local. Issuing one is a
11
11
  * person's act (POST /context/grants takes a signed-in session), so there is
12
- * no agent tool for it: an agent shares with artifact_share, a chat_open
13
- * invite, or context_delegate. */
12
+ * no agent tool for it: an artifact reaches people through a bucket, and an
13
+ * agent passes on a slice of its own grant with context_delegate. */
14
14
  export function registerTrustTools(server, creds, cfg) {
15
15
  const webUrl = cfg?.ZIGGS_WEB_URL?.replace(/\/$/, '') ?? DEFAULT_WEB_URL;
16
16
  registerCapabilities(server, DISCOVERY_CAPABILITIES, creds);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.41.6",
3
+ "version": "0.41.8",
4
4
  "description": "MCP server for Claude Code, Cursor, and other MCP hosts — act as your Ziggs delegate agent",
5
5
  "homepage": "https://ziggsai.com/docs",
6
6
  "bugs": {
@@ -51,13 +51,13 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@modelcontextprotocol/sdk": "^1.29.0",
54
- "@ziggs-ai/api-client": "0.40.4",
54
+ "@ziggs-ai/api-client": "0.40.6",
55
55
  "dotenv": "^16.6.1",
56
56
  "zod": "^3.24.2",
57
57
  "zod-to-json-schema": "^3.25.1"
58
58
  },
59
59
  "devDependencies": {
60
- "@ziggs-ai/agent-sdk": "0.46.3"
60
+ "@ziggs-ai/agent-sdk": "0.46.4"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=20"
@@ -56,7 +56,7 @@ counterparty owner approves it, and unpublished delegates can then reach each ot
56
56
  3. Target human approves via **`ziggs_agreement_respond`** (action `approve`) when the
57
57
  pending link agreement shows in their inbox / `ziggs_link_list`. This is the same generic
58
58
  approval tool used for hire and service proposals: there is no separate link-response tool.
59
- 4. **`ziggs_message_send`** with explicit readers, delivery recipients and any requested answer. To share existing context use **`ziggs_artifact_share`** for an exact artifact or **`ziggs_context_delegate`** for a bounded slice of a grant you hold. Contact, sharing and work remain separate operations.
59
+ 4. **`ziggs_message_send`** with explicit readers, delivery recipients and any requested answer. To give someone an artifact, add it to a bucket with them (**`ziggs_bucket_insert`**, or **`ziggs_bucket_create`** with items); use **`ziggs_context_delegate`** for a bounded slice of a grant you hold. Contact, sharing and work remain separate operations.
60
60
  5. To read their context, they grant it or you ask with **`ziggs_access_request`**.
61
61
  6. `ziggs_context_revoke_grant` when context access should end; **`ziggs_agreement_revoke`** when
62
62
  the agreement should end (any kind: hire, service, request, offer, or link).
@@ -83,4 +83,4 @@ which workspace it is, and call `ziggs_introduction_redeem` with `confirm: true`
83
83
  ## Web UI (Settings → Connections)
84
84
 
85
85
  Confusingly named after the *other* "connection" (this is the agent-link handshake UI, not
86
- `ziggs_connection_proxy` third-party connections). After an agent link appears under **Connected**, use your Claude delegate (MCP) for **`ziggs_message_send`**, **`ziggs_artifact_share`** or **`ziggs_context_delegate`**. The web UI manages handshake only, not chat or context grants.
86
+ `ziggs_connection_proxy` third-party connections). After an agent link appears under **Connected**, use your Claude delegate (MCP) for **`ziggs_message_send`**, a bucket with them, or **`ziggs_context_delegate`**. The web UI manages handshake only, not chat or context grants.