@ziggs-ai/ziggs-mcp 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # @ziggs-ai/ziggs-mcp
2
2
 
3
- MCP (stdio) server for **Claude Code**, **Cursor**, and other MCP hosts.
3
+ **Ziggs MCP** connects a brain in an existing MCP application to an agent on Ziggs. It is a peer to the [Ziggs SDK](../agent-sdk/README.md): the platform supplies the body (identity, inbox, wallet and contracts), while your application supplies the brain and its wake schedule. Use the remote endpoint or this stdio package.
4
+
5
+ A connection alone does not wake an unattended brain. Configure your application to check `ziggs_inbox` on a schedule, read and act on its deliveries, then acknowledge them. The packaged [inbox rhythm](./skills/ziggs/references/inbox-rhythm.md) describes that loop.
6
+
7
+ SDK `createMcpAgent` is a separate integration: it gives an SDK agent tools from an external MCP server. You do not need it to use Ziggs MCP.
4
8
 
5
9
  **In scope:** chat, agreements (service and hire, direct or published), scope, context discovery/reads, artifacts, points (`ziggs_payment_balance` — reading what you hold).
6
10
  Points move as a consequence of an agreement settling; there is no agent-side tool to move them, and none to approve a movement. A settlement paused above the wallet owner's policy is decided by the human on the wallet page, and a cold `ziggs_inbox` is where an agent sees that one is waiting.
@@ -130,7 +130,7 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
130
130
  case 'agreement':
131
131
  case 'request':
132
132
  // Deliberately no read call. Tasks/proposals arrive as standing state
133
- // elsewhere on the envelope; requests ride `requestsAwaitingMe`
133
+ // elsewhere on the envelope; requests ride `openRequestsAwaitingMe`
134
134
  // and are host-triaged with a plain string compare — never an LLM read
135
135
  // plan entry (that would recreate the per-request token drain).
136
136
  break;
@@ -153,9 +153,10 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
153
153
  // that would clear deliveries the plan never asked the agent to handle.
154
154
  // Reserve the ack slot only when every candidate still fits beside it;
155
155
  // otherwise spend the full budget on reads and omit ack.
156
- const canAckFully = !!inbox.ackTo &&
157
- !inbox.deliveriesCapped &&
158
- (inbox.truncatedRequests ?? 0) === 0;
156
+ // A capped list is the oldest head. Acking it (with the listed ids) is
157
+ // safe — the newer tail is past ackTo. A truncated *plan* still omits
158
+ // ack, because those steps never asked the agent to handle the rest.
159
+ const canAckFully = !!inbox.ackTo && (inbox.truncatedRequests ?? 0) === 0;
159
160
  const leaveRoomForAck = canAckFully && candidates.length <= MAX_READ_PLAN - 1;
160
161
  const budget = leaveRoomForAck ? MAX_READ_PLAN - 1 : MAX_READ_PLAN;
161
162
  const truncated = Math.max(0, candidates.length - budget);
@@ -170,7 +171,7 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
170
171
  ...(inbox.deliveries ?? [])
171
172
  .filter((d) => self.agentId === '' || d.assigneeId === self.agentId)
172
173
  .map((d) => d.resourceId),
173
- ...(inbox.requestsAwaitingMe ?? []).map((q) => q.agreementId),
174
+ ...(inbox.openRequestsAwaitingMe ?? []).map((q) => q.agreementId),
174
175
  ]),
175
176
  ].filter((id) => typeof id === 'string' && id.length > 0);
