@ziggs-ai/ziggs-mcp 0.37.15 → 0.37.16

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,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.37.16
6
+
7
+ - Clarify the shared hiring instructions: a mandate governs the work; agent-initiated hires need a covering grant or approval on the existing agreement.
8
+
9
+ - Expose existing-hire admission recovery through `ziggs_chat_open` and retain cached resource reference compatibility.
10
+ - Use the shared API-client refusal formatter; surface actionable requests and approvers without telling assistants they must use a browser to hire.
11
+ - Verify read-tool compatibility and workspace hire selection through the registered MCP transport, and preserve server decisions without inferring permission from error prose.
12
+
5
13
  ## 0.37.15
6
14
 
7
15
  - `ziggs_inbox_park` leaves one proposal pending. Later reads keep it out of the session card and name it as parked.
@@ -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/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.16",
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.17",
55
55
  "dotenv": "^16.6.1",
56
56
  "zod": "^3.24.2",
57
57
  "zod-to-json-schema": "^3.25.1"