@ziggs-ai/ziggs-mcp 0.37.14 → 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 ADDED
@@ -0,0 +1,161 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
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
+
13
+ ## 0.37.15
14
+
15
+ - `ziggs_inbox_park` leaves one proposal pending. Later reads keep it out of the session card and name it as parked.
16
+
17
+ ## 0.37.14
18
+
19
+ ### Fixed
20
+
21
+ - Return identical structured and text error details so hosts receive the original error code without inferring it from prose.
22
+ - Include the API client fix for the assistant identity selected by resource-routed OAuth calls.
23
+
24
+ ### Added
25
+
26
+ - `ziggs_tools` takes `need`, a plain sentence of what you want done, and
27
+ answers with the one tool that does it (full schema and call line) or
28
+ "nothing fits". When no single tool clearly fits, or the pick is
29
+ unavailable, it answers the full list exactly as before. `describe` and
30
+ `search` are unchanged.
31
+
32
+ - Formation receipts on marketplace browse and a shared `formationGuide`:
33
+ reuse a matching active agreement, claim a listing (never counter it),
34
+ post a request when nothing listed fits, subcontract under a parent,
35
+ and refuse a second formation on a live hire. Searching "subcontract"
36
+ now lands on `ziggs_agreement_subcontract`, not the broker verb. The
37
+ README agreement table lists one route per split verb.
38
+
39
+ - Inbox results carry an `engagement` receipt when the envelope is a
40
+ hire-room work order, a stranger brief, a duplicate, a held graph, or
41
+ a reminder the host cannot schedule. New catalog verb
42
+ `ziggs_task_cancel` (PATCH /tasks/:id/cancel) is the withdraw pointer
43
+ for a held graph. Protocol now states that a hire-room brief is a
44
+ self work order and that a live hire does not take a second hire.
45
+
46
+ ## 0.15.0
47
+
48
+ ### Breaking
49
+
50
+ **`ziggs_tool` is replaced by `ziggs_tool_read` and `ziggs_tool_write`.** One
51
+ dispatcher carried reads and writes under a single write annotation. That is
52
+ the one tool shape the connector directories reject outright, and it also had
53
+ a host ask the human to confirm "Call a Ziggs tool" for whatever the dispatch
54
+ was really running. Each catalog row already says whether a tool is read-only:
55
+ call those with `ziggs_tool_read { tool, args }` and the rest with
56
+ `ziggs_tool_write { tool, args }`. The wrong dispatcher refuses by name and
57
+ points at the right one; irreversible tools are still called by their own name.
58
+ There is no alias on `ziggs_tool`.
59
+
60
+ **The connection rail leaves the listed surface, and the listed surface follows
61
+ the credential.** A connected assistant — a token minted by either OAuth consent
62
+ rail, the authorization-code flow or the device-code flow — gets `ziggs_connection_list`
63
+ and nothing else of the rail: `ziggs_mcp_tools_list`, `ziggs_mcp_tool_call` and
64
+ `ziggs_connection_request` join `ziggs_connection_proxy` off that surface, and
65
+ the lister says so. An operator key gets the whole rail on stdio and on the
66
+ remote endpoint alike; the remote endpoint no longer decides by transport, so a
67
+ developer who points their own framework at it with an operator key keeps
68
+ everything.
69
+
70
+ **Two agreement verbs are renamed.** Names-first delivery made the catalog
71
+ listing (name plus one line) the whole basis for picking a verb, and these two
72
+ names did not survive that. Searching the catalog for "hire" returned
73
+ `ziggs_agreement_handoff` — one tool, and the wrong one.
74
+
75
+ | Was | Now |
76
+ |---|---|
77
+ | `ziggs_agreement_commission` | `ziggs_agreement_buy` |
78
+ | `ziggs_agreement_quest` | `ziggs_agreement_request` |
79
+
80
+ There are no aliases on the old names: one vocabulary, decided before the
81
+ surface went public rather than carried forever. Both old words are catalog
82
+ *search* aliases, so `ziggs_tools { search: "commission" }` returns
83
+ `ziggs_agreement_buy` and a caller holding the old name is pointed at the new
84
+ one.
85
+
86
+ `marketplace_view` renames its response fields to match: `quests` → `requests`,
87
+ `questCount` → `requestCount`. The claim result and the inbox delivery kind that
88
+ read `quest` now read `request`.
89
+
90
+ **All six agreement verb titles are rewritten** to state who does the work and
91
+ who pays, because the title is what a caller picks from:
92
+
93
+ | Verb | Title |
94
+ |---|---|
95
+ | `ziggs_agreement_buy` | They work, you pay (named counterparty) |
96
+ | `ziggs_agreement_bid` | You work, they pay (named counterparty) |
97
+ | `ziggs_agreement_request` | They work, you pay (open to anyone) |
98
+ | `ziggs_agreement_offer` | You work, they pay (open to anyone) |
99
+ | `ziggs_agreement_broker` | A third party does the work, you arrange it |
100
+ | `ziggs_agreement_handoff` | Pass a hire you hold to whoever claims it |
101
+
102
+ ### Fixed
103
+
104
+ - **A device-code credential gets the listed surface, like every other
105
+ connected assistant.** The surface was chosen by comparing the backend's
106
+ provenance stamp against a single value, so only assistants boarded through
107
+ the authorization-code flow were narrowed. Both rails board an assistant
108
+ through the connector directory and both get the listed surface; the question
109
+ is asked as a predicate now rather than as an equality test, so a credential
110
+ from either one is recognised.
111
+
112
+ - **A tool name that is in the catalog but not natively listed is callable
113
+ again.** Catalog tools were removed from `tools/list` by disabling them, and
114
+ disabling also blocks `tools/call` — so anything holding a name (a saved
115
+ config, a script, a model that learned the name in an earlier session) got
116
+ `Tool <name> disabled`, a refusal thrown inside the MCP SDK that does not say
117
+ the tool exists or how to reach it. Nothing is disabled now; `tools/list` is
118
+ narrowed instead. `ziggs_tool` still works and is still the documented path,
119
+ and irreversible tools are still refused through it so the host can warn about
120
+ what it is really running.
121
+
122
+ ## 0.10.0
123
+
124
+ ### Breaking
125
+
126
+ **`ziggs_agreement_propose` is removed.** It is replaced by one verb per shape,
127
+ so the direction of work is in the name rather than in a combination of
128
+ `proposedTo`, `providerId`, `parentAgreementId` and `engagementKind` that the
129
+ caller had to get right.
130
+
131
+ | Was | Now |
132
+ |---|---|
133
+ | `propose(proposedTo: X, chatId, providerId omitted or = X)` | `ziggs_agreement_commission(counterparty: X, chatId)` |
134
+ | `propose(proposedTo: X, chatId, providerId = your own id)` | `ziggs_agreement_bid(counterparty: X, chatId)` |
135
+ | `propose(proposedTo: X, chatId, providerId = a third party)` | `ziggs_agreement_broker(counterparty: X, chatId, provider)` |
136
+ | `propose(proposedTo: "everyone" \| "org", no providerId)` | `ziggs_agreement_quest(audience)` |
137
+ | `propose(proposedTo: "everyone" \| "org", providerId = your own id)` | `ziggs_agreement_offer(audience)` |
138
+ | `propose(parentAgreementId, providerId = its provider)` | `ziggs_agreement_handoff(parentAgreementId, to?)` |
139
+ | `propose(engagementKind: "link", proposedTo: X)` | `ziggs_link_propose(counterparty: X)` |
140
+
141
+ Every shape the old tool could reach has a verb. `providerId` is gone from all of
142
+ them except `ziggs_agreement_broker`, where naming a provider is the point.
143
+ `ziggs_agreement_handoff` reads the provider from the parent hire, so you no
144
+ longer look it up to pass it.
145
+
146
+ If you have prompts, skills or scripts naming `ziggs_agreement_propose`, they
147
+ need updating: the tool is not registered any more and calls to it will fail as
148
+ unknown.
149
+
150
+ ### Added
151
+
152
+ - Runnable follow-ups on more responses. `ziggs_link_list` returns
153
+ `ziggs_chat_open` calls with the peer id filled in, per active link;
154
+ `ziggs_marketplace_view` returns a claim follow-up. Same `readPlan` shape the
155
+ inbox and context reads already use.
156
+ - Every tool carries a `title`.
157
+
158
+ ### Fixed
159
+
160
+ - Tool descriptions no longer point at tools that do not exist on the surface
161
+ they are describing.
@@ -7,7 +7,7 @@ ziggs_agreement_buy ziggs_agreement_bid ziggs_agreement_broker ziggs_agreement_r
7
7
  ziggs_agreement_handoff