176
177
  plan.push({
@@ -14,6 +14,18 @@ export declare function resolveMcpGatewayTarget(opts: {
14
14
  operatorKey: string;
15
15
  agentId: string;
16
16
  baseUrl?: string;
17
+ /**
18
+ * The wake's lane (a chat id, or `agrn-<agreementId>`), sent as
19
+ * `X-Ziggs-Lane`. The broker reads off it who the agent is acting for and
20
+ * checks the grant against the party who gave it.
21
+ *
22
+ * Without it this door was the unfenced one: the named-connector proxy has
23
+ * stated its lane for a while, so the same account and the same grant got
24
+ * two different answers depending on which door the agent used. Omitting it
25
+ * cannot widen anything — a stated lane can only cause a refusal — but it
26
+ * does leave the spend unfenced.
27
+ */
28
+ laneId?: string;
17
29
  }): {
18
30
  url: URL;
19
31
  headers: Record<string, string>;
@@ -25,6 +25,7 @@ export function resolveMcpGatewayTarget(opts) {
25
25
  authorization: `Bearer ${opts.operatorKey}`,
26
26
  'x-agent-id': opts.agentId,
27
27
  'x-grant-id': opts.grantId,
28
+ ...(opts.laneId ? { 'x-ziggs-lane': opts.laneId } : {}),
28
29
  },
29
30
  };
30
31
  }
@@ -35,7 +36,12 @@ export async function resolveMcpConnectionTarget(creds, connectionId, grantId) {
35
36
  throw new Error('operatorKey is required');
36
37
  if (!creds.agentId)
37
38
  throw new Error('agentId is required');
38
- const groups = await new ConnectionsClient(creds.operatorKey, creds.agentId).listForHolder();
39
+ // The lane matters here too: since the grant list answers for the party the
40
+ // wake acts for, a lane-less list comes back spanning every customer this
41
+ // agent serves — and this is the path that PICKS one when the caller named
42
+ // no connection. baseUrl was missing as well, so it only ever worked
43
+ // against the default backend.
44
+ const groups = await new ConnectionsClient(creds.operatorKey, creds.agentId, getBackendUrl(), creds.laneId).listForHolder();
39
45
  const live = groups.filter((g) => (g.grants ?? []).length > 0);
40
46
  if (live.length === 0) {
41
47
  throw new Error('No live connection grant. Ask the connection owner to issue one for this agent, then retry.');
@@ -85,6 +91,7 @@ export async function withMcpGatewayClient(creds, connectionId, grantId, run) {
85
91
  grantId,
86
92
  operatorKey: creds.operatorKey,
87
93
  agentId: creds.agentId,
94
+ laneId: creds.laneId,
88
95
  });
89
96
  return gatewayRunner(target, run);
90
97
  }
@@ -1,47 +1,6 @@
1
- /** JWT payload fields we read client-side (signature not verified — identity hint only). */
2
- export interface OperatorKeyClaims {
3
- type?: string;
4
- keyId?: string;
5
- ownerId?: string;
6
- boundAgentId?: string | null;
7
- /**
8
- * How the key was minted. The backend stamps `mcp_oauth` on every access
9
- * token the MCP OAuth consent flow issues, refreshes included; a key minted
10
- * anywhere else (dashboard, CLI, provisioning) carries nothing here.
11
- */
12
- issuedVia?: string;
13
- exp?: number;
14
- }
1
+ export { decodeOperatorKeyClaims, isOperatorKeyExpired, isDirectoryBoarded, ISSUED_VIA_MCP_OAUTH, ISSUED_VIA_DEVICE_CODE } from '@ziggs-ai/api-client';
2
+ export type { OperatorKeyClaims } from '@ziggs-ai/api-client';
15
3
  declare const MINT_KEY_HELP: string;
16
- /** Decode operator JWT payload without verifying signature (boundAgentId). */
17
- export declare function decodeOperatorKeyClaims(token: string): OperatorKeyClaims | null;
18
- export declare function isOperatorKeyExpired(claims: OperatorKeyClaims | null): boolean;
19
- /** The stamp the MCP OAuth authorization-code consent flow puts on its tokens. */
20
- export declare const ISSUED_VIA_MCP_OAUTH = "mcp_oauth";
21
- /** The stamp the MCP OAuth device-code flow puts on its tokens. */
22
- export declare const ISSUED_VIA_DEVICE_CODE = "device_code";
23
- /**
24
- * Did a person board this credential through the connector directory?
25
- *
26
- * That is the question the tool surface turns on: a connected assistant
27
- * (Claude, ChatGPT, Cursor) gets the listed, directory-reviewed surface, and
28
- * anyone who configured this server themselves with an operator key gets the
29
- * whole thing.
30
- *
31
- * Ask it as a predicate, never as `issuedVia === 'mcp_oauth'`. Two consent rails
32
- * board an assistant through that door — the authorization-code flow and the
33
- * device-code flow — and both want the same narrowed surface, but the server
34
- * records them as the different acts they are. An equality test against one
35
- * value serves the whole catalogue to every credential from the other one.
36
- *
37
- * Read off the unverified payload. On the remote endpoint the backend verified
38
- * the signature before this ran; on stdio the key is the caller's own. Either
39
- * way the payload is the one the backend signed. And what hangs on the answer
40
- * is which tools are registered, not what a call may do: every call is still
41
- * authorized by the backend. Provenance may decide what is OFFERED, never what
42
- * is PERMITTED.
43
- */
44
- export declare function isDirectoryBoarded(claims: OperatorKeyClaims | null): boolean;
45
4
  /**
46
5
  * Resolve delegate agent id: agent-scoped key (boundAgentId) wins; else ZIGGS_AGENT_ID.
47
6
  */
@@ -1,62 +1,8 @@
1
+ import { decodeOperatorKeyClaims, isOperatorKeyExpired } from '@ziggs-ai/api-client';
2
+ export { decodeOperatorKeyClaims, isOperatorKeyExpired, isDirectoryBoarded, ISSUED_VIA_MCP_OAUTH, ISSUED_VIA_DEVICE_CODE } from '@ziggs-ai/api-client';
1
3
  const MINT_KEY_HELP = 'Mint a key in the Ziggs app: Developer Portal → Operator keys (fleet key + set ZIGGS_AGENT_ID), ' +
2
4
  'or open your delegate agent → Issue operator key (agent-scoped — no ZIGGS_AGENT_ID needed). ' +
3
5
  'Docs: https://ziggsai.com/docs (Claude Code MCP tier).';
4
- /** Decode operator JWT payload without verifying signature (boundAgentId). */
5
- export function decodeOperatorKeyClaims(token) {
6
- const trimmed = token.trim();
7
- const parts = trimmed.split('.');
8
- if (parts.length !== 3)
9
- return null;
10
- try {
11
- const json = Buffer.from(parts[1], 'base64url').toString('utf8');
12
- const payload = JSON.parse(json);
13
- return {
14
- type: payload.type,
15
- keyId: payload.keyId,
16
- ownerId: payload.ownerId,
17
- boundAgentId: payload.boundAgentId ?? null,
18
- issuedVia: typeof payload.issuedVia === 'string' ? payload.issuedVia : undefined,
19
- exp: payload.exp,
20
- };
21
- }
22
- catch {
23
- return null;
24
- }
25
- }
26
- export function isOperatorKeyExpired(claims) {
27
- if (!claims?.exp)
28
- return false;
29
- return claims.exp * 1000 <= Date.now();
30
- }
31
- /** The stamp the MCP OAuth authorization-code consent flow puts on its tokens. */
32
- export const ISSUED_VIA_MCP_OAUTH = 'mcp_oauth';
33
- /** The stamp the MCP OAuth device-code flow puts on its tokens. */
34
- export const ISSUED_VIA_DEVICE_CODE = 'device_code';
35
- /**
36
- * Did a person board this credential through the connector directory?
37
- *
38
- * That is the question the tool surface turns on: a connected assistant
39
- * (Claude, ChatGPT, Cursor) gets the listed, directory-reviewed surface, and
40
- * anyone who configured this server themselves with an operator key gets the
41
- * whole thing.
42
- *
43
- * Ask it as a predicate, never as `issuedVia === 'mcp_oauth'`. Two consent rails
44
- * board an assistant through that door — the authorization-code flow and the
45
- * device-code flow — and both want the same narrowed surface, but the server
46
- * records them as the different acts they are. An equality test against one
47
- * value serves the whole catalogue to every credential from the other one.
48
- *
49
- * Read off the unverified payload. On the remote endpoint the backend verified
50
- * the signature before this ran; on stdio the key is the caller's own. Either
51
- * way the payload is the one the backend signed. And what hangs on the answer
52
- * is which tools are registered, not what a call may do: every call is still
53
- * authorized by the backend. Provenance may decide what is OFFERED, never what
54
- * is PERMITTED.
55
- */
56
- export function isDirectoryBoarded(claims) {
57
- return (claims?.issuedVia === ISSUED_VIA_MCP_OAUTH ||
58
- claims?.issuedVia === ISSUED_VIA_DEVICE_CODE);
59
- }
60
6
  /**
61
7
  * Resolve delegate agent id: agent-scoped key (boundAgentId) wins; else ZIGGS_AGENT_ID.
62
8
  */
@@ -1,3 +1,4 @@
1
+ export { resolveWebAppOrigin, agreementAppUrl, agreementsListAppUrl, connectionsSettingsAppUrl, walletAppUrl } from '@ziggs-ai/api-client';
1
2
  import { type InboxEnvelope, type InboxTaskRef, type Task } from '@ziggs-ai/api-client';
2
3
  /**
3
4
  * The two ids this delegate answers for, in the order authority is checked:
@@ -79,13 +80,6 @@ export interface PaymentApprovalItem {
79
80
  * second tool to go call.
80
81
  */
81
82
  export declare const SESSION_CARD_POINTER = "Call ziggs_inbox without waitSeconds and paste its sessionChatCard for the human.";
82
- export declare function resolveWebAppOrigin(webUrl?: string | null): string;
83
- export declare function agreementAppUrl(origin: string, agreementId: string): string;
84
- export declare function agreementsListAppUrl(origin: string): string;
85
- /** Where the human connects MCP servers and grants tools. */
86
- export declare function connectionsSettingsAppUrl(origin: string): string;
87
- /** Where the human decides paused transfers. */
88
- export declare function walletAppUrl(origin: string): string;
89
83
  export declare function buildPendingDecisionItems(inbox: InboxEnvelope, webOrigin: string, self: DecisionSelfIds): PendingDecisionItem[];
90
84
  /** shape pending payment approvals for the session payload/card. */
91
85
  export declare function buildPaymentApprovalItems(approvals: Array<Record<string, unknown>>, webOrigin: string): PaymentApprovalItem[];
@@ -1,3 +1,5 @@
1
+ import { agreementAppUrl, agreementsListAppUrl, connectionsSettingsAppUrl, walletAppUrl } from '@ziggs-ai/api-client';
2
+ export { resolveWebAppOrigin, agreementAppUrl, agreementsListAppUrl, connectionsSettingsAppUrl, walletAppUrl } from '@ziggs-ai/api-client';
1
3
  import { partySideIds, resolvePendingApprovalPartyId, } from '@ziggs-ai/api-client';
2
4
  /** Chat/tool cues when {@link PendingDecisionItem.respondableBy} is `agent`. */
3
5
  export function decisionRespondCues(item) {
@@ -21,23 +23,6 @@ const ACTIVE_TASK_LIMIT = 20;
21
23
  * second tool to go call.
22
24
  */
23
25
  export const SESSION_CARD_POINTER = 'Call ziggs_inbox without waitSeconds and paste its sessionChatCard for the human.';
24
- export function resolveWebAppOrigin(webUrl) {
25
- return (webUrl?.trim() || 'https://ziggsai.com').replace(/\/$/, '');
26
- }
27
- export function agreementAppUrl(origin, agreementId) {
28
- return `${origin}/app/agreements/${encodeURIComponent(agreementId)}`;
29
- }
30
- export function agreementsListAppUrl(origin) {
31
- return `${origin}/app/agreements`;
32
- }
33
- /** Where the human connects MCP servers and grants tools. */
34
- export function connectionsSettingsAppUrl(origin) {
35
- return `${origin}/app/access`;
36
- }
37
- /** Where the human decides paused transfers. */
38
- export function walletAppUrl(origin) {
39
- return `${origin}/app/settings/organization/billing`;
40
- }
41
26
  function truncateText(text, max = TITLE_MAX) {
42
27
  const oneLine = text.replace(/\s+/g, ' ').trim();
43
28
  if (oneLine.length <= max)
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * The protocol (inbox → read → act → ack, the reporting rule, humanAttention
5
5
  * handling, the untrusted-input hard rule) is stated once here and rendered
6
- * into every surface that carries it: the `ziggs_inbox` tool description, the
7
- * server `instructions`, SKILL.md, the skill references and `.cursorrules`.
8
- * Hand-copied across those, it drifted.
6
+ * into the connect surfaces: server `instructions`, SKILL.md, the skill
7
+ * references and `.cursorrules`. Tool descriptions do not repeat these
8
+ * paragraphs. Hand-copied across those, it drifted.
9
9
  *
10
10
  * Static surfaces are generated from these fragments; runtime surfaces import
11
11
  * them directly, so neither can drift. A drift check fails the build when a
@@ -64,8 +64,7 @@ export declare const PROTOCOL: {
64
64
  };
65
65
  /**
66
66
  * Ordered protocol rules for the prose surfaces (server instructions, SKILL,
67
- * .cursorrules). The `ziggs_inbox` description composes its own narrower string
68
- * from the same fragments — see tools.ts.
67
+ * .cursorrules). Tool descriptions stay on their own fields.
69
68
  */
70
69
  export declare const PROTOCOL_RULES: readonly string[];
71
70
  /** HTML-comment markers delimiting the generated region in a markdown file. */
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * The protocol (inbox → read → act → ack, the reporting rule, humanAttention
5
5
  * handling, the untrusted-input hard rule) is stated once here and rendered
6
- * into every surface that carries it: the `ziggs_inbox` tool description, the
7
- * server `instructions`, SKILL.md, the skill references and `.cursorrules`.
8
- * Hand-copied across those, it drifted.
6
+ * into the connect surfaces: server `instructions`, SKILL.md, the skill
7
+ * references and `.cursorrules`. Tool descriptions do not repeat these
8
+ * paragraphs. Hand-copied across those, it drifted.
9
9
  *
10
10
  * Static surfaces are generated from these fragments; runtime surfaces import
11
11
  * them directly, so neither can drift. A drift check fails the build when a
@@ -64,8 +64,7 @@ export const PROTOCOL = {
64
64
  };
65
65
  /**
66
66
  * Ordered protocol rules for the prose surfaces (server instructions, SKILL,
67
- * .cursorrules). The `ziggs_inbox` description composes its own narrower string
68
- * from the same fragments — see tools.ts.
67
+ * .cursorrules). Tool descriptions stay on their own fields.
69
68
  */
70
69
  export const PROTOCOL_RULES = [
71
70
  PROTOCOL.surface,
package/dist/tools.js CHANGED
@@ -1,41 +1,33 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
- import { getAgreement, getMyAgreements, listMyChats, delegateAgreement, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateAgentId, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, listTasksCapability, marketplaceViewCapability, } from '@ziggs-ai/api-client';
3
+ import { getAgreement, getMyAgreements, listMyChats, delegateAgreement, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateSession, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, listTasksCapability, marketplaceViewCapability, parseListFields, pickListedRows, } from '@ziggs-ai/api-client';
4
4
  import { decodeOperatorKeyClaims } from './operatorKey.js';
5
5
  import { registerTrustTools } from './trustTools.js';
6
6
  import { registerPaymentTools } from './paymentTools.js';
7
7
  import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
8
8
  import { agreementAppUrl, filterTasksForDelegate, formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
9
- import { PROTOCOL } from './protocol/delegateProtocol.js';
10
9
  import { resolveMcpConnectionTarget, withMcpGatewayClient, } from './mcpConnectionTools.js';
11
10
  import { readOnly, write, destructive } from './toolAnnotations.js';
12
11
  import { registerStrictTool } from './strictParams.js';
13
12
  import { toolError } from './toolError.js';
14
13
  import { registerCapability, registerCapabilities, textResult, } from './capabilityAdapter.js';
15
- // the protocol sentences (loop / ack / humanAttention) are sourced
16
- // from the shared const so this description can't drift from SKILL / server
17
- // instructions / .cursorrules.
14
+ // Shared protocol paragraphs live on connect `instructions` only.
15
+ // This description is the tool's own fields and next calls not PROTOCOL.*.
18
16
  const ZIGGS_INBOX_DESCRIPTION = "Where you stand, in one call. What's addressed to you since your last ack — references only, never content: `deliveries` (newest first) with a per-chat `chats` fold, plus assigned open tasks and agreement proposals awaiting your response. " +
19
17
  'Open the conversations behind the references with ziggs_context_read (type=messages, via=chat:<chatId>). ' +
20
- `${PROTOCOL.humanAttention} ${PROTOCOL.pendingDecisions} ` +
21
18
  'A cold call (no waitSeconds) is the session-start read: it also carries `session` (who you are acting as, in which org, against which backend), the structured `decisions` and `activeWork` awaiting an answer, and the `sessionChatCard` to paste for the human. Do NOT call ziggs_agreement_respond until they explicitly approve or reject. ' +
22
19
  'A long-poll call (waitSeconds) is the working loop and returns news only — the session block is a session-start cost, not a per-poll one. ' +
23
20
  'A `readPlan` array gives the exact next calls (tool + pre-filled args) for the news in this response — run them verbatim to read each chat and ack; when the plan overflows or deliveries are capped, `readPlanTruncated` counts dropped reads and the ack step is omitted so a partial plan cannot bury other mail. ' +
24
- 'readPlan reads come pre-pinned with the covering contextGrantId when you hold one, so no separate ziggs_grant_list call is needed. ' +
25
- `${PROTOCOL.loop} ${PROTOCOL.ack}`;
26
- // steer the reporting slot at the point of choice. Reporting rule is sourced
27
- // from the shared const so it can't drift.
28
- //
21
+ 'readPlan reads come pre-pinned with the covering contextGrantId when you hold one, so no separate ziggs_grant_list call is needed.';
29
22
  // The requirement is one grant, and saying so is the whole point: this used to
30
23
  // promise a cross-org reach test on every send (propose a link, or fail with
31
24
  // AGENT_NOT_PUBLISHED), which no longer exists. Reach is decided once, when
32
25
  // somebody is admitted to the room; a send only asks whether the sender holds
33
- // write on it.
26
+ // write on it. Where finished work goes is PROTOCOL.reporting on connect.
34
27
  const ZIGGS_SEND_MESSAGE_DESCRIPTION = 'Send a chat message as the acting agent. Pass chatId of a room you write in, or to naming a person you already have a conversation with (that existing pair room — this does not open contact). First contact is still ziggs_chat_open then send. ' +
35
28
  'One requirement: an active write grant on this room. ' +
36
29
  'Taking part IS the permission, so there is no separate reach check on the send and no auto-add of a receiver who is not already in the room. ' +
37
- 'A refusal means you hold no write grant here: rooms are opened with you (ziggs_chat_open) and holders are admitted under an instrument, and the refusal names which one is missing and how to create it. ' +
38
- PROTOCOL.reporting;
30
+ 'A refusal means you hold no write grant here: rooms are opened with you (ziggs_chat_open) and holders are admitted under an instrument, and the refusal names which one is missing and how to create it.';
39
31
  // the strict artifact write (fail loudly, return the artifactId)
40
32
  // moved into ArtifactsClient.writeStrict, shared with the SDK's artifact_record.
41
33
  // the leak-guard, the connections proxy/request calls, the
@@ -130,7 +122,7 @@ function buildSessionActions(inbox, reads, creds, cfg) {
130
122
  */
131
123
  async function loadSessionBinding(creds, cfg) {
132
124
  const claims = decodeOperatorKeyClaims(creds.operatorKey);
133
- const isDelegate = isMcpOAuthDelegateAgentId(creds.agentId);
125
+ const isDelegate = isMcpOAuthDelegateSession(creds);
134
126
  let actingOrgId = null;
135
127
  let actingOrgName = null;
136
128
  let actingOrgKind = null;
@@ -373,13 +365,21 @@ export function registerZiggsTools(server, creds, cfg) {
373
365
  .string()
374
366
  .optional()
375
367
  .describe('Optional filter: pending, approved, rejected, …'),
376
- }, readOnly('List your agreements'), async ({ scope, proposalStatus }) => {
368
+ fields: z
369
+ .array(z.string())
370
+ .optional()
371
+ .describe('Return only these keys on each row. Omit for the full row. Keep agreementId if you will claim or get next.'),
372
+ }, readOnly('List your agreements'), async ({ scope, proposalStatus, fields }) => {
377
373
  try {
378
374
  const agreements = await getMyAgreements({
379
375
  ...(proposalStatus ? { proposalStatus } : {}),
380
376
  partyOnly: scope !== 'reachable',
381
377
  }, creds);
382
- return textResult({ count: agreements.length, scope: scope ?? 'mine', agreements });
378
+ return textResult({
379
+ count: agreements.length,
380
+ scope: scope ?? 'mine',
381
+ agreements: pickListedRows(agreements, parseListFields(fields)),
382
+ });
383
383
  }
384
384
  catch (e) {
385
385
  return toolError(e);
@@ -696,9 +696,8 @@ export function registerZiggsTools(server, creds, cfg) {
696
696
  : result;
697
697
  },
698
698
  });
699
- // descriptions.mcp is canonical — do not override. The reporting paragraph
700
- // is PROTOCOL.reporting copied into the capability (api-client cannot import
701
- // ziggs-mcp); record-artifact-teaching.test.ts gates the live tool against both.
699
+ // descriptions.mcp is canonical — do not override. Shared protocol
700
+ // paragraphs stay on connect instructions, not this description.
702
701
  registerCapability(server, recordArtifactCapability, creds);
703
702
  // find what you recorded free-standing, and hand one artifact to one
704
703
  // agent. Descriptions come from the shared capability (no PROTOCOL override
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "MCP server for Claude Code, Cursor, and other MCP hosts — act as your Ziggs delegate agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@modelcontextprotocol/sdk": "^1.29.0",
42
- "@ziggs-ai/api-client": "0.15.1",
42
+ "@ziggs-ai/api-client": "0.16.0",
43
43
  "dotenv": "^16.6.1",
44
44
  "zod": "^3.24.2",
45
45
  "zod-to-json-schema": "^3.25.1"