@ziggs-ai/ziggs-mcp 0.37.15 → 0.37.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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.37.18
6
+
7
+ - Inspect notification health through a read-only assistant-connection status tool: explicit workspace, expiry, callback acceptance, retry timing and categorized failures, without callback URLs or signing secrets. Receipt is not proof of a host response. Preserve backend errors when observing through a connection.
8
+
9
+ - Read small inbox deltas with an observation cursor that renews on use, independent of work acknowledgements. Preserve the recovery cursor when the stream limit is reached. Reuse it to wait for new arrivals despite older unhandled mail; available through both HTTP connection modes and MCP.
10
+
11
+ ## 0.37.16
12
+
13
+ - Clarify the shared hiring instructions: a mandate governs the work; agent-initiated hires need a covering grant or approval on the existing agreement.
14
+
15
+ - Expose existing-hire admission recovery through `ziggs_chat_open` and retain cached resource reference compatibility.
16
+ - Use the shared API-client refusal formatter; surface actionable requests and approvers without telling assistants they must use a browser to hire.
17
+ - Verify read-tool compatibility and workspace hire selection through the registered MCP transport, and preserve server decisions without inferring permission from error prose.
18
+
5
19
  ## 0.37.15
6
20
 
7
21
  - `ziggs_inbox_park` leaves one proposal pending. Later reads keep it out of the session card and name it as parked.
@@ -2,12 +2,13 @@
2
2
  const directories = new WeakSet();
