@ziggs-ai/ziggs-mcp 0.1.19 → 0.1.22

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.
@@ -1,18 +1,67 @@
1
- import type { InboxAckResult, InboxEnvelope, Task } from '@ziggs-ai/api-client';
1
+ import type { ContextReachDescriptor, ContextReadType, InboxAckResult, InboxEnvelope, Task } from '@ziggs-ai/api-client';
2
2
  /**
3
- * ZIG-558 (A3): each inbox call points at the next call. Synthesized purely
4
- * from fields already on the envelope — no new endpoint, no new tool — so the
5
- * agent doesn't have to remember the inbox → read → act → ack loop from the
6
- * upfront prompt. Thin protocol up front, heavy guidance in the response.
3
+ * ZIG-634 (Step 1): a pre-filled next call. The agent can run it verbatim
4
+ * instead of assembling args from the ids scattered through the response.
5
+ * `tool` + `args` mirror the MCP tool signature; `why` is a one-line reason.
6
+ */
7
+ export interface ReadPlanCall {
8
+ tool: string;
9
+ args: Record<string, unknown>;
10
+ why: string;
11
+ }
12
+ /**
13
+ * ZIG-634: replace the free-text `nextActions` hints with typed `readPlan`
14
+ * call objects — tool name + pre-filled args — so the most common loop
15
+ * (inbox → read each chat with news → ack) needs no guesswork.
16
+ *
17
+ * Safe by construction: every entry is synthesized purely from fields already
18
+ * on the envelope (scope kinds, ids, per-chat counts, latestAt). No new data
19
+ * is read and no new permission check runs — this is the same information the
20
+ * caller already received, restated as runnable calls.
21
+ *
22
+ * Mapping honours how reads resolve server-side: messages read only via chat,
23
+ * artifacts via chat or agreement. For multi-chat scopes (org / agreement) we
24
+ * use the per-chat breakdown (ZIG-543) to name the chatIds.
25
+ */
26
+ export declare function buildReadPlan(inbox: InboxEnvelope): ReadPlanCall[];
27
+ /**
28
+ * ZIG-634: forward-continuation for a read_context page. Built only from fields
29
+ * already on the page (via, hasMore/nextCursor, latestSequence) plus the grant
30
+ * id the caller presented — echoed back, never discovered. Nothing new is read.
7
31
  *
8
- * Mapping honours how reads actually resolve server-side: messages read only
9
- * via chat, artifacts via chat or agreement. For multi-chat scopes (org /
10
- * agreement) we use the per-chat breakdown (ZIG-543) to name the chatIds.
32
+ * - When the page has more rows in this window: a cursor call for the next page.
33
+ * - When the page reports a high-water sequence: a forward-delta call that
34
+ * returns only items created after this page (after = latestSequence).
11
35
  */
12
- export declare function buildNextActions(inbox: InboxEnvelope): string[];
36
+ export declare function buildReadContextReadPlan(page: {
37
+ hasMore?: boolean;
38
+ nextCursor?: string | null;
39
+ latestSequence?: string | null;
40
+ }, type: ContextReadType, via: string, presentedGrantId?: string): ReadPlanCall[];
41
+ /**
42
+ * ZIG-635 (Step 2): the covering grant attached to a scope so the agent can
43
+ * pin the right X-Context-Grant-Id without a separate discover_context call.
44
+ * Grant metadata only — never a resource-derived field.
45
+ */
46
+ export interface ScopeGrantTag {
47
+ grantId: string;
48
+ temporal: 'from-now' | 'from-start';
49
+ watermarkAt: string;
50
+ expiresAt: string | null;
51
+ parentGrantId: string | null;
52
+ }
53
+ /**
54
+ * ZIG-635: index the caller's own live reach descriptors by scope. The reach
55
+ * list is already the caller's non-expired grants (holderId == principalId),
56
+ * so this is grant metadata the caller already holds — no protected content.
57
+ */
58
+ export declare function indexReachByScope(reach: ContextReachDescriptor[]): Map<string, ScopeGrantTag>;
13
59
  /**
14
60
  * Put humanAttention first so MCP hosts surface it before counts (ZIG-482),
15
- * and append nextActions last so each inbox call self-narrates the follow-up
16
- * call (ZIG-558) without disturbing the leading humanAttention key.
61
+ * and append readPlan last so each inbox call self-narrates the follow-up
62
+ * calls (ZIG-634) without disturbing the leading humanAttention key.
63
+ *
64
+ * When `reach` (the caller's own live grants) is passed, each scope is tagged
65
+ * with its covering grant (ZIG-635) so the agent can pin X-Context-Grant-Id.
17
66
  */
