@ziggs-ai/ziggs-mcp 0.37.19 → 0.37.20

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,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.37.20
6
+
7
+ - Set `ZIGGS_MCP_CHANNEL=1` to make the local server a Claude Code channel: an open Claude Code session hears new mail within seconds. Mail already waiting becomes one event, and each new delivery one more. Events carry ids only, never what the mail says; read it with `ziggs_inbox`. Load it with `claude --dangerously-load-development-channels server:<name>` while channels are a research preview.
8
+ - `tools/list` no longer carries the `$schema` line in every input schema.
9
+ - Shorter parameter text on the agreement verbs, `ziggs_task_create` and `ziggs_artifact_record`. Every rule stays.
10
+
5
11
  ## 0.37.19
6
12
 
7
13
  - New read-only `ziggs_inbox_show`: what is waiting on the person and their work in hand, as an MCP Apps card in hosts that draw one (Claude, ChatGPT, Cursor) and as the usual text everywhere else. The card's buttons only open the Ziggs website; it changes nothing itself. The view is served at `ui://ziggs/inbox-card`.
package/dist/config.d.ts CHANGED
@@ -49,6 +49,12 @@ declare const envSchema: z.ZodObject<{
49
49
  * cut cold-start deferred-loading (#7). Unset = all tools register.
50
50
  */
51
51
  ZIGGS_MCP_CORE_ONLY: z.ZodOptional<z.ZodString>;
52
+ /**
53
+ * Set to 1/true/yes to ring an open Claude Code session when new mail
54
+ * arrives (a Claude Code channel). Claude Code must also load this server
55
+ * as a channel; see `inboxChannel.ts`.
56
+ */
57
+ ZIGGS_MCP_CHANNEL: z.ZodOptional<z.ZodString>;
52
58
  }, "strip", z.ZodTypeAny, {
53
59
  ZIGGS_OPERATOR_KEY: string;
54
60
  ZIGGS_API_URL?: string | undefined;
@@ -63,6 +69,7 @@ declare const envSchema: z.ZodObject<{
63
69
  ZIGGS_SESSION_NAME?: string | undefined;
64
70
  ZIGGS_MCP_DEBUG?: string | undefined;
65
71
  ZIGGS_MCP_CORE_ONLY?: string | undefined;
72
+ ZIGGS_MCP_CHANNEL?: string | undefined;
66
73
  }, {
67
74
  ZIGGS_OPERATOR_KEY: string;
68
75
  ZIGGS_API_URL?: string | undefined;
@@ -77,6 +84,7 @@ declare const envSchema: z.ZodObject<{
77
84
  ZIGGS_SESSION_NAME?: string | undefined;
78
85
  ZIGGS_MCP_DEBUG?: string | undefined;
79
86
  ZIGGS_MCP_CORE_ONLY?: string | undefined;
87
+ ZIGGS_MCP_CHANNEL?: string | undefined;
80
88
  }>;
81
89
  type EnvConfig = z.infer<typeof envSchema>;
82
90
  export interface ZiggsMcpConfig extends EnvConfig {
@@ -114,6 +122,11 @@ export interface ZiggsMcpConfig extends EnvConfig {
114
122
  * connect text, inside that text's budget.
115
123
  */
116
124
  eventsEnabled?: boolean;
125
+ /**
126
+ * Local only: declare the Claude Code channel and ring the session for new
127
+ * mail. Adds one line to the compact door's connect text.
128
+ */
129
+ channel?: boolean;
117
130
  }
118
131
  /** Load delegate credentials from the environment. */
119
132
  export declare function loadConfig(): ZiggsMcpConfig;
package/dist/config.js CHANGED
@@ -51,6 +51,12 @@ const envSchema = z.object({
51
51
  * cut cold-start deferred-loading (#7). Unset = all tools register.
52
52
  */
53
53
  ZIGGS_MCP_CORE_ONLY: z.string().optional(),
54
+ /**
55
+ * Set to 1/true/yes to ring an open Claude Code session when new mail
56
+ * arrives (a Claude Code channel). Claude Code must also load this server
57
+ * as a channel; see `inboxChannel.ts`.
58
+ */
59
+ ZIGGS_MCP_CHANNEL: z.string().optional(),
54
60
  });
55
61
  /** Parse a truthy env flag (1/true/yes) — shared by ZIGGS_MCP_DEBUG and ZIGGS_MCP_CORE_ONLY. */
56
62
  function parseBoolFlag(raw) {
@@ -75,6 +81,7 @@ export function loadConfig() {
75
81
  ZIGGS_SESSION_NAME: process.env.ZIGGS_SESSION_NAME,
76
82
  ZIGGS_MCP_DEBUG: process.env.ZIGGS_MCP_DEBUG,
77
83
  ZIGGS_MCP_CORE_ONLY: process.env.ZIGGS_MCP_CORE_ONLY,
84
+ ZIGGS_MCP_CHANNEL: process.env.ZIGGS_MCP_CHANNEL,
78
85
  };
79
86
  const parsed = envSchema.safeParse(raw);
80
87
  if (!parsed.success) {
@@ -108,6 +115,7 @@ export function loadConfig() {
108
115
  resolvedAgentId,
109
116
  debugTools: parseBoolFlag(parsed.data.ZIGGS_MCP_DEBUG),
110
117
  coreOnly: parseBoolFlag(parsed.data.ZIGGS_MCP_CORE_ONLY),
118
+ channel: parseBoolFlag(parsed.data.ZIGGS_MCP_CHANNEL),
111
119
  directConnection: !isDirectoryBoarded(decodeOperatorKeyClaims(parsed.data.ZIGGS_OPERATOR_KEY)),
112
120
  };
113
121
  }
@@ -0,0 +1,51 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { InboxClient, type Creds } from '@ziggs-ai/api-client';
3
+ export type InboxChangesOptions = NonNullable<Parameters<InboxClient['getChanges']>[0]>;
4
+ export type InboxChangesResult = Awaited<ReturnType<InboxClient['getChanges']>>;
5
+ /**
6
+ * The Claude Code channel: an open Claude Code session hears new Ziggs mail
7
+ * within seconds, without polling a tool.
8
+ *
9
+ * A local (stdio) server that declares `experimental['claude/channel']` may
10
+ * push `notifications/claude/channel`; Claude Code puts each one in front of
11
+ * the model as a `<channel>` tag. The server holds the inbox observation
12
+ * long-poll (`/inbox/changes`) and rings once per new delivery.
13
+ *
14
+ * It rings, it does not deliver. An event carries references only (kind and
15
+ * ids), never message text: what a counterparty wrote would otherwise land in
16
+ * the session as if the person had typed it. The model reads the mail through
17
+ * the inbox tools, so claiming, acknowledging and who handles the work stay
18
+ * exactly where they are. Observation itself never claims or acknowledges.
19
+ *
20
+ * Off unless `ZIGGS_MCP_CHANNEL` is set, and Claude Code also has to load the
21
+ * server as a channel (`--dangerously-load-development-channels server:<name>`
22
+ * while channels are a research preview). Without that, Claude Code drops the
23
+ * events and the long-poll costs no tokens.
24
+ */
25
+ export declare const CHANNEL_CAPABILITY: {
26
+ 'claude/channel': {};
27
+ };
28
+ export declare const CHANNEL_NOTIFICATION = "notifications/claude/channel";
29
+ type Delivery = InboxChangesResult['deliveries'][number];
30
+ type Observe = (options: InboxChangesOptions) => Promise<InboxChangesResult>;
31
+ export interface ChannelEvent {
32
+ content: string;
33
+ meta: Record<string, string>;
34
+ }
35
+ /** The observation read for these credentials: the agent's, or the connection's. */
36
+ export declare function observeFor(creds: Creds): Observe;
37
+ /** One delivery as a channel event: what arrived and where, never what it says. */
38
+ export declare function channelEvent(delivery: Delivery, agentId?: string): ChannelEvent;
39
+ /** Mail that was already waiting when the session started, as one event. */
40
+ export declare function backlogEvent(count: number): ChannelEvent;
41
+ /**
42
+ * Watch the inbox and ring the session for each new delivery. Starts by
43
+ * folding whatever was already waiting into one event, so a session does not
44
+ * open to a wall of tags. Returns a stop function.
45
+ */
46
+ export declare function startInboxChannel(server: McpServer, observe: Observe, options?: {
47
+ agentId?: string;
48
+ holdSeconds?: number;
49
+ log?: (line: string) => void;
50
+ }): () => void;
51
+ export {};
@@ -0,0 +1,133 @@
1
+ import { AssistantConnectionClient, decodeOperatorKeyClaims, InboxClient, } from '@ziggs-ai/api-client';
2
+ /**
3
+ * The Claude Code channel: an open Claude Code session hears new Ziggs mail
4
+ * within seconds, without polling a tool.
5
+ *
6
+ * A local (stdio) server that declares `experimental['claude/channel']` may
7
+ * push `notifications/claude/channel`; Claude Code puts each one in front of
8
+ * the model as a `<channel>` tag. The server holds the inbox observation
9
+ * long-poll (`/inbox/changes`) and rings once per new delivery.
10
+ *
11
+ * It rings, it does not deliver. An event carries references only (kind and
12
+ * ids), never message text: what a counterparty wrote would otherwise land in
13
+ * the session as if the person had typed it. The model reads the mail through
14
+ * the inbox tools, so claiming, acknowledging and who handles the work stay
15
+ * exactly where they are. Observation itself never claims or acknowledges.
16
+ *
17
+ * Off unless `ZIGGS_MCP_CHANNEL` is set, and Claude Code also has to load the
18
+ * server as a channel (`--dangerously-load-development-channels server:<name>`
19
+ * while channels are a research preview). Without that, Claude Code drops the
20
+ * events and the long-poll costs no tokens.
21
+ */
22
+ export const CHANNEL_CAPABILITY = { 'claude/channel': {} };
23
+ export const CHANNEL_NOTIFICATION = 'notifications/claude/channel';
24
+ /** The longest hold `/inbox/changes` grants. */
25
+ const HOLD_SECONDS = 110;
26
+ const RETRY_MS = [1_000, 5_000, 15_000, 60_000];
27
+ /** The observation read for these credentials: the agent's, or the connection's. */
28
+ export function observeFor(creds) {
29
+ if (decodeOperatorKeyClaims(creds.operatorKey)?.connectionClientId) {
30
+ const client = new AssistantConnectionClient(creds);
31
+ return (options) => client.getChanges(options);
32
+ }
33
+ const inbox = new InboxClient(creds.operatorKey, creds.agentId, undefined, creds.instanceId, {
34
+ clientSessionId: creds.clientSessionId,
35
+ workspaceIds: creds.workspaceIds,
36
+ app: creds.app,
37
+ sessionName: creds.sessionName,
38
+ });
39
+ return (options) => inbox.getChanges(options);
40
+ }
41
+ /** One delivery as a channel event: what arrived and where, never what it says. */
42
+ export function channelEvent(delivery, agentId) {
43
+ const forYou = !!agentId && delivery.assigneeId === agentId;
44
+ const meta = { kind: delivery.kind, resource_id: delivery.resourceId };
45
+ if (delivery.chatId)
46
+ meta.chat_id = delivery.chatId;
47
+ if (delivery.taskId)
48
+ meta.task_id = delivery.taskId;
49
+ if (delivery.agreementId)
50
+ meta.agreement_id = delivery.agreementId;
51
+ if (delivery.orgId)
52
+ meta.org_id = delivery.orgId;
53
+ if (forYou)
54
+ meta.for_you = 'true';
55
+ if (delivery.needsHuman)
56
+ meta.needs_human = 'true';
57
+ const who = forYou ? ' It is assigned to you.' : delivery.needsHuman ? ' It asks for your person.' : '';
58
+ return {
59
+ content: `New Ziggs mail (${delivery.kind.replace(/_/g, ' ')}).${who} Read it with ziggs_inbox and follow its rules.`,
60
+ meta,
61
+ };
62
+ }
63
+ /** Mail that was already waiting when the session started, as one event. */
64
+ export function backlogEvent(count) {
65
+ return {
66
+ content: `${count} ${count === 1 ? 'item was' : 'items were'} already waiting in Ziggs when this session started. Read them with ziggs_inbox when you are free.`,
67
+ meta: { kind: 'backlog', count: String(count) },
68
+ };
69
+ }
70
+ function apiStatus(error) {
71
+ const status = error?.status;
72
+ return typeof status === 'number' ? status : undefined;
73
+ }
74
+ const sleep = (ms, signal) => new Promise((resolve) => {
75
+ if (signal.aborted)
76
+ return resolve();
77
+ const timer = setTimeout(resolve, ms);
78
+ signal.addEventListener('abort', () => { clearTimeout(timer); resolve(); }, { once: true });
79
+ });
80
+ /**
81
+ * Watch the inbox and ring the session for each new delivery. Starts by
82
+ * folding whatever was already waiting into one event, so a session does not
83
+ * open to a wall of tags. Returns a stop function.
84
+ */
85
+ export function startInboxChannel(server, observe, options = {}) {
86
+ const stop = new AbortController();
87
+ const signal = stop.signal;
88
+ const hold = options.holdSeconds ?? HOLD_SECONDS;
89
+ const log = options.log ?? ((line) => process.stderr.write(`[ziggs-mcp channel] ${line}\n`));
90
+ const ring = (event) => server.server.notification({ method: CHANNEL_NOTIFICATION, params: { ...event } });
91
+ void (async () => {
92
+ let cursor;
93
+ let failures = 0;
94
+ while (!signal.aborted) {
95
+ try {
96
+ if (cursor === undefined) {
97
+ // Catch up without a hold: what is retained now is the backlog.
98
+ let waiting = 0;
99
+ let page;
100
+ do {
101
+ page = await observe({ cursor, signal });
102
+ cursor = page.cursor;
103
+ waiting += page.deliveries.length;
104
+ } while (page.hasMore && !signal.aborted);
105
+ if (waiting > 0 && !signal.aborted)
106
+ await ring(backlogEvent(waiting));
107
+ }
108
+ const page = await observe({ cursor, waitSeconds: hold, signal });
109
+ cursor = page.cursor;
110
+ failures = 0;
111
+ for (const delivery of page.deliveries) {
112
+ if (signal.aborted)
113
+ break;
114
+ await ring(channelEvent(delivery, options.agentId));
115
+ }
116
+ }
117
+ catch (error) {
118
+ if (signal.aborted)
119
+ break;
120
+ // An expired cursor starts over from what is retained.
121
+ if (apiStatus(error) === 410) {
122
+ cursor = undefined;
123
+ continue;
124
+ }
125
+ const wait = RETRY_MS[Math.min(failures, RETRY_MS.length - 1)];
126
+ failures += 1;
127
+ log(`inbox watch failed (${error?.message ?? String(error)}); retrying in ${wait / 1000}s`);
128
+ await sleep(wait, signal);
129
+ }
130
+ }
131
+ })();
132
+ return () => stop.abort();
133
+ }
@@ -27,5 +27,7 @@ export declare function sessionRules(door: ConnectDoor): string[];
27
27
  export interface ConnectTextOptions {
28
28
  /** The serving backend delivers `ziggs.inbox.updated` events. */
29
29
  events?: boolean;
30
+ /** The local server rings a Claude Code session through a channel. */
31
+ channel?: boolean;
30
32
  }
31
33
  export declare function renderConnectText(door: ConnectDoor, options?: ConnectTextOptions): string;
@@ -55,6 +55,11 @@ const CATALOG = 'Only the everyday tools are loaded. ziggs_tools lists the rest;
55
55
  * text was already sized: that append is what cut the text mid-word.
56
56
  */
57
57
  const EVENTS = 'Inbox events: a host may subscribe to ziggs.inbox.updated per workspace; promise alerts only once it confirms. An event carries only orgId: read that inbox and follow its rules.';
58
+ /**
59
+ * Compact door only, when the local server rings a Claude Code session. The
60
+ * event says that mail arrived, never what it says.
61
+ */
62
+ const CHANNEL = 'A <channel> event from Ziggs means new mail (ids only): read it with ziggs_inbox and follow its rules.';
58
63
  export function renderConnectText(door, options = {}) {
59
64
  return [
60
65
  `- ${DATA}`,
@@ -63,5 +68,6 @@ export function renderConnectText(door, options = {}) {
63
68
  ...RULES.map((r) => `- ${r}`),
64
69
  ...(door === 'compact' ? [`- ${CATALOG}`] : []),
65
70
  ...(door === 'directory' && options.events ? [`- ${EVENTS}`] : []),
71
+ ...(door === 'compact' && options.channel ? [`- ${CHANNEL}`] : []),
66
72
  ].join('\n');
67
73
  }
package/dist/server.js CHANGED
@@ -8,9 +8,10 @@ import { AssistantConnectionClient, decodeOperatorKeyClaims, InboxClient } from
8
8
  import { loadConfig } from './config.js';
9
9
  import { credsFromConfig } from './creds.js';
10
10
  import { registerZiggsTools } from './tools.js';
11
- import { applySurfacePolicy } from './surface.js';
11
+ import { applySurfacePolicy, listWithoutSchemaDialect } from './surface.js';
12
12
  import { backendToolPick } from './toolPick.js';
13
13
  import { renderConnectText } from './protocol/connectText.js';
14
+ import { CHANNEL_CAPABILITY, observeFor, startInboxChannel } from './inboxChannel.js';
14
15
  const require = createRequire(import.meta.url);
15
16
  const { version } = require('../package.json');
16
17
  /** Shared MCP server factory — stdio (local) and remote HTTP (backend) reuse this. */
@@ -25,8 +26,11 @@ export function createZiggsMcpServer(creds, cfg) {
25
26
  // stdio and remote HTTP alike, since both build the server here.
26
27
  instructions: renderConnectText(cfg.directConnection === false ? 'directory' : 'compact', {
27
28
  events: cfg.eventsEnabled === true,
29
+ channel: cfg.channel === true && cfg.directConnection !== false,
28
30
  }),
29
31
  });
32
+ if (cfg.channel === true && cfg.directConnection !== false)
33
+ server.server.registerCapabilities({ experimental: CHANNEL_CAPABILITY });
30
34
  if (cfg.directConnection === false)
31
35
  installDirectorySurface(server);
32
36
  installConnectionNotices(server, creds, version);
@@ -37,6 +41,7 @@ export function createZiggsMcpServer(creds, cfg) {
37
41
  // catalog remains only for self-configured operator-key clients.
38
42
  if (cfg.directConnection !== false)
39
43
  applySurfacePolicy(server, { pickTool: backendToolPick(creds) });
44
+ listWithoutSchemaDialect(server);
40
45
  return server;
41
46
  }
42
47
  /** Drop this process's inbox host. Best-effort — shutdown must not fail on it. */
@@ -74,4 +79,6 @@ export async function startStdioServer() {
74
79
  installStdioInboxHostRelease(creds);
75
80
  const transport = new StdioServerTransport();
76
81
  await server.connect(transport);
82
+ if (cfg.channel)
83
+ startInboxChannel(server, observeFor(creds), { agentId: creds.agentId });
77
84
  }
package/dist/surface.d.ts CHANGED
@@ -43,6 +43,14 @@ export declare const NATIVE_TOOLS: readonly string[];
43
43
  export declare const CATALOG_TOOLS: readonly string[];
44
44
  /** Per-tool how-to-call, derived from NATIVE_TOOLS and the annotations. */
45
45
  export declare function callLine(entry: CatalogEntry): string;
46
+ /**
47
+ * Drop the `$schema` line the SDK writes into every tool's input and output
48
+ * schema. It names a JSON Schema dialect no MCP client reads, and on the
49
+ * directory door it was the same 52 characters 64 times in every
50
+ * `tools/list`. Wraps whatever handler is installed last, so call it after
51
+ * every other `tools/list` filter.
52
+ */
53
+ export declare function listWithoutSchemaDialect(server: McpServer): void;
46
54
  /**
47
55
  * Narrow `tools/list` to the native set, then register the tools that make the
48
56
  * rest reachable: the catalog, and one dispatcher per annotation lane.
package/dist/surface.js CHANGED
@@ -131,6 +131,36 @@ function listOnly(server, listed) {
131
131
  };
132
132
  });
133
133
  }
134
+ /**
135
+ * Drop the `$schema` line the SDK writes into every tool's input and output
136
+ * schema. It names a JSON Schema dialect no MCP client reads, and on the
137
+ * directory door it was the same 52 characters 64 times in every
138
+ * `tools/list`. Wraps whatever handler is installed last, so call it after
139
+ * every other `tools/list` filter.
140
+ */
141
+ export function listWithoutSchemaDialect(server) {
142
+ const inner = server.server;
143
+ const built = inner._requestHandlers?.get('tools/list');
144
+ if (!built)
145
+ return;
146
+ const strip = (schema) => {
147
+ if (!schema || typeof schema !== 'object')
148
+ return schema;
149
+ const { $schema: _dialect, ...rest } = schema;
150
+ return rest;
151
+ };
152
+ server.server.setRequestHandler(ListToolsRequestSchema, async (req, extra) => {
153
+ const all = await built(req, extra);
154
+ return {
155
+ ...all,
156
+ tools: all.tools.map((tool) => ({
157
+ ...tool,
158
+ inputSchema: strip(tool.inputSchema),
159
+ ...(tool.outputSchema ? { outputSchema: strip(tool.outputSchema) } : {}),
160
+ })),
161
+ };
162
+ });
163
+ }
134
164
  /**
135
165
  * One dispatcher: reaches every catalog tool of its lane by name, validates the
136
166
  * arguments against that tool's own strict schema, and runs its stored handler.
package/dist/tools.js CHANGED
@@ -927,12 +927,12 @@ export function registerZiggsTools(server, creds, cfg) {
927
927
  .string()
928
928
  .max(80)
929
929
  .optional()
930
- .describe('Short label for list rows, around 60 characters. Omit and the task lists under a trimmed description, which is a wall of prose at a glance. Set it whenever this task will be one row among many.'),
930
+ .describe('Short list label, around 60 characters. Set it when this is one of many tasks; otherwise the list shows the description.'),
931
931
  parentTaskId: z.string().optional().describe('Parent task id for sub-tasks'),
932
932
  assigneeId: z
933
933
  .string()
934
934
  .optional()
935
- .describe('Agent or user ID to explicitly assign this task to — must hold a grant on the agreement, so you can only hand work to someone that contract already reaches'),
935
+ .describe('Agent or user to assign it to; they must already hold a grant on the agreement.'),
936
936
  inputArtifactIds: z
937
937
  .array(z.string())
938
938
  .optional()
@@ -940,7 +940,7 @@ export function registerZiggsTools(server, creds, cfg) {
940
940
  waitsOn: z
941
941
  .array(z.string())
942
942
  .optional()
943
- .describe('Task ids this task waits for. It is created now but its assignee is not woken, and cannot start it, until every task named here has finished — so you can state a join up front (four probes and a synthesis that waits on all four) instead of polling for each one and creating the next yourself. Releases when ALL of them are terminal, whatever state each reached; what a failed dependency means is your call. You must be able to read a task to name it here.'),
943
+ .describe('Task ids to wait for: created now, but its assignee is not woken until all of them end, in any state. States a join up front instead of polling. Name only tasks you can read.'),
944
944
  closesBy: z
945
945
  .enum(['result', 'children'])
946
946
  .optional()
@@ -965,7 +965,7 @@ export function registerZiggsTools(server, creds, cfg) {
965
965
  .min(1)
966
966
  .max(100)
967
967
  .optional()
968
- .describe('Children created with this task, all or none. waitsOn names sibling names. The answer maps each name to its task id. This task\'s inputArtifactIds go to children that wait on nothing; planReviewTiming before_execution holds every child until this task\'s plan is acknowledged. runIf makes a child conditional on what the children before it found.'),
968
+ .describe('Children created with this task, all or none; waitsOn names sibling names, and the answer maps each name to its task id. Children that wait on nothing get this task\'s inputArtifactIds. before_execution holds every child until the plan is acknowledged.'),
969
969
  // POST /tasks and the SDK's task_create have always taken
970
970
  // these three; only this surface hid them, so an agent on MCP had to
971
971
  // create-then-replace_plan even when it already knew the steps.
@@ -1021,7 +1021,7 @@ export function registerZiggsTools(server, creds, cfg) {
1021
1021
  .describe('Total reminders allowed per person asked, after the first ask. Then the holder stops and reports.'),
1022
1022
  })
1023
1023
  .optional()
1024
- .describe('How often the holder may remind, and how many times at most. Both halves are required together: a cadence with no budget is refused, because one reminder is one wake and a rule that never says stop is an open-ended loop. Omit entirely for a task nobody needs reminding about.'),
1024
+ .describe('How often the holder may remind and how many times at most; both are required. Omit for a task nobody needs reminding about.'),
1025
1025
  sources: sourcesField(),
1026
1026
  }, write('Create a task'), async ({ agreementId, description, title, parentTaskId, assigneeId, inputArtifactIds, waitsOn, closesBy, children, plan, planReviewTiming, requireMidWorkPlanAck, dueAt, checkBack, sources, }) => {
1027
1027
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.37.19",
3
+ "version": "0.37.20",
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.19",
54
+ "@ziggs-ai/api-client": "0.37.20",
55
55
  "dotenv": "^16.6.1",
56
56
  "zod": "^3.24.2",
57
57
  "zod-to-json-schema": "^3.25.1"