3
3
  export const DIRECTORY_TOOLS = new Set(`
4
4
  ziggs_auth_status ziggs_org_list ziggs_workspace_sync ziggs_context_snapshot
5
+ ziggs_notification_status
5
6
  ziggs_agreement_list ziggs_agreement_get ziggs_chat_list ziggs_chat_open ziggs_chat_send
6
7
  ziggs_agreement_buy ziggs_agreement_bid ziggs_agreement_broker ziggs_agreement_request
7
8
  ziggs_agreement_handoff
8
9
  ziggs_agreement_claim ziggs_agreement_subcontract ziggs_marketplace_view
9
10
  ziggs_agreement_respond ziggs_agreement_revoke ziggs_agreement_counter ziggs_agreement_fulfill
10
- ziggs_inbox_peek ziggs_inbox ziggs_inbox_claim ziggs_inbox_ack ziggs_inbox_park
11
+ ziggs_inbox_changes ziggs_inbox_peek ziggs_inbox ziggs_inbox_claim ziggs_inbox_ack ziggs_inbox_park
11
12
  ziggs_grant_list ziggs_context_expand_reach ziggs_context_discover_grantable
12
13
  ziggs_open ziggs_access_explain ziggs_context_read
13
14
  ziggs_artifact_record ziggs_artifact_list ziggs_artifact_find ziggs_artifact_share
@@ -25,7 +26,7 @@ ziggs_introduction_list ziggs_introduction_revoke ziggs_context_revoke_grant zig
25
26
  * in one workspace, so tools.ts registers it only when the key carries a
26
27
  * connection. Tests subtract this set for such a key.
27
28
  */
28
- export const CONNECTION_ONLY_DIRECTORY_TOOLS = new Set(['ziggs_workspace_sync']);
29
+ export const CONNECTION_ONLY_DIRECTORY_TOOLS = new Set(['ziggs_workspace_sync', 'ziggs_notification_status']);
29
30
  /** Public discovery and actions accepting new counterparties or audiences. */
30
31
  const OPEN_WORLD = new Set(`
31
32
  ziggs_agent_search ziggs_agent_get ziggs_marketplace_view
@@ -1,42 +1,6 @@
1
- /**
2
- * one error surface for every MCP tool.
3
- *
4
- * classify from the thrown {@link ApiError} fields (`status`,
5
- * `code`, `body` / `message`). No client-prefix strip, no HTTP-status regex,
6
- * no re-parsing a JSON body that was concatenated into the message string.
7
- */
8
- export interface ToolErrorShape {
9
- code: string;
10
- message: string;
11
- hint?: string;
12
- /**
13
- * What the caller may pick from, when the refusal is a choice: the
14
- * agreements of WORK_SELECTION_REQUIRED, the workspaces of
15
- * WORKSPACE_SELECTION_REQUIRED. Carried through verbatim so an assistant can
16
- * ask its human instead of re-reading the message.
17
- */
18
- choices?: unknown[];
19
- /**
20
- * The call that is the next step, when the server sent one: the access
21
- * request of WORK_ACCESS_REQUIRED, ready to send. Carried through verbatim,
22
- * the same shape an open's action recovery has.
23
- */
24
- recovery?: Record<string, unknown>;
25
- /** hidden | unreadable, the same outcome open and access_explain give. */
26
- outcome?: string;
27
- /** The resource the outcome is about. */
28
- ref?: {
29
- kind: string;
30
- id: string;
31
- };
32
- /** For an unreadable resource: the request to make, as open lists it. */
33
- actions?: unknown[];
34
- }
35
- /**
36
- * Classify a thrown client error (or a plain string this layer raised) into a
37
- * stable MCP shape.
38
- */
39
- export declare function classifyToolError(input: unknown): ToolErrorShape;
1
+ import { type OperationError } from '@ziggs-ai/api-client';
2
+ export type ToolErrorShape = OperationError;
3
+ export declare const classifyToolError: (input: unknown) => OperationError;
40
4
  /**
41
5
  * MCP tool error result: machine-readable code + cleaned message (+ hint).
42
6
  *
@@ -56,7 +20,7 @@ export declare function refusalError(refusal: {
56
20
  actions?: unknown[];
57
21
  }): {
58
22
  structuredContent: {
59
- error: ToolErrorShape;
23
+ error: OperationError;
60
24
  };
61
25
  content: {
62
26
  type: "text";
@@ -66,7 +30,7 @@ export declare function refusalError(refusal: {
66
30
  };
67
31
  export declare function toolError(input: unknown): {
68
32
  structuredContent: {
69
- error: ToolErrorShape;
33
+ error: OperationError;
70
34
  };
71
35
  content: {
72
36
  type: "text";
package/dist/toolError.js CHANGED
@@ -1,188 +1,5 @@
1
- /**
2
- * one error surface for every MCP tool.
3
- *
4
- * classify from the thrown {@link ApiError} fields (`status`,
5
- * `code`, `body` / `message`). No client-prefix strip, no HTTP-status regex,
6
- * no re-parsing a JSON body that was concatenated into the message string.
7
- */
8
- /**
9
- * The shared refusal vocabulary every read tool answers in (hidden,
10
- * unreadable, invalid input), so an assistant can branch on the code instead
11
- * of parsing each tool's sentence.
12
- */
13
- const SHARED_REFUSALS = new Set(['RESOURCE_HIDDEN', 'RESOURCE_UNREADABLE', 'INVALID_INPUT']);
14
- const SCOPE_DENIED_HINT = 'For missing access to a returned artifact, chat, agreement, or task, use ' +
15
- 'ziggs_access_request with that original resource id and your purpose. It creates ' +
16
- 'a pending access approval agreement; it grants no access. After approval, ' +
17
- 'ziggs_open the same resource using the same agent identity. For earlier chat ' +
18
- 'messages request temporal=from-start; replying also requires permission=reply. ' +
19
- 'Do not issue yourself a grant. A new room does not show this room\'s history.';
20
- // a human-authority denial is not "try again with more scope": no
21
- // retry by this caller can ever pass it, because the guard refuses on being an
22
- // agent at all, before it looks at what was asked. Without a next action the
23
- // only strategy left is repetition, which is what agents did.
24
- const HUMAN_AUTHORITY_HINT = 'This action is the account owner\'s to take, not yours — retrying will not ' +
25
- 'change that. Your paths: ziggs_context_delegate to pass on a narrower slice ' +
26
- 'of a grant you ALREADY hold, ziggs_artifact_share for an artifact you ' +
27
- 'authored, ziggs_access_request to ask for your own missing resource access, or tell your human to do it in the Ziggs app.';
28
- /**
29
- * What covers a conversation, refused. These arrive as 429 or 403,
30
- * and a 429 read as "rate limited" tells an agent to wait and retry, which can
31
- * never help: the writer has to hire the agent, or their person has to set up
32
- * the agreement. The backend's own code and sentence are the answer.
33
- */
34
- const COVERAGE_REFUSALS = new Set([
35
- 'CONVERSATION_ALLOWANCE_EXHAUSTED',
36
- 'CONTACT_POLICY_REFUSED',
37
- 'LISTING_PAUSED',
38
- 'CONVERSATION_COVERAGE_REQUIRED',
39
- 'CONVERSATION_COVERAGE_ENDED',
40
- 'CONVERSATION_NOT_FUNDED',
41
- 'CONVERSATION_SPONSORSHIP_REQUIRED',
42
- 'TASK_ONLY_AGENT',
43
- ]);
44
- const COVERAGE_HINT = 'Not a rate limit: retrying will not change it. Tell the person you work ' +
45
- 'for what the message says; hiring an agent, or agreeing the terms that ' +
46
- 'cover this conversation, is their decision in the Ziggs app.';
47
- function codeForStatus(status) {
48
- if (status === 401)
49
- return 'NOT_AUTHENTICATED';
50
- if (status === 403)
51
- return 'NOT_AUTHORIZED';
52
- if (status === 404)
53
- return 'NOT_FOUND';
54
- if (status === 409)
55
- return 'CONFLICT';
56
- if (status === 429)
57
- return 'RATE_LIMITED';
58
- if (status >= 500)
59
- return 'UPSTREAM_ERROR';
60
- if (status >= 400)
61
- return 'BAD_REQUEST';
62
- return 'TOOL_ERROR';
63
- }
64
- /**
65
- * An `ApiError` from api-client, recognised by shape rather than `instanceof`:
66
- * a bundled or duplicated copy of the class would fail an identity check while
67
- * carrying exactly the fields we need.
68
- */
69
- function thrownFields(err) {
70
- if (typeof err === 'string') {
71
- return { message: err, status: null, code: null, choices: null };
72
- }
73
- if (typeof err !== 'object' || err === null) {
74
- return { message: String(err), status: null, code: null, choices: null };
75
- }
76
- const o = err;
77
- const status = typeof o.status === 'number' && o.status >= 100 && o.status < 600
78
- ? o.status
79
- : null;
80
- const code = typeof o.code === 'string' && o.code ? o.code : null;
81
- const message = typeof o.message === 'string' && o.message ? o.message : String(err);
82
- return { message, status, code, choices: choicesIn(o.body) };
83
- }
84
- /** The `recovery` call of a refusal body, when the server sent one. */
85
- function recoveryIn(body) {
86
- if (typeof body !== 'string' || !body)
87
- return null;
88
- try {
89
- const parsed = JSON.parse(body);
90
- const recovery = parsed?.recovery;
91
- if (!recovery || typeof recovery !== 'object' || Array.isArray(recovery))
92
- return null;
93
- // The server names its tools by verb; here they are the MCP tools, the
94
- // same renaming an open's actions get.
95
- const call = recovery;
96
- return typeof call.tool === 'string' && !call.tool.startsWith('ziggs_')
97
- ? { ...call, tool: `ziggs_${call.tool}` }
98
- : call;
99
- }
100
- catch {
101
- return null;
102
- }
103
- }
104
- /** The `choices` list of a refusal body, when the server sent one. */
105
- function choicesIn(body) {
106
- if (typeof body !== 'string' || !body)
107
- return null;
108
- try {
109
- const parsed = JSON.parse(body);
110
- return Array.isArray(parsed?.choices) && parsed.choices.length
111
- ? parsed.choices
112
- : null;
113
- }
114
- catch {
115
- return null;
116
- }
117
- }
118
- /**
119
- * Classify a thrown client error (or a plain string this layer raised) into a
120
- * stable MCP shape.
121
- */
122
- export function classifyToolError(input) {
123
- const shape = classify(input);
124
- const { choices } = thrownFields(input);
125
- const recovery = typeof input === 'object' && input !== null
126
- ? recoveryIn(input.body)
127
- : null;
128
- return {
129
- ...shape,
130
- ...(choices ? { choices } : {}),
131
- ...(recovery ? { recovery } : {}),
132
- };
133
- }
134
- function classify(input) {
135
- const { message, status, code: machineCode } = thrownFields(input);
136
- if (machineCode && SHARED_REFUSALS.has(machineCode)) {
137
- return { code: machineCode, message };
138
- }
139
- if (machineCode === 'AGENT_LACKS_HUMAN_AUTHORITY') {
140
- return {
141
- code: 'AGENT_LACKS_HUMAN_AUTHORITY',
142
- message,
143
- hint: HUMAN_AUTHORITY_HINT,
144
- };
145
- }
146
- // Can read the agreement, cannot work on it: the server's sentence and its
147
- // recovery are the answer, and the generic scope hint would send the agent
148
- // back for the read it already has.
149
- if (machineCode === 'WORK_ACCESS_REQUIRED') {
150
- return { code: machineCode, message };
151
- }
152
- if (machineCode && COVERAGE_REFUSALS.has(machineCode)) {
153
- return { code: machineCode, message, hint: COVERAGE_HINT };
154
- }
155
- if (machineCode === 'NOT_AUTHORIZED_FOR_SCOPE') {
156
- return {
157
- code: 'NOT_AUTHORIZED_FOR_SCOPE',
158
- message,
159
- hint: SCOPE_DENIED_HINT,
160
- };
161
- }
162
- if (status === null) {
163
- return { code: 'TOOL_ERROR', message };
164
- }
165
- const code = codeForStatus(status);
166
- // Backend human-authority already sends AGENT_LACKS_HUMAN_AUTHORITY; keep a
167
- // message fallback for older bodies / tests that omit `code`.
168
- if (code === 'NOT_AUTHORIZED' && /impersonated agent cannot perform/i.test(message)) {
169
- return {
170
- code: 'AGENT_LACKS_HUMAN_AUTHORITY',
171
- message,
172
- hint: HUMAN_AUTHORITY_HINT,
173
- };
174
- }
175
- // Many scope denials still throw a bare ForbiddenException string with no
176
- // machine code — classify those by the word "scope" in the message.
177
- if (code === 'NOT_AUTHORIZED' && /\bscope\b/i.test(message)) {
178
- return {
179
- code: 'NOT_AUTHORIZED_FOR_SCOPE',
180
- message,
181
- hint: SCOPE_DENIED_HINT,
182
- };
183
- }
184
- return { code, message };
185
- }
1
+ import { describeOperationError } from '@ziggs-ai/api-client';
2
+ export const classifyToolError = (input) => describeOperationError(input, 'mcp');
186
3
  /**
187
4
  * MCP tool error result: machine-readable code + cleaned message (+ hint).
188
5
  *
package/dist/tools.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { isDirectorySurface } from './directorySurface.js';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { z } from 'zod';
4
- import { presentResourceActions, getAgreement, getMyAgreements, listMyChats, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, acknowledgeTaskControl, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateSession, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, LISTING_DOOR_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, openCapability, accessExplainCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, agreementSubcontractCapability, listTasksCapability, cancelTaskCapability, marketplaceViewCapability, parseListFields, pickListedRows, projectChatListRows, sessionOrientation, presentSendResult, presentTaskOutcome, explainRefusedRead, RESOURCE_HIDDEN_MESSAGE, SOURCES_DESCRIPTION, } from '@ziggs-ai/api-client';
4
+ import { presentResourceActions, getAgreement, getMyAgreements, listMyChats, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, ContextReadClient, GrantsClient, InboxClient, AssistantConnectionClient, createTask, updateTaskState, acknowledgeTaskControl, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateSession, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, LISTING_DOOR_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, openCapability, accessExplainCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, findArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, agreementSubcontractCapability, listTasksCapability, cancelTaskCapability, marketplaceViewCapability, parseListFields, pickListedRows, projectChatListRows, sessionOrientation, presentSendResult, presentTaskOutcome, explainRefusedRead, RESOURCE_HIDDEN_MESSAGE, SOURCES_DESCRIPTION, } from '@ziggs-ai/api-client';
5
5
  import { decodeOperatorKeyClaims } from './operatorKey.js';
6
6
  import { registerTrustTools } from './trustTools.js';
7
7
  import { registerPaymentTools } from './paymentTools.js';
@@ -613,6 +613,29 @@ export function registerZiggsTools(server, creds, cfg) {
613
613
  return toolError(e);
614
614
  }
615
615
  });
616
+ if (decodeOperatorKeyClaims(creds.operatorKey)?.connectionClientId) {
617
+ registerStrictTool(server, 'ziggs_notification_status', {
618
+ orgId: z.string().min(1).max(200).describe('Workspace ID.'),
619
+ }, readOnly('Notification status'), async ({ orgId }) => {
620
+ try {
621
+ return textResult(await new AssistantConnectionClient(creds).eventStatus(orgId));
622
+ }
623
+ catch (error) {
624
+ return toolError(error);
625
+ }
626
+ });
627
+ }
628
+ registerStrictTool(server, 'ziggs_inbox_changes', {
629
+ cursor: z.string().optional().describe('Observation cursor; reads renew it for 24h. Omit or restart on 410 to scan retained history. Never ack it.'),
630
+ waitSeconds: z.number().min(0).optional().describe('Wait up to 110 seconds for an unobserved delivery; use 55 during an active session.'),
631
+ }, readOnly('Read new inbox arrivals'), async ({ cursor, waitSeconds }) => {
632
+ try {
633
+ return textResult(await inboxFor(creds).getChanges({ cursor, waitSeconds }));
634
+ }
635
+ catch (error) {
636
+ return toolError(error);
637
+ }
638
+ });
616
639
  registerStrictTool(server, 'ziggs_inbox_peek', {
617
640
  waitSeconds: z
618
641
  .number()
@@ -1,4 +1,5 @@
1
1
  import { isDirectorySurface } from './directorySurface.js';
2
+ import { toolError } from './toolError.js';
2
3
  import { AsyncLocalStorage } from 'node:async_hooks';
3
4
  import { AssistantConnectionClient, withAssistantRequest, decodeOperatorKeyClaims, parseRef, } from '@ziggs-ai/api-client';
4
5
  import { formatInboxToolResult } from './inboxToolResult.js';
@@ -14,6 +15,8 @@ const CONNECTION = new Set([
14
15
  'ziggs_org_list',
15
16
  'ziggs_inbox',
16
17
  'ziggs_inbox_peek',
18
+ 'ziggs_inbox_changes',
19
+ 'ziggs_notification_status',
17
20
  'ziggs_inbox_ack',
18
21
  'ziggs_inbox_park',
19
22
  'ziggs_inbox_claim',
@@ -249,6 +252,15 @@ export class WorkspaceRouter {
249
252
  instruction: `deliveries.forYou is your work; deliveries.forYourPerson is for your person to see. ${ASK_YOUR_HUMAN}`,
250
253
  }, { agentIds: self.agentIds ?? [] });
251
254
  }
255
+ if (name === 'ziggs_notification_status') {
256
+ return result(await this.client.eventStatus(args.orgId));
257
+ }
258
+ if (name === 'ziggs_inbox_changes') {
259
+ return result(await this.client.getChanges({
260
+ cursor: args.cursor,
261
+ waitSeconds: args.waitSeconds,
262
+ }));
263
+ }
252
264
  if (name === 'ziggs_inbox_peek') {
253
265
  return result(await this.client.request(`inbox/peek?wait=${Number(args.waitSeconds ?? 0)}`));
254
266
  }
@@ -258,6 +270,8 @@ export class WorkspaceRouter {
258
270
  return result({ count: workspaces.length, orgs: workspaces });
259
271
  }
260
272
  catch (error) {
273
+ if (name === 'ziggs_notification_status' || name === 'ziggs_inbox_changes')
274
+ return toolError(error);
261
275
  return refusal('CONNECTION_REQUEST_FAILED', error instanceof Error ? error.message : String(error));
262
276
  }
263
277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.37.15",
3
+ "version": "0.37.18",
4
4
  "description": "MCP server for Claude Code, Cursor, and other MCP hosts \u2014 act as your Ziggs delegate agent",
5
5
  "homepage": "https://ziggsai.com/docs",
6
6
  "bugs": {
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@modelcontextprotocol/sdk": "^1.29.0",
54
- "@ziggs-ai/api-client": "0.37.15",
54
+ "@ziggs-ai/api-client": "0.37.18",
55
55
  "dotenv": "^16.6.1",
56
56
  "zod": "^3.24.2",
57
57
  "zod-to-json-schema": "^3.25.1"