18
- export declare function formatInboxToolResult(inbox: InboxEnvelope, ack?: InboxAckResult | null, webOrigin?: string, activeTasks?: Task[]): Record<string, unknown>;
67
+ export declare function formatInboxToolResult(inbox: InboxEnvelope, ack?: InboxAckResult | null, webOrigin?: string, activeTasks?: Task[], reach?: ContextReachDescriptor[]): Record<string, unknown>;
@@ -1,77 +1,194 @@
1
1
  import { formatPendingDecisionsPayload, resolveWebAppOrigin, } from './pendingDecisions.js';
2
- /** Keep the hint list bounded; the full scopes array still carries everything. */
3
- const MAX_NEXT_ACTIONS = 12;
4
- function readHint(type, kind, id) {
5
- return `ziggs_read_context type=${type} via=${kind}:${id}`;
2
+ /** Keep the plan bounded; the full scopes array still carries everything. */
3
+ const MAX_READ_PLAN = 12;
4
+ function readContextCall(type, kind, id) {
5
+ return {
6
+ tool: 'ziggs_read_context',
7
+ args: { type, via: `${kind}:${id}` },
8
+ why: `open the ${type} behind the count on ${kind}:${id}`,
9
+ };
6
10
  }
7
11
  /**
8
- * ZIG-558 (A3): each inbox call points at the next call. Synthesized purely
9
- * from fields already on the envelope — no new endpoint, no new tool — so the
10
- * agent doesn't have to remember the inbox → read → act → ack loop from the
11
- * upfront prompt. Thin protocol up front, heavy guidance in the response.
12
+ * ZIG-634: replace the free-text `nextActions` hints with typed `readPlan`
13
+ * call objects — tool name + pre-filled args — so the most common loop
14
+ * (inbox → read each chat with news → ack) needs no guesswork.
12
15
  *
13
- * Mapping honours how reads actually resolve server-side: messages read only
14
- * via chat, artifacts via chat or agreement. For multi-chat scopes (org /
15
- * agreement) we use the per-chat breakdown (ZIG-543) to name the chatIds.
16
+ * Safe by construction: every entry is synthesized purely from fields already
17
+ * on the envelope (scope kinds, ids, per-chat counts, latestAt). No new data
18
+ * is read and no new permission check runs — this is the same information the
19
+ * caller already received, restated as runnable calls.
20
+ *
21
+ * Mapping honours how reads resolve server-side: messages read only via chat,
22
+ * artifacts via chat or agreement. For multi-chat scopes (org / agreement) we
23
+ * use the per-chat breakdown (ZIG-543) to name the chatIds.
16
24
  */
17
- export function buildNextActions(inbox) {
18
- const actions = [];
25
+ export function buildReadPlan(inbox) {
26
+ const plan = [];
19
27
  const proposals = inbox.proposalsAwaitingMe ?? [];
20
28
  const connectionRequests = inbox.connectionRequestsAwaitingMe ?? [];
21
29
  const scopes = inbox.scopes ?? [];
22
30
  // Decisions first — these also drive humanAttention (pull-only: no push).
23
- if (proposals.length) {
24
- const ids = proposals.map((p) => p.agreementId).join(', ');
25
- actions.push(`Respond to ${proposals.length} agreement proposal(s) with ziggs_respond_to_agreement (${ids})`);
31
+ // The decision (approve/reject) is the human's; we only pre-fill the target.
32
+ for (const p of proposals) {
33
+ if (plan.length >= MAX_READ_PLAN)
34
+ break;
35
+ plan.push({
36
+ tool: 'ziggs_respond_to_agreement',
37
+ args: { agreementId: p.agreementId },
38
+ why: 'agreement proposal awaiting your response — wait for the human to approve/reject',
39
+ });
26
40
  }
27
- if (connectionRequests.length) {
28
- const ids = connectionRequests.map((c) => c.requestId).join(', ');
29
- actions.push(`Respond to ${connectionRequests.length} connection request(s) with ziggs_respond_to_agreement (${ids})`);
41
+ for (const c of connectionRequests) {
42
+ if (plan.length >= MAX_READ_PLAN)
43
+ break;
44
+ plan.push({
45
+ tool: 'ziggs_respond_to_agreement',
46
+ args: { agreementId: c.requestId },
47
+ why: 'connection request awaiting your response — wait for the human to approve/reject',
48
+ });
30
49
  }
31
50
  // Reads — point each scope's news at the call that opens it.
32
51
  for (const s of scopes) {
33
- if (actions.length >= MAX_NEXT_ACTIONS)
52
+ if (plan.length >= MAX_READ_PLAN)
34
53
  break;
35
54
  const { kind, id } = s.scope;
36
55
  if (kind === 'chat') {
37
56
  if (s.newMessages)
38
- actions.push(readHint('messages', 'chat', id));
57
+ plan.push(readContextCall('messages', 'chat', id));
39
58
  if (s.newArtifacts)
40
- actions.push(readHint('artifacts', 'chat', id));
59
+ plan.push(readContextCall('artifacts', 'chat', id));
41
60
  }
42
61
  else if (kind === 'agreement') {
43
62
  // Messages resolve only via chat — name the chats from the breakdown.
44
63
  for (const c of s.chats ?? []) {
45
64
  if (c.newMessages)
46
- actions.push(readHint('messages', 'chat', c.chatId));
65
+ plan.push(readContextCall('messages', 'chat', c.chatId));
47
66
  }
48
67
  // Artifacts (incl. task-result artifacts) read directly via the agreement.
49
68
  if (s.newArtifacts)
50
- actions.push(readHint('artifacts', 'agreement', id));
69
+ plan.push(readContextCall('artifacts', 'agreement', id));
51
70
  }
52
71
  else {
53
72
  // org: both messages and artifacts resolve per chat only.
54
73
  for (const c of s.chats ?? []) {
55
74
  if (c.newMessages)
56
- actions.push(readHint('messages', 'chat', c.chatId));
75
+ plan.push(readContextCall('messages', 'chat', c.chatId));
57
76
  if (c.newArtifacts)
58
- actions.push(readHint('artifacts', 'chat', c.chatId));
77
+ plan.push(readContextCall('artifacts', 'chat', c.chatId));
59
78
  }
60
79
  }
61
80
  }
62
- // Close the loop: reading never clears the inbox — ack what you handled.
63
- if (scopes.length) {
64
- actions.push('After handling a scope, ack it: ziggs_inbox ack=[{ kind, id, upTo: latestAt }]');
81
+ // Close the loop: reading never clears the inbox — pre-fill the ack call with
82
+ // each scope's own latestAt (only scopes that actually have a high-water mark).
83
+ const ackTargets = scopes
84
+ .filter((s) => s.latestAt)
85
+ .map((s) => ({ kind: s.scope.kind, id: s.scope.id, upTo: s.latestAt }));
86
+ if (ackTargets.length && plan.length < MAX_READ_PLAN) {
87
+ plan.push({
88
+ tool: 'ziggs_inbox',
89
+ args: { ack: ackTargets },
90
+ why: 'reading does not clear the inbox — ack the scopes you handled (drop any you did not)',
91
+ });
65
92
  }
66
- return actions.slice(0, MAX_NEXT_ACTIONS);
93
+ return plan.slice(0, MAX_READ_PLAN);
94
+ }
95
+ /**
96
+ * ZIG-634: forward-continuation for a read_context page. Built only from fields
97
+ * already on the page (via, hasMore/nextCursor, latestSequence) plus the grant
98
+ * id the caller presented — echoed back, never discovered. Nothing new is read.
99
+ *
100
+ * - When the page has more rows in this window: a cursor call for the next page.
101
+ * - When the page reports a high-water sequence: a forward-delta call that
102
+ * returns only items created after this page (after = latestSequence).
103
+ */
104
+ export function buildReadContextReadPlan(page, type, via, presentedGrantId) {
105
+ const plan = [];
106
+ // Only echo a grant the caller already presented — never one we discovered.
107
+ const grant = presentedGrantId ? { contextGrantId: presentedGrantId } : {};
108
+ if (page.hasMore && page.nextCursor) {
109
+ plan.push({
110
+ tool: 'ziggs_read_context',
111
+ args: { type, via, cursor: page.nextCursor, ...grant },
112
+ why: 'more rows in this window — next page',
113
+ });
114
+ }
115
+ if (page.latestSequence) {
116
+ plan.push({
117
+ tool: 'ziggs_read_context',
118
+ args: {
119
+ type,
120
+ via,
121
+ direction: 'forward',
122
+ after: page.latestSequence,
123
+ ...grant,
124
+ },
125
+ why: 'forward-delta — only items created after this page',
126
+ });
127
+ }
128
+ return plan;
129
+ }
130
+ function toScopeGrantTag(d) {
131
+ return {
132
+ grantId: d.grantId,
133
+ temporal: d.temporal,
134
+ watermarkAt: d.watermarkAt,
135
+ expiresAt: d.expiresAt,
136
+ parentGrantId: d.parentGrantId,
137
+ };
138
+ }
139
+ /**
140
+ * When several live grants cover the same scope, pick the one the agent should
141
+ * pin: the broadest history first (from-start over from-now, then the earliest
142
+ * watermark), so a read against it returns the most the caller is entitled to.
143
+ */
144
+ function isBroaderGrant(a, b) {
145
+ if (a.temporal !== b.temporal)
146
+ return a.temporal === 'from-start';
147
+ return a.watermarkAt < b.watermarkAt;
148
+ }
149
+ /**
150
+ * ZIG-635: index the caller's own live reach descriptors by scope. The reach
151
+ * list is already the caller's non-expired grants (holderId == principalId),
152
+ * so this is grant metadata the caller already holds — no protected content.
153
+ */
154
+ export function indexReachByScope(reach) {
155
+ const byScope = new Map();
156
+ for (const d of reach) {
157
+ if (!d?.scope)
158
+ continue;
159
+ const key = `${d.scope.kind}:${d.scope.id}`;
160
+ const tag = toScopeGrantTag(d);
161
+ const existing = byScope.get(key);
162
+ if (!existing || isBroaderGrant(tag, existing))
163
+ byScope.set(key, tag);
164
+ }
165
+ return byScope;
166
+ }
167
+ /**
168
+ * ZIG-635: tag each inbox scope with its covering grant. Scopes with no
169
+ * matching live grant (e.g. reachable via membership, not a grant) are left
170
+ * untagged — the agent keeps navigating by id, never a fabricated grant.
171
+ */
172
+ function tagScopesWithGrants(scopes, reach) {
173
+ if (!reach?.length)
174
+ return scopes;
175
+ const byScope = indexReachByScope(reach);
176
+ return scopes.map((s) => {
177
+ const tag = byScope.get(`${s.scope.kind}:${s.scope.id}`);
178
+ return tag ? { ...s, grant: tag } : s;
179
+ });
67
180
  }
68
181
  /**
69
182
  * Put humanAttention first so MCP hosts surface it before counts (ZIG-482),
70
- * and append nextActions last so each inbox call self-narrates the follow-up
71
- * call (ZIG-558) without disturbing the leading humanAttention key.
183
+ * and append readPlan last so each inbox call self-narrates the follow-up
184
+ * calls (ZIG-634) without disturbing the leading humanAttention key.
185
+ *
186
+ * When `reach` (the caller's own live grants) is passed, each scope is tagged
187
+ * with its covering grant (ZIG-635) so the agent can pin X-Context-Grant-Id.
72
188
  */
73
- export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks) {
74
- const nextActions = buildNextActions(inbox);
189
+ export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks, reach) {
190
+ const readPlan = buildReadPlan(inbox);
191
+ const scopes = tagScopesWithGrants(inbox.scopes ?? [], reach);
75
192
  const origin = resolveWebAppOrigin(webOrigin);
76
193
  const pending = formatPendingDecisionsPayload(inbox, origin, { activeTasks });
77
194
  const pendingTail = pending.hasActionable === true
@@ -84,14 +201,14 @@ export function formatInboxToolResult(inbox, ack, webOrigin, activeTasks) {
84
201
  ...(pending.workChatCard ? { workChatCard: pending.workChatCard } : {}),
85
202
  }
86
203
  : {};
87
- const tail = { ...pendingTail, ...(nextActions.length ? { nextActions } : {}) };
204
+ const tail = { ...pendingTail, ...(readPlan.length ? { readPlan } : {}) };
88
205
  const { humanAttention, ...rest } = inbox;
89
206
  const payload = ack
90
- ? { acked: ack.acked, ...rest, ...tail }
91
- : { ...rest, ...tail };
207
+ ? { acked: ack.acked, ...rest, scopes, ...tail }
208
+ : { ...rest, scopes, ...tail };
92
209
  return humanAttention
93
210
  ? { humanAttention, ...payload }
94
211
  : ack
95
212
  ? payload
96
- : { ...inbox, ...tail };
213
+ : { ...inbox, scopes, ...tail };
97
214
  }
package/dist/tools.js CHANGED
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDirectTo, proposeBroadcast, publishOffer, respondToAgreement, ScopeClient, sendChatMessage, ContextDiscoveryClient, ContextReadClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getBackendUrl, } from '@ziggs-ai/api-client';
4
4
  import { decodeOperatorKeyClaims } from './operatorKey.js';
5
5
  import { registerTrustTools } from './trustTools.js';
6
- import { formatInboxToolResult } from './inboxToolResult.js';
6
+ import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
7
7
  import { formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
8
8
  import { PROTOCOL } from './protocol/delegateProtocol.js';
9
9
  import { READ_ONLY, WRITE } from './toolAnnotations.js';
@@ -13,7 +13,9 @@ import { READ_ONLY, WRITE } from './toolAnnotations.js';
13
13
  const ZIGGS_INBOX_DESCRIPTION = "What's new since your last ack — references only, never content: scopes with new-message/artifact counts, plus agreement proposals awaiting your response. " +
14
14
  'For org/agreement scopes each entry includes a `chats` breakdown (chatId + per-chat counts) so you can open the conversations behind the count — read them with ziggs_read_context (type=messages, via=chat:<chatId>). ' +
15
15
  `${PROTOCOL.humanAttention} ${PROTOCOL.pendingDecisions} ` +
16
- 'When hasActionable the response includes sessionChatCard (decisions + active tasks), decisionChatCard, workChatCard, and nextActions. ' +
16
+ 'When hasActionable the response includes sessionChatCard (decisions + active tasks), decisionChatCard, and workChatCard. ' +
17
+ 'A `readPlan` array gives the exact next calls (tool + pre-filled args) for the news in this response — run them verbatim to read each scope and ack (ZIG-634). ' +
18
+ 'Each scope also carries the covering `grant` (grantId, temporal, watermarkAt, expiresAt) so you can pin the right contextGrantId on the read without a separate discover_context call (ZIG-635). ' +
17
19
  `${PROTOCOL.loop} ${PROTOCOL.ack}`;
18
20
  const ZIGGS_PENDING_DECISIONS_DESCRIPTION = 'Session start summary: approve/reject decisions AND active tasks assigned to your delegate (ZIG-625). ' +
19
21
  'Call at session start in Cursor/Claude — pull-only MCP has no notification tray. ' +
@@ -441,7 +443,16 @@ export function registerZiggsTools(server, creds, cfg) {
441
443
  catch {
442
444
  // omit work card when tasks fail
443
445
  }
444
- return textResult(formatInboxToolResult(inbox, acked, cfg.ZIGGS_WEB_URL, activeTasks));
446
+ // ZIG-635: tag each scope with the covering grant from the caller's own
447
+ // live reach descriptors. Best-effort — the inbox still works untagged.
448
+ let reach = [];
449
+ try {
450
+ reach = await new ContextDiscoveryClient(creds.operatorKey, creds.agentId).discover();
451
+ }
452
+ catch {
453
+ // omit grant tags when discovery fails
454
+ }
455
+ return textResult(formatInboxToolResult(inbox, acked, cfg.ZIGGS_WEB_URL, activeTasks, reach));
445
456
  }
446
457
  catch (e) {
447
458
  return toolError(e.message);
@@ -457,7 +468,7 @@ export function registerZiggsTools(server, creds, cfg) {
457
468
  return toolError(e.message);
458
469
  }
459
470
  });
460
- server.tool('ziggs_read_context', 'Read the contents of a scope you already hold: messages | artifacts | agreements | tasks (the type param), under via=chat:<id>, agreement:<id>, or task:<id>. Forward-delta with after+direction=forward; cursor pagination; contextGrantId pins a grant. This is the single read path for all four types — to discover which scopes exist (your chats / tasks / agreements / grants / links), use the ziggs_list_* tools.', {
471
+ server.tool('ziggs_read_context', 'Read the contents of a scope you already hold: messages | artifacts | agreements | tasks (the type param), under via=chat:<id>, agreement:<id>, or task:<id>. Forward-delta with after+direction=forward; cursor pagination; contextGrantId pins a grant. The response carries a `readPlan` with the next page and/or forward-delta call pre-filled (after=this page\'s latestSequence), so you can keep reading without rebuilding args (ZIG-634). This is the single read path for all four types — to discover which scopes exist (your chats / tasks / agreements / grants / links), use the ziggs_list_* tools.', {
461
472
  type: contextReadTypeSchema.describe('Resource type to read'),
462
473
  via: z
463
474
  .string()
@@ -489,7 +500,8 @@ export function registerZiggsTools(server, creds, cfg) {
489
500
  state,
490
501
  contextGrantId,
491
502
  });
492
- return textResult(result);
503
+ const readPlan = buildReadContextReadPlan(result, type, via, contextGrantId);
504
+ return textResult(readPlan.length ? { ...result, readPlan } : result);
493
505
  }
494
506
  catch (e) {
495
507
  return toolError(e.message);
@@ -18,8 +18,8 @@ const DEFAULT_WEB_URL = 'https://ziggsai.com';
18
18
  /** ZIG-433 — agent search + context grant management through MCP. */
19
19
  export function registerTrustTools(server, creds, cfg) {
20
20
  const webUrl = cfg?.ZIGGS_WEB_URL?.replace(/\/$/, '') ?? DEFAULT_WEB_URL;
21
- server.tool('ziggs_search_agents', 'Find agents (AgentSearchClient). A keyword/natural-language query searches published store agents AND, scoped to authority, your own org-mates and any delegate you have an active link with — so you can find a teammate or another user\'s delegate by name and ziggs_open_conversation with it directly, even if it is unpublished/offline and has never been in a chat with you (ZIG-578). Passing an EXACT agent id resolves that one agent even if unpublished/private — use this for a delegate someone shared an id for, then ziggs_request_link if not yet linked (ZIG-480). Use returned agentId in grant/issue tools — do not guess ids.', {
22
- query: z.string().describe('Keyword/natural-language search (published store agents + your org-mates + your linked delegates) OR an exact agent id (resolves that agent even if unpublished)'),
21
+ server.tool('ziggs_search_agents', 'Find agents (AgentSearchClient). A keyword/natural-language query searches the published store AND, scoped to your authority, your own org-mates and any delegate you have an active link with — so you can find a teammate or another user\'s delegate by name and ziggs_open_conversation with it directly, even if it is unpublished/offline and has never been in a chat with you (ZIG-578). Passing an EXACT agent id resolves that one agent even if unpublished/private — use this for a delegate someone shared an id for, then ziggs_request_link if not yet linked (ZIG-480). Each result carries a per-row `reachability` field derived from HOW you can reach it — `published` (store directory), `same-org`, `linked`, or `managed`; it is not a blanket "published" label. If an exact-id lookup matches an unpublished agent you cannot reach, the row is `reachability: "restricted"` and returns the id only with no name/profile (ZIG-638). Use returned agentId in grant/issue tools — do not guess ids.', {
22
+ query: z.string().describe('Keyword/natural-language search (published store + your org-mates + your linked delegates) OR an exact agent id (resolves that agent even if unpublished, when you can reach it)'),
23
23
  limit: z.number().optional().describe('Max results (default server-side)'),
24
24
  minScore: z.number().optional().describe('Minimum match score filter'),
25
25
  }, READ_ONLY, async ({ query, limit, minScore }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.1.19",
3
+ "version": "0.1.22",
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": {