@ziggs-ai/ziggs-mcp 0.1.10 → 0.1.11

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
@@ -179,6 +179,12 @@ ZIGGS_OPERATOR_KEY_B=... ZIGGS_AGENT_ID_B=... \
179
179
  ZIGGS_APPROVER_OPERATOR_KEY=... ZIGGS_APPROVER_USER_ID=... \
180
180
  ZIGGS_SMOKE_CHAT_ID=... \
181
181
  node scripts/smoke-ziggs-mcp-z433-e2e.mjs
182
+
183
+ # ZIG-481 / ZIG-520 party handshake (link agreement, prod)
184
+ HTTP_URL=https://api.ziggsai.com \
185
+ OP_KEY_A=... AGENT_A=... USER_B=... OP_KEY_B=... AGENT_B=... \
186
+ npm run smoke:zig-481-prod
187
+ # Runbook: docs/evals/party-connection-two-claude.md (ZIG-521)
182
188
  ```
183
189
 
184
190
  ---
@@ -197,6 +203,9 @@ ZIGGS_SMOKE_CHAT_ID=... \
197
203
  | `ziggs_issue_grant` | Chat admission or `POST /context/grants` |
198
204
  | `ziggs_delegate_grant` | `POST /context/grants/:id/delegate` |
199
205
  | `ziggs_revoke_grant` | `DELETE /context/grants/:id` |
206
+ | `ziggs_request_link` | `POST /agreements` `{engagementKind:"link"}` (ZIG-481 — a link is just an agreement) |
207
+ | `ziggs_list_links` | `GET /agreements?engagementKind=link` |
208
+ | `ziggs_revoke_link` | `DELETE /agreements/:agreementId` |
200
209
  | `ziggs_smoke_impersonation` | Smoke step 1 |
201
210
  | `ziggs_get_scope` | `GET /scope?via=` |
202
211
  | `ziggs_list_my_agreements` | `GET /agreements?scope=mine` |
@@ -206,7 +215,7 @@ ZIGGS_SMOKE_CHAT_ID=... \
206
215
  | `ziggs_list_messages` | `GET /chats/:id/messages` |
207
216
  | `ziggs_send_message` | `POST /chats/:id/messages` |
208
217
  | `ziggs_propose_agreement` | `POST /agreements/proposals` |
209
- | `ziggs_respond_to_agreement` | `POST /agreements/:id/respond` |
218
+ | `ziggs_respond_to_agreement` | `PUT /agreements/:id/approvals/:partyId` (owner principal; approves hire, service, and `link` proposals) |
210
219
 
211
220
  ---
212
221
 
package/dist/tools.js CHANGED
@@ -133,7 +133,7 @@ export function registerZiggsTools(server, creds, cfg) {
133
133
  return toolError(e.message);
134
134
  }
135
135
  });
136
- server.tool('ziggs_open_conversation', 'Open or reuse a chat with a user or agent participant.', {
136
+ 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.', {
137
137
  participantId: z.string().describe('User or agent id to converse with'),
138
138
  }, async ({ participantId }) => {
139
139
  try {
@@ -164,7 +164,7 @@ export function registerZiggsTools(server, creds, cfg) {
164
164
  return toolError(e.message);
165
165
  }
166
166
  });
167
- server.tool('ziggs_send_message', 'Send a chat message as the delegate agent (requires chat membership).', {
167
+ server.tool('ziggs_send_message', 'Send a chat message as the delegate agent (requires chat membership). Cross-org first contact requires an ACTIVE link first (ziggs_request_link / ziggs_create_link_invite, then approve/claim); without it, messaging an agent outside your org fails with AGENT_NOT_PUBLISHED.', {
168
168
  chatId: z.string(),
169
169
  receiverId: z.string().describe('User or agent id receiving the message'),
170
170
  text: z.string(),
@@ -217,13 +217,17 @@ export function registerZiggsTools(server, creds, cfg) {
217
217
  return toolError(e.message);
218
218
  }
219
219
  });
220
- server.tool('ziggs_respond_to_agreement', 'Approve or reject a pending proposal as the token owner (payer-side delegate).', {
220
+ server.tool('ziggs_respond_to_agreement', 'Approve or reject a pending agreement (ZIG-524). Uses PUT /approvals/:partyId or POST /claim for open broadcast.', {
221
221
  agreementId: z.string(),
222
222
  action: z.enum(['approve', 'reject']),
223
223
  }, async ({ agreementId, action }) => {
224
224
  try {
225
- const agreement = await respondToAgreement(agreementId, action, creds);
226
- return textResult({ agreement });
225
+ const claims = decodeOperatorKeyClaims(creds.operatorKey);
226
+ const ownerId = claims?.ownerId ?? cfg.ZIGGS_OWNER_USER_ID;
227
+ const updated = await respondToAgreement(agreementId, action, creds, {
228
+ ownerUserId: ownerId,
229
+ });
230
+ return textResult({ agreement: updated });
227
231
  }
228
232
  catch (e) {
229
233
  return toolError(e.message);
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { AgentSearchClient, ContextGrantsClient, addChatMember, } from '@ziggs-ai/api-client';
2
+ import { AgentSearchClient, ContextGrantsClient, createAgreement, listAgreements, revokeAgreement, claimLink, addChatMember, } from '@ziggs-ai/api-client';
3
3
  function textResult(data) {
4
4
  return {
5
5
  content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
@@ -15,8 +15,8 @@ const grantScopeKindSchema = z.enum(['chat', 'agreement', 'org']);
15
15
  const contextTemporalSchema = z.enum(['from-now', 'from-start']);
16
16
  /** ZIG-433 — agent search + context grant management through MCP. */
17
17
  export function registerTrustTools(server, creds) {
18
- server.tool('ziggs_search_agents', 'Search published agents by query (AgentSearchClient). Use returned agentId in grant/issue tools — do not guess ids.', {
19
- query: z.string().describe('Natural language or keyword search'),
18
+ server.tool('ziggs_search_agents', 'Find agents (AgentSearchClient). A keyword/natural-language query searches PUBLISHED agents only. Passing an EXACT agent id resolves that one agent even if it is unpublished/private — use this to look up a delegate someone shared their id for, then ziggs_request_link against it (ZIG-480). Use returned agentId in grant/issue tools — do not guess ids.', {
19
+ query: z.string().describe('Keyword/natural-language search (published agents) OR an exact agent id (resolves that agent even if unpublished)'),
20
20
  limit: z.number().optional().describe('Max results (default server-side)'),
21
21
  minScore: z.number().optional().describe('Minimum match score filter'),
22
22
  }, async ({ query, limit, minScore }) => {
@@ -151,6 +151,99 @@ export function registerTrustTools(server, creds) {
151
151
  return toolError(e.message);
152
152
  }
153
153
  });
154
+ server.tool('ziggs_request_link', 'Request a bilateral trust link with another agent before cross-org reach. A link is just an agreement (POST /agreements {engagementKind:"link"}, ZIG-481). The target OWNER must approve it (via ziggs_respond_to_agreement) before unpublished delegates can message each other.', {
155
+ providerId: z
156
+ .string()
157
+ .describe('Bare agent id to link with (the target delegate). Use ziggs_search_agents or a known delegate id — do not guess.'),
158
+ message: z
159
+ .string()
160
+ .optional()
161
+ .describe('Optional note shown to the counterparty human on approval (agreement description)'),
162
+ }, async ({ providerId, message }) => {
163
+ try {
164
+ const { agreement } = await createAgreement({ engagementKind: 'link', providerId, description: message }, creds);
165
+ return textResult({
166
+ status: 'pending',
167
+ message: 'Link agreement created — the counterparty owner must approve (ziggs_respond_to_agreement) before cross-org reach. Surface pending state to the human.',
168
+ agreement,
169
+ });
170
+ }
171
+ catch (e) {
172
+ return toolError(e.message);
173
+ }
174
+ });
175
+ server.tool('ziggs_create_link_invite', 'Create a shareable OPEN link invite when you do NOT have the counterparty\'s agent id (e.g. connecting across orgs) (ZIG-525). Creates an open link agreement (POST /agreements {engagementKind:"link"}, proposedTo:"everyone"). Share the returned inviteId (agreementId) out-of-band; the recipient forms the link by calling ziggs_claim_link_invite — neither side pastes an agent id. Revoke via ziggs_revoke_link to disable.', {
176
+ message: z
177
+ .string()
178
+ .optional()
179
+ .describe('Optional note shown to whoever opens the invite (agreement description)'),
180
+ }, async ({ message }) => {
181
+ try {
182
+ const { agreement } = await createAgreement({ engagementKind: 'link', description: message }, creds);
183
+ return textResult({
184
+ status: 'open',
185
+ inviteId: agreement.agreementId,
186
+ message: 'Open link invite created. Share inviteId (agreementId) with the counterparty; they claim it via ziggs_claim_link_invite. No agent id needed on either side.',
187
+ agreement,
188
+ });
189
+ }
190
+ catch (e) {
191
+ return toolError(e.message);
192
+ }
193
+ });
194
+ server.tool('ziggs_claim_link_invite', 'Claim an open link invite by its id to form a bilateral link (POST /agreements/:id/claim, ZIG-525). You become the counterparty and the link activates immediately (cross-org reach + bilateral context grants). You cannot claim your own invite.', {
195
+ agreementId: z
196
+ .string()
197
+ .describe('The invite id (agreementId) shared by the issuer'),
198
+ }, async ({ agreementId }) => {
199
+ try {
200
+ const { agreement } = await claimLink(agreementId, creds);
201
+ return textResult({
202
+ status: 'linked',
203
+ message: 'Link invite claimed — you are now linked. A link is reach-only: use ziggs_open_conversation and/or ziggs_issue_grant before reading context.',
204
+ agreement,
205
+ });
206
+ }
207
+ catch (e) {
208
+ return toolError(e.message);
209
+ }
210
+ });
211
+ 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 () => {
212
+ try {
213
+ const links = await listAgreements({ engagementKind: 'link' }, creds);
214
+ const hasActive = links.some((a) => a.status === 'active');
215
+ return textResult({
216
+ count: links.length,
217
+ links,
218
+ ...(hasActive
219
+ ? {
220
+ nextSteps: 'A link is reach-only. Use ziggs_open_conversation (participantId = peer agent id) and/or ziggs_issue_grant before reading context.',
221
+ }
222
+ : {}),
223
+ });
224
+ }
225
+ catch (e) {
226
+ return toolError(e.message);
227
+ }
228
+ });
229
+ server.tool('ziggs_revoke_link', 'Revoke a bilateral link agreement (DELETE /agreements/:agreementId, ZIG-481). Either party may revoke; cross-org reach ends immediately.', {
230
+ agreementId: z
231
+ .string()
232
+ .describe('agreementId of the link agreement (from ziggs_list_links)'),
233
+ }, async ({ agreementId }) => {
234
+ try {
235
+ const result = await revokeAgreement(agreementId, creds);
236
+ return textResult({
237
+ status: 'revoked',
238
+ message: 'Link revoked — unpublished cross-org reach to this peer is blocked again.',
239
+ agreementId,
240
+ agreement: result.agreement,
241
+ });
242
+ }
243
+ catch (e) {
244
+ return toolError(e.message);
245
+ }
246
+ });
154
247
  server.tool('ziggs_revoke_grant', 'Revoke a context grant and its descendants (DELETE /context/grants/:id). Requires context:admin on the operator key.', {
155
248
  grantId: z.string(),
156
249
  }, async ({ grantId }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
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": {
@@ -29,8 +29,24 @@ These commonly surface as **`pending_approval`** or blocked tool errors:
29
29
 
30
30
  ## Trust tool sequence (cross-org)
31
31
 
32
+ A **link is just an agreement** (`engagementKind: "link"`, ZIG-481). Create it, the
33
+ counterparty owner approves it, and unpublished delegates can then reach each other.
34
+
32
35
  1. Human describes goal and counterparty.
33
- 2. `ziggs_search_agents` — present candidates; human picks.
34
- 3. `ziggs_issue_grant` or chat admission flow — wait for approval if required.
35
- 4. `ziggs_read_context` only after grant is active.
36
- 5. `ziggs_revoke_grant` when the human says access should end.
36
+ 2. Create the link with **`ziggs_request_link`** (`providerId` = the target delegate agent id;
37
+ use `ziggs_search_agents` to find published service agents — do not guess ids).
38
+ 3. Target human approves via **`ziggs_respond_to_agreement`** (action `approve`) when the
39
+ pending link agreement shows in their inbox / `ziggs_list_links`. This is the same generic
40
+ approval tool used for hire and service proposals — there is no separate link-response tool.
41
+ 4. **A link ≠ a grant.** The link only allows unpublished delegates to *reach* each other. To
42
+ chat or read context, continue with **`ziggs_open_conversation`** (participantId = peer agent
43
+ id) and/or **`ziggs_issue_grant`** — still approval-gated when exposing existing scope.
44
+ 5. `ziggs_read_context` only after grant is active.
45
+ 6. `ziggs_revoke_grant` when context access should end; **`ziggs_revoke_link`** when the
46
+ bilateral link should end.
47
+
48
+ **Org join:** accepting an org invite auto-links inviter ↔ joiner delegates when both exist (no separate MCP step).
49
+
50
+ ## Web UI (Settings → Connections)
51
+
52
+ After an agent link appears under **Connected**, use your Claude delegate (MCP) for **`ziggs_open_conversation`** or **`ziggs_issue_grant`**. The web UI manages handshake only — not chat or context grants.