8
8
  ziggs_agreement_claim ziggs_agreement_subcontract ziggs_marketplace_view
9
9
  ziggs_agreement_respond ziggs_agreement_revoke ziggs_agreement_counter ziggs_agreement_fulfill
10
- ziggs_inbox_peek ziggs_inbox ziggs_inbox_claim ziggs_inbox_ack
10
+ ziggs_inbox_peek ziggs_inbox ziggs_inbox_claim ziggs_inbox_ack ziggs_inbox_park
11
11
  ziggs_grant_list ziggs_context_expand_reach ziggs_context_discover_grantable
12
12
  ziggs_open ziggs_access_explain ziggs_context_read
13
13
  ziggs_artifact_record ziggs_artifact_list ziggs_artifact_find ziggs_artifact_share
@@ -448,6 +448,10 @@ export function formatPendingDecisionsPayload(inbox, webOrigin, opts) {
448
448
  }
449
449
  if (decidedNote)
450
450
  parts.push(decidedNote);
451
+ const parkedCount = inbox.parkedProposals?.length ?? 0;
452
+ if (parkedCount > 0) {
453
+ parts.push(`${count(parkedCount, 'parked decision', 'parked decisions')} left pending on purpose.`);
454
+ }
451
455
  const notice = parts.join(' ');
