@ziggs-ai/ziggs-mcp 0.15.1 → 0.16.0
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/README.md +5 -1
- package/dist/inboxToolResult.js +6 -5
- package/dist/mcpConnectionTools.d.ts +12 -0
- package/dist/mcpConnectionTools.js +8 -1
- package/dist/operatorKey.d.ts +2 -43
- package/dist/operatorKey.js +2 -56
- package/dist/pendingDecisions.d.ts +1 -7
- package/dist/pendingDecisions.js +2 -17
- package/dist/protocol/delegateProtocol.d.ts +4 -5
- package/dist/protocol/delegateProtocol.js +4 -5
- package/dist/tools.js +19 -20
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @ziggs-ai/ziggs-mcp
|
|
2
2
|
|
|
3
|
-
MCP (
|
|
3
|
+
**Ziggs MCP** connects a brain in an existing MCP application to an agent on Ziggs. It is a peer to the [Ziggs SDK](../agent-sdk/README.md): the platform supplies the body (identity, inbox, wallet and contracts), while your application supplies the brain and its wake schedule. Use the remote endpoint or this stdio package.
|
|
4
|
+
|
|
5
|
+
A connection alone does not wake an unattended brain. Configure your application to check `ziggs_inbox` on a schedule, read and act on its deliveries, then acknowledge them. The packaged [inbox rhythm](./skills/ziggs/references/inbox-rhythm.md) describes that loop.
|
|
6
|
+
|
|
7
|
+
SDK `createMcpAgent` is a separate integration: it gives an SDK agent tools from an external MCP server. You do not need it to use Ziggs MCP.
|
|
4
8
|
|
|
5
9
|
**In scope:** chat, agreements (service and hire, direct or published), scope, context discovery/reads, artifacts, points (`ziggs_payment_balance` — reading what you hold).
|
|
6
10
|
Points move as a consequence of an agreement settling; there is no agent-side tool to move them, and none to approve a movement. A settlement paused above the wallet owner's policy is decided by the human on the wallet page, and a cold `ziggs_inbox` is where an agent sees that one is waiting.
|
package/dist/inboxToolResult.js
CHANGED
|
@@ -130,7 +130,7 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
|
|
|
130
130
|
case 'agreement':
|
|
131
131
|
case 'request':
|
|
132
132
|
// Deliberately no read call. Tasks/proposals arrive as standing state
|
|
133
|
-
// elsewhere on the envelope; requests ride `
|
|
133
|
+
// elsewhere on the envelope; requests ride `openRequestsAwaitingMe`
|
|
134
134
|
// and are host-triaged with a plain string compare — never an LLM read
|
|
135
135
|
// plan entry (that would recreate the per-request token drain).
|
|
136
136
|
break;
|
|
@@ -153,9 +153,10 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
|
|
|
153
153
|
// that would clear deliveries the plan never asked the agent to handle.
|
|
154
154
|
// Reserve the ack slot only when every candidate still fits beside it;
|
|
155
155
|
// otherwise spend the full budget on reads and omit ack.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
// A capped list is the oldest head. Acking it (with the listed ids) is
|
|
157
|
+
// safe — the newer tail is past ackTo. A truncated *plan* still omits
|
|
158
|
+
// ack, because those steps never asked the agent to handle the rest.
|
|
159
|
+
const canAckFully = !!inbox.ackTo && (inbox.truncatedRequests ?? 0) === 0;
|
|
159
160
|
const leaveRoomForAck = canAckFully && candidates.length <= MAX_READ_PLAN - 1;
|
|
160
161
|
const budget = leaveRoomForAck ? MAX_READ_PLAN - 1 : MAX_READ_PLAN;
|
|
161
162
|
const truncated = Math.max(0, candidates.length - budget);
|
|
@@ -170,7 +171,7 @@ export function buildReadPlan(inbox, grantsByScope, self = { agentId: '' }) {
|
|
|
170
171
|
...(inbox.deliveries ?? [])
|
|
171
172
|
.filter((d) => self.agentId === '' || d.assigneeId === self.agentId)
|
|
172
173
|
.map((d) => d.resourceId),
|
|
173
|
-
...(inbox.
|
|
174
|
+
...(inbox.openRequestsAwaitingMe ?? []).map((q) => q.agreementId),
|
|
174
175
|
]),
|
|
175
176
|
].filter((id) => typeof id === 'string' && id.length > 0);
|
|
176
177
|
plan.push({
|
|
@@ -14,6 +14,18 @@ export declare function resolveMcpGatewayTarget(opts: {
|
|
|
14
14
|
operatorKey: string;
|
|
15
15
|
agentId: string;
|
|
16
16
|
baseUrl?: string;
|
|
17
|
+
/**
|
|
18
|
+
* The wake's lane (a chat id, or `agrn-<agreementId>`), sent as
|
|
19
|
+
* `X-Ziggs-Lane`. The broker reads off it who the agent is acting for and
|
|
20
|
+
* checks the grant against the party who gave it.
|
|
21
|
+
*
|
|
22
|
+
* Without it this door was the unfenced one: the named-connector proxy has
|
|
23
|
+
* stated its lane for a while, so the same account and the same grant got
|
|
24
|
+
* two different answers depending on which door the agent used. Omitting it
|
|
25
|
+
* cannot widen anything — a stated lane can only cause a refusal — but it
|
|
26
|
+
* does leave the spend unfenced.
|
|
27
|
+
*/
|
|
28
|
+
laneId?: string;
|
|
17
29
|
}): {
|
|
18
30
|
url: URL;
|
|
19
31
|
headers: Record<string, string>;
|
|
@@ -25,6 +25,7 @@ export function resolveMcpGatewayTarget(opts) {
|
|
|
25
25
|
authorization: `Bearer ${opts.operatorKey}`,
|
|
26
26
|
'x-agent-id': opts.agentId,
|
|
27
27
|
'x-grant-id': opts.grantId,
|
|
28
|
+
...(opts.laneId ? { 'x-ziggs-lane': opts.laneId } : {}),
|
|
28
29
|
},
|
|
29
30
|
};
|
|
30
31
|
}
|
|
@@ -35,7 +36,12 @@ export async function resolveMcpConnectionTarget(creds, connectionId, grantId) {
|
|
|
35
36
|
throw new Error('operatorKey is required');
|
|
36
37
|
if (!creds.agentId)
|
|
37
38
|
throw new Error('agentId is required');
|
|
38
|
-
|
|
39
|
+
// The lane matters here too: since the grant list answers for the party the
|
|
40
|
+
// wake acts for, a lane-less list comes back spanning every customer this
|
|
41
|
+
// agent serves — and this is the path that PICKS one when the caller named
|
|
42
|
+
// no connection. baseUrl was missing as well, so it only ever worked
|
|
43
|
+
// against the default backend.
|
|
44
|
+
const groups = await new ConnectionsClient(creds.operatorKey, creds.agentId, getBackendUrl(), creds.laneId).listForHolder();
|
|
39
45
|
const live = groups.filter((g) => (g.grants ?? []).length > 0);
|
|
40
46
|
if (live.length === 0) {
|
|
41
47
|
throw new Error('No live connection grant. Ask the connection owner to issue one for this agent, then retry.');
|
|
@@ -85,6 +91,7 @@ export async function withMcpGatewayClient(creds, connectionId, grantId, run) {
|
|
|
85
91
|
grantId,
|
|
86
92
|
operatorKey: creds.operatorKey,
|
|
87
93
|
agentId: creds.agentId,
|
|
94
|
+
laneId: creds.laneId,
|
|
88
95
|
});
|
|
89
96
|
return gatewayRunner(target, run);
|
|
90
97
|
}
|
package/dist/operatorKey.d.ts
CHANGED
|
@@ -1,47 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
type?: string;
|
|
4
|
-
keyId?: string;
|
|
5
|
-
ownerId?: string;
|
|
6
|
-
boundAgentId?: string | null;
|
|
7
|
-
/**
|
|
8
|
-
* How the key was minted. The backend stamps `mcp_oauth` on every access
|
|
9
|
-
* token the MCP OAuth consent flow issues, refreshes included; a key minted
|
|
10
|
-
* anywhere else (dashboard, CLI, provisioning) carries nothing here.
|
|
11
|
-
*/
|
|
12
|
-
issuedVia?: string;
|
|
13
|
-
exp?: number;
|
|
14
|
-
}
|
|
1
|
+
export { decodeOperatorKeyClaims, isOperatorKeyExpired, isDirectoryBoarded, ISSUED_VIA_MCP_OAUTH, ISSUED_VIA_DEVICE_CODE } from '@ziggs-ai/api-client';
|
|
2
|
+
export type { OperatorKeyClaims } from '@ziggs-ai/api-client';
|
|
15
3
|
declare const MINT_KEY_HELP: string;
|
|
16
|
-
/** Decode operator JWT payload without verifying signature (boundAgentId). */
|
|
17
|
-
export declare function decodeOperatorKeyClaims(token: string): OperatorKeyClaims | null;
|
|
18
|
-
export declare function isOperatorKeyExpired(claims: OperatorKeyClaims | null): boolean;
|
|
19
|
-
/** The stamp the MCP OAuth authorization-code consent flow puts on its tokens. */
|
|
20
|
-
export declare const ISSUED_VIA_MCP_OAUTH = "mcp_oauth";
|
|
21
|
-
/** The stamp the MCP OAuth device-code flow puts on its tokens. */
|
|
22
|
-
export declare const ISSUED_VIA_DEVICE_CODE = "device_code";
|
|
23
|
-
/**
|
|
24
|
-
* Did a person board this credential through the connector directory?
|
|
25
|
-
*
|
|
26
|
-
* That is the question the tool surface turns on: a connected assistant
|
|
27
|
-
* (Claude, ChatGPT, Cursor) gets the listed, directory-reviewed surface, and
|
|
28
|
-
* anyone who configured this server themselves with an operator key gets the
|
|
29
|
-
* whole thing.
|
|
30
|
-
*
|
|
31
|
-
* Ask it as a predicate, never as `issuedVia === 'mcp_oauth'`. Two consent rails
|
|
32
|
-
* board an assistant through that door — the authorization-code flow and the
|
|
33
|
-
* device-code flow — and both want the same narrowed surface, but the server
|
|
34
|
-
* records them as the different acts they are. An equality test against one
|
|
35
|
-
* value serves the whole catalogue to every credential from the other one.
|
|
36
|
-
*
|
|
37
|
-
* Read off the unverified payload. On the remote endpoint the backend verified
|
|
38
|
-
* the signature before this ran; on stdio the key is the caller's own. Either
|
|
39
|
-
* way the payload is the one the backend signed. And what hangs on the answer
|
|
40
|
-
* is which tools are registered, not what a call may do: every call is still
|
|
41
|
-
* authorized by the backend. Provenance may decide what is OFFERED, never what
|
|
42
|
-
* is PERMITTED.
|
|
43
|
-
*/
|
|
44
|
-
export declare function isDirectoryBoarded(claims: OperatorKeyClaims | null): boolean;
|
|
45
4
|
/**
|
|
46
5
|
* Resolve delegate agent id: agent-scoped key (boundAgentId) wins; else ZIGGS_AGENT_ID.
|
|
47
6
|
*/
|
package/dist/operatorKey.js
CHANGED
|
@@ -1,62 +1,8 @@
|
|
|
1
|
+
import { decodeOperatorKeyClaims, isOperatorKeyExpired } from '@ziggs-ai/api-client';
|
|
2
|
+
export { decodeOperatorKeyClaims, isOperatorKeyExpired, isDirectoryBoarded, ISSUED_VIA_MCP_OAUTH, ISSUED_VIA_DEVICE_CODE } from '@ziggs-ai/api-client';
|
|
1
3
|
const MINT_KEY_HELP = 'Mint a key in the Ziggs app: Developer Portal → Operator keys (fleet key + set ZIGGS_AGENT_ID), ' +
|
|
2
4
|
'or open your delegate agent → Issue operator key (agent-scoped — no ZIGGS_AGENT_ID needed). ' +
|
|
3
5
|
'Docs: https://ziggsai.com/docs (Claude Code MCP tier).';
|
|
4
|
-
/** Decode operator JWT payload without verifying signature (boundAgentId). */
|
|
5
|
-
export function decodeOperatorKeyClaims(token) {
|
|
6
|
-
const trimmed = token.trim();
|
|
7
|
-
const parts = trimmed.split('.');
|
|
8
|
-
if (parts.length !== 3)
|
|
9
|
-
return null;
|
|
10
|
-
try {
|
|
11
|
-
const json = Buffer.from(parts[1], 'base64url').toString('utf8');
|
|
12
|
-
const payload = JSON.parse(json);
|
|
13
|
-
return {
|
|
14
|
-
type: payload.type,
|
|
15
|
-
keyId: payload.keyId,
|
|
16
|
-
ownerId: payload.ownerId,
|
|
17
|
-
boundAgentId: payload.boundAgentId ?? null,
|
|
18
|
-
issuedVia: typeof payload.issuedVia === 'string' ? payload.issuedVia : undefined,
|
|
19
|
-
exp: payload.exp,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
export function isOperatorKeyExpired(claims) {
|
|
27
|
-
if (!claims?.exp)
|
|
28
|
-
return false;
|
|
29
|
-
return claims.exp * 1000 <= Date.now();
|
|
30
|
-
}
|
|
31
|
-
/** The stamp the MCP OAuth authorization-code consent flow puts on its tokens. */
|
|
32
|
-
export const ISSUED_VIA_MCP_OAUTH = 'mcp_oauth';
|
|
33
|
-
/** The stamp the MCP OAuth device-code flow puts on its tokens. */
|
|
34
|
-
export const ISSUED_VIA_DEVICE_CODE = 'device_code';
|
|
35
|
-
/**
|
|
36
|
-
* Did a person board this credential through the connector directory?
|
|
37
|
-
*
|
|
38
|
-
* That is the question the tool surface turns on: a connected assistant
|
|
39
|
-
* (Claude, ChatGPT, Cursor) gets the listed, directory-reviewed surface, and
|
|
40
|
-
* anyone who configured this server themselves with an operator key gets the
|
|
41
|
-
* whole thing.
|
|
42
|
-
*
|
|
43
|
-
* Ask it as a predicate, never as `issuedVia === 'mcp_oauth'`. Two consent rails
|
|
44
|
-
* board an assistant through that door — the authorization-code flow and the
|
|
45
|
-
* device-code flow — and both want the same narrowed surface, but the server
|
|
46
|
-
* records them as the different acts they are. An equality test against one
|
|
47
|
-
* value serves the whole catalogue to every credential from the other one.
|
|
48
|
-
*
|
|
49
|
-
* Read off the unverified payload. On the remote endpoint the backend verified
|
|
50
|
-
* the signature before this ran; on stdio the key is the caller's own. Either
|
|
51
|
-
* way the payload is the one the backend signed. And what hangs on the answer
|
|
52
|
-
* is which tools are registered, not what a call may do: every call is still
|
|
53
|
-
* authorized by the backend. Provenance may decide what is OFFERED, never what
|
|
54
|
-
* is PERMITTED.
|
|
55
|
-
*/
|
|
56
|
-
export function isDirectoryBoarded(claims) {
|
|
57
|
-
return (claims?.issuedVia === ISSUED_VIA_MCP_OAUTH ||
|
|
58
|
-
claims?.issuedVia === ISSUED_VIA_DEVICE_CODE);
|
|
59
|
-
}
|
|
60
6
|
/**
|
|
61
7
|
* Resolve delegate agent id: agent-scoped key (boundAgentId) wins; else ZIGGS_AGENT_ID.
|
|
62
8
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { resolveWebAppOrigin, agreementAppUrl, agreementsListAppUrl, connectionsSettingsAppUrl, walletAppUrl } from '@ziggs-ai/api-client';
|
|
1
2
|
import { type InboxEnvelope, type InboxTaskRef, type Task } from '@ziggs-ai/api-client';
|
|
2
3
|
/**
|
|
3
4
|
* The two ids this delegate answers for, in the order authority is checked:
|
|
@@ -79,13 +80,6 @@ export interface PaymentApprovalItem {
|
|
|
79
80
|
* second tool to go call.
|
|
80
81
|
*/
|
|
81
82
|
export declare const SESSION_CARD_POINTER = "Call ziggs_inbox without waitSeconds and paste its sessionChatCard for the human.";
|
|
82
|
-
export declare function resolveWebAppOrigin(webUrl?: string | null): string;
|
|
83
|
-
export declare function agreementAppUrl(origin: string, agreementId: string): string;
|
|
84
|
-
export declare function agreementsListAppUrl(origin: string): string;
|
|
85
|
-
/** Where the human connects MCP servers and grants tools. */
|
|
86
|
-
export declare function connectionsSettingsAppUrl(origin: string): string;
|
|
87
|
-
/** Where the human decides paused transfers. */
|
|
88
|
-
export declare function walletAppUrl(origin: string): string;
|
|
89
83
|
export declare function buildPendingDecisionItems(inbox: InboxEnvelope, webOrigin: string, self: DecisionSelfIds): PendingDecisionItem[];
|
|
90
84
|
/** shape pending payment approvals for the session payload/card. */
|
|
91
85
|
export declare function buildPaymentApprovalItems(approvals: Array<Record<string, unknown>>, webOrigin: string): PaymentApprovalItem[];
|
package/dist/pendingDecisions.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { agreementAppUrl, agreementsListAppUrl, connectionsSettingsAppUrl, walletAppUrl } from '@ziggs-ai/api-client';
|
|
2
|
+
export { resolveWebAppOrigin, agreementAppUrl, agreementsListAppUrl, connectionsSettingsAppUrl, walletAppUrl } from '@ziggs-ai/api-client';
|
|
1
3
|
import { partySideIds, resolvePendingApprovalPartyId, } from '@ziggs-ai/api-client';
|
|
2
4
|
/** Chat/tool cues when {@link PendingDecisionItem.respondableBy} is `agent`. */
|
|
3
5
|
export function decisionRespondCues(item) {
|
|
@@ -21,23 +23,6 @@ const ACTIVE_TASK_LIMIT = 20;
|
|
|
21
23
|
* second tool to go call.
|
|
22
24
|
*/
|
|
23
25
|
export const SESSION_CARD_POINTER = 'Call ziggs_inbox without waitSeconds and paste its sessionChatCard for the human.';
|
|
24
|
-
export function resolveWebAppOrigin(webUrl) {
|
|
25
|
-
return (webUrl?.trim() || 'https://ziggsai.com').replace(/\/$/, '');
|
|
26
|
-
}
|
|
27
|
-
export function agreementAppUrl(origin, agreementId) {
|
|
28
|
-
return `${origin}/app/agreements/${encodeURIComponent(agreementId)}`;
|
|
29
|
-
}
|
|
30
|
-
export function agreementsListAppUrl(origin) {
|
|
31
|
-
return `${origin}/app/agreements`;
|
|
32
|
-
}
|
|
33
|
-
/** Where the human connects MCP servers and grants tools. */
|
|
34
|
-
export function connectionsSettingsAppUrl(origin) {
|
|
35
|
-
return `${origin}/app/access`;
|
|
36
|
-
}
|
|
37
|
-
/** Where the human decides paused transfers. */
|
|
38
|
-
export function walletAppUrl(origin) {
|
|
39
|
-
return `${origin}/app/settings/organization/billing`;
|
|
40
|
-
}
|
|
41
26
|
function truncateText(text, max = TITLE_MAX) {
|
|
42
27
|
const oneLine = text.replace(/\s+/g, ' ').trim();
|
|
43
28
|
if (oneLine.length <= max)
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The protocol (inbox → read → act → ack, the reporting rule, humanAttention
|
|
5
5
|
* handling, the untrusted-input hard rule) is stated once here and rendered
|
|
6
|
-
* into
|
|
7
|
-
*
|
|
8
|
-
* Hand-copied across those, it drifted.
|
|
6
|
+
* into the connect surfaces: server `instructions`, SKILL.md, the skill
|
|
7
|
+
* references and `.cursorrules`. Tool descriptions do not repeat these
|
|
8
|
+
* paragraphs. Hand-copied across those, it drifted.
|
|
9
9
|
*
|
|
10
10
|
* Static surfaces are generated from these fragments; runtime surfaces import
|
|
11
11
|
* them directly, so neither can drift. A drift check fails the build when a
|
|
@@ -64,8 +64,7 @@ export declare const PROTOCOL: {
|
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
66
|
* Ordered protocol rules for the prose surfaces (server instructions, SKILL,
|
|
67
|
-
* .cursorrules).
|
|
68
|
-
* from the same fragments — see tools.ts.
|
|
67
|
+
* .cursorrules). Tool descriptions stay on their own fields.
|
|
69
68
|
*/
|
|
70
69
|
export declare const PROTOCOL_RULES: readonly string[];
|
|
71
70
|
/** HTML-comment markers delimiting the generated region in a markdown file. */
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The protocol (inbox → read → act → ack, the reporting rule, humanAttention
|
|
5
5
|
* handling, the untrusted-input hard rule) is stated once here and rendered
|
|
6
|
-
* into
|
|
7
|
-
*
|
|
8
|
-
* Hand-copied across those, it drifted.
|
|
6
|
+
* into the connect surfaces: server `instructions`, SKILL.md, the skill
|
|
7
|
+
* references and `.cursorrules`. Tool descriptions do not repeat these
|
|
8
|
+
* paragraphs. Hand-copied across those, it drifted.
|
|
9
9
|
*
|
|
10
10
|
* Static surfaces are generated from these fragments; runtime surfaces import
|
|
11
11
|
* them directly, so neither can drift. A drift check fails the build when a
|
|
@@ -64,8 +64,7 @@ export const PROTOCOL = {
|
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
66
|
* Ordered protocol rules for the prose surfaces (server instructions, SKILL,
|
|
67
|
-
* .cursorrules).
|
|
68
|
-
* from the same fragments — see tools.ts.
|
|
67
|
+
* .cursorrules). Tool descriptions stay on their own fields.
|
|
69
68
|
*/
|
|
70
69
|
export const PROTOCOL_RULES = [
|
|
71
70
|
PROTOCOL.surface,
|
package/dist/tools.js
CHANGED
|
@@ -1,41 +1,33 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { getAgreement, getMyAgreements, listMyChats, delegateAgreement, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess,
|
|
3
|
+
import { getAgreement, getMyAgreements, listMyChats, delegateAgreement, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, updateTaskPlanSteps, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateSession, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, listTasksCapability, marketplaceViewCapability, parseListFields, pickListedRows, } from '@ziggs-ai/api-client';
|
|
4
4
|
import { decodeOperatorKeyClaims } from './operatorKey.js';
|
|
5
5
|
import { registerTrustTools } from './trustTools.js';
|
|
6
6
|
import { registerPaymentTools } from './paymentTools.js';
|
|
7
7
|
import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
|
|
8
8
|
import { agreementAppUrl, filterTasksForDelegate, formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
|
|
9
|
-
import { PROTOCOL } from './protocol/delegateProtocol.js';
|
|
10
9
|
import { resolveMcpConnectionTarget, withMcpGatewayClient, } from './mcpConnectionTools.js';
|
|
11
10
|
import { readOnly, write, destructive } from './toolAnnotations.js';
|
|
12
11
|
import { registerStrictTool } from './strictParams.js';
|
|
13
12
|
import { toolError } from './toolError.js';
|
|
14
13
|
import { registerCapability, registerCapabilities, textResult, } from './capabilityAdapter.js';
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// instructions / .cursorrules.
|
|
14
|
+
// Shared protocol paragraphs live on connect `instructions` only.
|
|
15
|
+
// This description is the tool's own fields and next calls — not PROTOCOL.*.
|
|
18
16
|
const ZIGGS_INBOX_DESCRIPTION = "Where you stand, in one call. What's addressed to you since your last ack — references only, never content: `deliveries` (newest first) with a per-chat `chats` fold, plus assigned open tasks and agreement proposals awaiting your response. " +
|
|
19
17
|
'Open the conversations behind the references with ziggs_context_read (type=messages, via=chat:<chatId>). ' +
|
|
20
|
-
`${PROTOCOL.humanAttention} ${PROTOCOL.pendingDecisions} ` +
|
|
21
18
|
'A cold call (no waitSeconds) is the session-start read: it also carries `session` (who you are acting as, in which org, against which backend), the structured `decisions` and `activeWork` awaiting an answer, and the `sessionChatCard` to paste for the human. Do NOT call ziggs_agreement_respond until they explicitly approve or reject. ' +
|
|
22
19
|
'A long-poll call (waitSeconds) is the working loop and returns news only — the session block is a session-start cost, not a per-poll one. ' +
|
|
23
20
|
'A `readPlan` array gives the exact next calls (tool + pre-filled args) for the news in this response — run them verbatim to read each chat and ack; when the plan overflows or deliveries are capped, `readPlanTruncated` counts dropped reads and the ack step is omitted so a partial plan cannot bury other mail. ' +
|
|
24
|
-
'readPlan reads come pre-pinned with the covering contextGrantId when you hold one, so no separate ziggs_grant_list call is needed.
|
|
25
|
-
`${PROTOCOL.loop} ${PROTOCOL.ack}`;
|
|
26
|
-
// steer the reporting slot at the point of choice. Reporting rule is sourced
|
|
27
|
-
// from the shared const so it can't drift.
|
|
28
|
-
//
|
|
21
|
+
'readPlan reads come pre-pinned with the covering contextGrantId when you hold one, so no separate ziggs_grant_list call is needed.';
|
|
29
22
|
// The requirement is one grant, and saying so is the whole point: this used to
|
|
30
23
|
// promise a cross-org reach test on every send (propose a link, or fail with
|
|
31
24
|
// AGENT_NOT_PUBLISHED), which no longer exists. Reach is decided once, when
|
|
32
25
|
// somebody is admitted to the room; a send only asks whether the sender holds
|
|
33
|
-
// write on it.
|
|
26
|
+
// write on it. Where finished work goes is PROTOCOL.reporting on connect.
|
|
34
27
|
const ZIGGS_SEND_MESSAGE_DESCRIPTION = 'Send a chat message as the acting agent. Pass chatId of a room you write in, or to naming a person you already have a conversation with (that existing pair room — this does not open contact). First contact is still ziggs_chat_open then send. ' +
|
|
35
28
|
'One requirement: an active write grant on this room. ' +
|
|
36
29
|
'Taking part IS the permission, so there is no separate reach check on the send and no auto-add of a receiver who is not already in the room. ' +
|
|
37
|
-
'A refusal means you hold no write grant here: rooms are opened with you (ziggs_chat_open) and holders are admitted under an instrument, and the refusal names which one is missing and how to create it.
|
|
38
|
-
PROTOCOL.reporting;
|
|
30
|
+
'A refusal means you hold no write grant here: rooms are opened with you (ziggs_chat_open) and holders are admitted under an instrument, and the refusal names which one is missing and how to create it.';
|
|
39
31
|
// the strict artifact write (fail loudly, return the artifactId)
|
|
40
32
|
// moved into ArtifactsClient.writeStrict, shared with the SDK's artifact_record.
|
|
41
33
|
// the leak-guard, the connections proxy/request calls, the
|
|
@@ -130,7 +122,7 @@ function buildSessionActions(inbox, reads, creds, cfg) {
|
|
|
130
122
|
*/
|
|
131
123
|
async function loadSessionBinding(creds, cfg) {
|
|
132
124
|
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
133
|
-
const isDelegate =
|
|
125
|
+
const isDelegate = isMcpOAuthDelegateSession(creds);
|
|
134
126
|
let actingOrgId = null;
|
|
135
127
|
let actingOrgName = null;
|
|
136
128
|
let actingOrgKind = null;
|
|
@@ -373,13 +365,21 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
373
365
|
.string()
|
|
374
366
|
.optional()
|
|
375
367
|
.describe('Optional filter: pending, approved, rejected, …'),
|
|
376
|
-
|
|
368
|
+
fields: z
|
|
369
|
+
.array(z.string())
|
|
370
|
+
.optional()
|
|
371
|
+
.describe('Return only these keys on each row. Omit for the full row. Keep agreementId if you will claim or get next.'),
|
|
372
|
+
}, readOnly('List your agreements'), async ({ scope, proposalStatus, fields }) => {
|
|
377
373
|
try {
|
|
378
374
|
const agreements = await getMyAgreements({
|
|
379
375
|
...(proposalStatus ? { proposalStatus } : {}),
|
|
380
376
|
partyOnly: scope !== 'reachable',
|
|
381
377
|
}, creds);
|
|
382
|
-
return textResult({
|
|
378
|
+
return textResult({
|
|
379
|
+
count: agreements.length,
|
|
380
|
+
scope: scope ?? 'mine',
|
|
381
|
+
agreements: pickListedRows(agreements, parseListFields(fields)),
|
|
382
|
+
});
|
|
383
383
|
}
|
|
384
384
|
catch (e) {
|
|
385
385
|
return toolError(e);
|
|
@@ -696,9 +696,8 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
696
696
|
: result;
|
|
697
697
|
},
|
|
698
698
|
});
|
|
699
|
-
// descriptions.mcp is canonical — do not override.
|
|
700
|
-
//
|
|
701
|
-
// ziggs-mcp); record-artifact-teaching.test.ts gates the live tool against both.
|
|
699
|
+
// descriptions.mcp is canonical — do not override. Shared protocol
|
|
700
|
+
// paragraphs stay on connect instructions, not this description.
|
|
702
701
|
registerCapability(server, recordArtifactCapability, creds);
|
|
703
702
|
// find what you recorded free-standing, and hand one artifact to one
|
|
704
703
|
// agent. Descriptions come from the shared capability (no PROTOCOL override
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
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": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
|
-
"@ziggs-ai/api-client": "0.
|
|
42
|
+
"@ziggs-ai/api-client": "0.16.0",
|
|
43
43
|
"dotenv": "^16.6.1",
|
|
44
44
|
"zod": "^3.24.2",
|
|
45
45
|
"zod-to-json-schema": "^3.25.1"
|