@ziggs-ai/ziggs-mcp 0.36.1 → 0.36.2

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.
@@ -17,7 +17,10 @@
17
17
  * which `ziggs_auth_status` returns in full;
18
18
  * - a read step's `why` that only restates its tool and args, and the ack
19
19
  * step's long one, whose ids are the step's own args;
20
- * - an `outOfReach` entry's scope and remedy, which its delivery row carries.
20
+ * - an `outOfReach` entry's scope and remedy, which its delivery row carries;
21
+ * - `sessionCardHint` ("the card is not in this response") when the card is.
22
+ * Both doors assemble the session start from the long-poll result plus the
23
+ * card, and the long-poll half's hint said the opposite of what followed.
21
24
  *
22
25
  * Acknowledgement stays exact: every `ackTo` — the envelope's, each row's and
23
26
  * the ack step's — is passed through byte for byte.
@@ -17,7 +17,10 @@
17
17
  * which `ziggs_auth_status` returns in full;
18
18
  * - a read step's `why` that only restates its tool and args, and the ack
19
19
  * step's long one, whose ids are the step's own args;
20
- * - an `outOfReach` entry's scope and remedy, which its delivery row carries.
20
+ * - an `outOfReach` entry's scope and remedy, which its delivery row carries;
21
+ * - `sessionCardHint` ("the card is not in this response") when the card is.
22
+ * Both doors assemble the session start from the long-poll result plus the
23
+ * card, and the long-poll half's hint said the opposite of what followed.
21
24
  *
22
25
  * Acknowledgement stays exact: every `ackTo` — the envelope's, each row's and
23
26
  * the ack step's — is passed through byte for byte.