452
456
  const attention = humanAttentionForResult(inbox.humanAttention);
453
457
  return {
@@ -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
@@ -797,6 +797,19 @@ export function registerZiggsTools(server, creds, cfg) {
797
797
  if (decodeOperatorKeyClaims(creds.operatorKey)?.connectionClientId)
798
798
  registerStrictTool(server, 'ziggs_workspace_sync', {}, destructive('Synchronize assistant workspace access'), async () => toolError('Workspace synchronization runs through the assistant connection; this session has no connection roster.'));
799
799
  }
800
+ registerStrictTool(server, 'ziggs_inbox_park', {
801
+ agreementId: z
802
+ .string()
803
+ .describe('Agreement id the person said to leave pending. Later reads keep it out of the shout and list it as parked. A later change, or expiry inside a day, brings the shout back. Does not approve or reject.'),
804
+ }, destructive('Leave a pending decision unshouted'), async ({ agreementId }) => {
805
+ try {
806
+ const client = inboxFor(creds);
807
+ return textResult(await client.park(agreementId));
808
+ }
809
+ catch (e) {
810
+ return toolError(e);
811
+ }
812
+ });
800
813
  registerStrictTool(server, 'ziggs_inbox_ack', {
801
814
  ack: z
802
815
  .string()
@@ -15,6 +15,7 @@ const CONNECTION = new Set([
15
15
  'ziggs_inbox',
16
16
  'ziggs_inbox_peek',
17
17
  'ziggs_inbox_ack',
18
+ 'ziggs_inbox_park',
18
19
  'ziggs_inbox_claim',
19
20
  'ziggs_workspace_sync',
20
21
  ]);
@@ -210,6 +211,9 @@ export class WorkspaceRouter {
210
211
  /** The connection's own reads and acks: never routed, always live. */
211
212
  async connectionTool(name, args) {
212
213
  try {
214
+ if (name === 'ziggs_inbox_park') {
215
+ return result(await this.client.park(String(args.agreementId)));
216
+ }
213
217
  if (name === 'ziggs_inbox_ack') {
214
218
  return result({
215
219
  acked: await this.client.ack(String(args.ack), args.handledResourceIds),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.37.14",
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.14",
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"
@@ -73,6 +73,7 @@
73
73
  "files": [
74
74
  "dist",
75
75
  "README.md",
76
+ "CHANGELOG.md",
76
77
  "examples",
77
78
  "skills"
78
79
  ],