@@ -92,6 +95,8 @@ export function compactInboxResult(result) {
92
95
  if (isRecord(s))
93
96
  kindOfParty.set(String(s.partyId), s.partyKind);
94
97
  const out = { ...result };
98
+ if (typeof result.sessionChatCard === 'string' && result.sessionChatCard)
99
+ delete out.sessionCardHint;
95
100
  if (Array.isArray(result.deliveries)) {
96
101
  const usual = usualPartyOf(result.deliveries);
97
102
  if (usual !== undefined)
@@ -149,13 +154,16 @@ export function windowNote(deliveries, selfAgentIds, outOfReach) {
149
154
  const mine = rows.filter((d) => selfAgentIds.includes(String(d.assigneeId))).length;
150
155
  const person = rows.filter((d) => !d.assigneeId && d.needsHuman === true).length;
151
156
  const others = rows.filter((d) => d.assigneeId && !selfAgentIds.includes(String(d.assigneeId))).length;
157
+ // Rows nobody is asked to handle (no assignee, not the person's) are context.
158
+ const context = rows.length - mine - person - others;
152
159
  const n = (k, one, many) => `${k} ${k === 1 ? one : many}`;
153
160
  const parts = [n(mine, 'is assigned to you', 'are assigned to you')];
154
161
  if (person)
155
162
  parts.push(`${person} ${person === 1 ? 'needs' : 'need'} your person`);
156
163
  if (others)
157
164
  parts.push(`${others} ${others === 1 ? 'is' : 'are'} for other assistants`);
158
- if (outOfReach)
159
- parts.push(`${outOfReach} out of reach`);
160
- return `Those counts are decisions and tasks, not messages. This window has ${n(rows.length, 'delivery', 'deliveries')}: ${parts.join(', ')}.`;
165
+ if (context)
166
+ parts.push(`${context} ${context === 1 ? 'is' : 'are'} context`);
167
+ const reach = outOfReach ? ` ${outOfReach} of them ${outOfReach === 1 ? 'is' : 'are'} out of reach.` : '';
168
+ return `Those counts are decisions and tasks, not messages. This window has ${n(rows.length, 'delivery', 'deliveries')}: ${parts.join(', ')}.${reach}`;
161
169
  }
@@ -18,4 +18,10 @@
18
18
  * .cursorrules, which a client loads when it chooses to.
19
19
  */
20
20
  export type ConnectDoor = 'directory' | 'compact';
21
+ /**
22
+ * The rules a directory client must see even when it never shows the model
23
+ * the server's instructions (claude.ai does not). Carried on the session-start
24
+ * inbox read, the first call an agent makes, in the same words as above.
25
+ */
26
+ export declare function sessionRules(door: ConnectDoor): string[];
21
27
  export declare function renderConnectText(door: ConnectDoor): string;
@@ -32,6 +32,14 @@ const RULES = [
32
32
  'A send refused with WORK_SELECTION_REQUIRED lists choices: set conversationAgreementId to one of them; never guess.',
33
33
  'Agreement and task writes can reserve or settle points. After an uncertain write, read the record, then retry with the same idempotencyKey.',
34
34
  ];
35
+ /**
36
+ * The rules a directory client must see even when it never shows the model
37
+ * the server's instructions (claude.ai does not). Carried on the session-start
38
+ * inbox read, the first call an agent makes, in the same words as above.
39
+ */
40
+ export function sessionRules(door) {
41
+ return [DATA, INBOX[door]];
42
+ }
35
43
  const CATALOG = 'Only the everyday tools are loaded. ziggs_tools lists the rest; call them through ziggs_tool_read or ziggs_tool_write.';
36
44
  export function renderConnectText(door) {
37
45
  return [
@@ -13,6 +13,8 @@ import type { TitledAnnotations } from './toolAnnotations.js';
13
13
  * against. So every tool schema is built strict, and the rejection names both
14
14
  * the offending key and the params the tool actually accepts.
15
15
  */
16
+ /** The workspace parameter the router adds to a tool that acts in one. */
17
+ export declare const WORKSPACE_PARAM = "Workspace orgId, when more than one fits.";
16
18
  export type StrictShape<S extends ZodRawShape> = ZodObject<S, 'strict', z.ZodTypeAny>;
17
19
  export declare function strictParams<S extends ZodRawShape>(toolName: string, shape: S): StrictShape<S>;
18
20
  /**
@@ -4,6 +4,20 @@ import { workspaceRouter } from './workspaceRouting.js';
4
4
  import { mcpDescription } from '@ziggs-ai/api-client';
5
5
  import { z } from 'zod';
6
6
  import { recordCatalogEntry } from './toolCatalog.js';
7
+ /**
8
+ * registering a tool with a plain `ZodRawShape` lets the MCP SDK
9
+ * wrap it in a default `z.object()`, which *strips* unknown keys instead of
10
+ * rejecting them. A misspelled param (`nweChat` for `newChat`) was therefore
11
+ * dropped and the call ran anyway, reporting success for the opposite of what
12
+ * the caller asked for.
13
+ *
14
+ * A human typing that into a form gets a red field; an agent got silence, and
15
+ * silence reads as confirmation — there was no feedback loop to self-correct
16
+ * against. So every tool schema is built strict, and the rejection names both
17
+ * the offending key and the params the tool actually accepts.
18
+ */
19
+ /** The workspace parameter the router adds to a tool that acts in one. */
20
+ export const WORKSPACE_PARAM = 'Workspace orgId, when more than one fits.';
7
21
  export function strictParams(toolName, shape) {
8
22
  const accepted = Object.keys(shape);
9
23
  return z
@@ -58,8 +72,11 @@ export function registerStrictTool(server, name, shape, annotations, cb) {
58
72
  const router = workspaceRouter(server);
59
73
  const nativeOrgId = !!shape.orgId;
60
74
  if (router) {
75
+ // Said once per tool and kept short: it rides on most of the surface. Which
76
+ // workspace to use is taught where it matters — the refusal for a call that
77
+ // needs one lists the choices and says to ask the person.
61
78
  if (router.acceptsWorkspace(name))
62
- shape = { ...shape, orgId: shape.orgId?.optional() ?? z.string().optional().describe('Workspace named by your human. Required for new actions when more than one workspace fits.') };
79
+ shape = { ...shape, orgId: shape.orgId?.optional() ?? z.string().optional().describe(WORKSPACE_PARAM) };
63
80
  cb = router.wrap(name, annotations.readOnlyHint === true, cb, nativeOrgId);
64
81
  }
65
82
  const routed = cb;
package/dist/tools.js CHANGED
@@ -7,6 +7,7 @@ import { registerTrustTools } from './trustTools.js';
7
7
  import { registerPaymentTools } from './paymentTools.js';
8
8
  import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
9
9
  import { compactTextResult, windowNote } from './inboxCompact.js';
10
+ import { sessionRules } from './protocol/connectText.js';
10
11
  import { agreementAppUrl, filterTasksForDelegate, formatPendingDecisionsPayload, resolveWebAppOrigin, } from './pendingDecisions.js';
11
12
  import { resolveMcpConnectionTarget, withMcpGatewayClient, } from './mcpConnectionTools.js';
12
13
  import { readOnly, write, destructive } from './toolAnnotations.js';
@@ -676,6 +677,9 @@ export function registerZiggsTools(server, creds, cfg) {
676
677
  ...news,
677
678
  session,
678
679
  ...actions,
680
+ // A directory host may never show the model the server's
681
+ // instructions; the session-start read is the one place it looks.
682
+ ...(isDirectorySurface(server) ? { rules: sessionRules('directory') } : {}),
679
683
  ...(note && typeof actions.notice === 'string'
680
684
  ? { notice: `${actions.notice} ${note}` }
681
685
  : {}),
@@ -3,6 +3,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import { AssistantConnectionClient, withAssistantRequest, decodeOperatorKeyClaims, } from '@ziggs-ai/api-client';
4
4
  import { formatInboxToolResult } from './inboxToolResult.js';
5
5
  import { compactTextResult, windowNote } from './inboxCompact.js';
6
+ import { sessionRules } from './protocol/connectText.js';
6
7
  import { formatPendingDecisionsPayload, resolveWebAppOrigin } from './pendingDecisions.js';
7
8
  const routers = new WeakMap();
8
9
  /** The catalog and its dispatchers: they reach other tools, which route themselves. */
@@ -207,6 +208,7 @@ export class WorkspaceRouter {
207
208
  return compactTextResult({
208
209
  ...news,
209
210
  ...pending,
211
+ ...(name === 'ziggs_inbox' && args.waitSeconds == null ? { rules: sessionRules('directory') } : {}),
210
212
  ...(note && typeof pending.notice === 'string' ? { notice: `${pending.notice} ${note}` } : {}),
211
213
  session: { workspaces },
212
214
  instruction: `Assigned to any of agentIds is your work. ${ASK_YOUR_HUMAN}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.36.1",
3
+ "version": "0.36.2",
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": {
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "scripts": {
29
29
  "build": "tsc -p tsconfig.json && chmod +x dist/index.js",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit",
30
31
  "prepack": "rm -rf dist && npm run build",
31
32
  "start": "node dist/index.js",
32
33
  "dev": "tsx src/index.ts",