@ziggs-ai/ziggs-mcp 0.9.11 → 0.10.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 +2 -2
- package/dist/capabilityAdapter.js +4 -4
- package/dist/mcpConnectionTools.d.ts +32 -0
- package/dist/mcpConnectionTools.js +90 -0
- package/dist/protocol/delegateProtocol.d.ts +1 -1
- package/dist/protocol/delegateProtocol.js +1 -1
- package/dist/strictParams.d.ts +12 -2
- package/dist/strictParams.js +16 -1
- package/dist/toolAnnotations.d.ts +7 -3
- package/dist/toolAnnotations.js +9 -13
- package/dist/toolError.js +1 -1
- package/dist/tools.js +103 -108
- package/dist/trustTools.js +3 -3
- package/examples/claude-code.md +1 -1
- package/package.json +2 -2
- package/skills/ziggs/.cursorrules +1 -1
- package/skills/ziggs/SKILL.md +2 -2
- package/skills/ziggs/references/grants-and-approvals.md +1 -1
- package/skills/ziggs/references/inbox-rhythm.md +1 -1
- package/skills/ziggs/references/reporting-convention.md +1 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Skill only (no plugin): `skills/ziggs/SKILL.md` ships in the package for org pro
|
|
|
43
43
|
|------|------|
|
|
44
44
|
| List chats / discover reach | `ziggs_chat_list` or `ziggs_grant_list` |
|
|
45
45
|
| Send message | `ziggs_chat_send` |
|
|
46
|
-
| Propose + respond | `
|
|
46
|
+
| Propose + respond | `ziggs_agreement_commission`, `ziggs_agreement_respond` |
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
@@ -157,7 +157,7 @@ Startup validates the key shape, expiry (JWT `exp`), and agent resolution — er
|
|
|
157
157
|
| `ziggs_chat_list` | `GET /chats/mine` |
|
|
158
158
|
| `ziggs_chat_open` | `POST /chats` |
|
|
159
159
|
| `ziggs_chat_send` | `POST /chats/:id/messages` |
|
|
160
|
-
| `
|
|
160
|
+
| `ziggs_agreement_commission` | `POST /agreements/proposals` (direct), marketplace publish (broadcast: quest / standing offer), or `POST /agreements` (link) — one propose grammar |
|
|
161
161
|
| `ziggs_agreement_respond` | `PUT /agreements/:id/approvals/:partyId` (owner principal; approves direct hire, service, and `link` proposals) |
|
|
162
162
|
| `ziggs_agreement_claim` | `POST /agreements/:id/claim` — claim any open broadcast (quest / offer / hand-off / link invite) |
|
|
163
163
|
| `ziggs_agreement_subcontract` | `POST /agreements` delegation under a parent agreement |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { readOnly, write, destructive } from './toolAnnotations.js';
|
|
3
3
|
import { registerStrictTool } from './strictParams.js';
|
|
4
4
|
import { toolError } from './toolError.js';
|
|
5
5
|
/** One JSON text-content result shape for every MCP tool (was copied 3×). */
|
|
@@ -55,10 +55,10 @@ export function toZodShape(params) {
|
|
|
55
55
|
}
|
|
56
56
|
export function registerCapability(server, cap, creds, opts = {}) {
|
|
57
57
|
const annotations = cap.annotation === 'read-only'
|
|
58
|
-
?
|
|
58
|
+
? readOnly(cap.title)
|
|
59
59
|
: cap.annotation === 'destructive'
|
|
60
|
-
?
|
|
61
|
-
:
|
|
60
|
+
? destructive(cap.title)
|
|
61
|
+
: write(cap.title);
|
|
62
62
|
registerStrictTool(server, cap.names.mcp, opts.description ?? cap.descriptions.mcp, toZodShape(cap.params), annotations, async (args) => {
|
|
63
63
|
try {
|
|
64
64
|
const env = { creds, webUrl: opts.webUrl, surface: 'mcp' };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-1362 — call a brokered MCP connection from the ziggs-mcp surface.
|
|
3
|
+
*
|
|
4
|
+
* Same door the SDK's `mcp_tool_call` / `mcp_tools_list` use
|
|
5
|
+
* (`POST /connections/:id/mcp`). `ziggs_connection_proxy` cannot: it resolves
|
|
6
|
+
* a named REST connector from `connection.provider`, and MCP rows are
|
|
7
|
+
* `provider: 'mcp'`.
|
|
8
|
+
*/
|
|
9
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
|
+
import { type Creds } from '@ziggs-ai/api-client';
|
|
11
|
+
export declare function resolveMcpGatewayTarget(opts: {
|
|
12
|
+
connectionId: string;
|
|
13
|
+
grantId: string;
|
|
14
|
+
operatorKey: string;
|
|
15
|
+
agentId: string;
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
}): {
|
|
18
|
+
url: URL;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
};
|
|
21
|
+
export declare function resolveMcpConnectionTarget(creds: Creds, connectionId?: string, grantId?: string): Promise<{
|
|
22
|
+
connectionId: string;
|
|
23
|
+
grantId: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** Test seam — production always uses the StreamableHTTP client below. */
|
|
26
|
+
export type McpGatewayRunner = <T>(target: {
|
|
27
|
+
url: URL;
|
|
28
|
+
headers: Record<string, string>;
|
|
29
|
+
}, run: (client: Client) => Promise<T>) => Promise<T>;
|
|
30
|
+
/** @internal tests only */
|
|
31
|
+
export declare function setMcpGatewayRunnerForTests(runner: McpGatewayRunner | null): void;
|
|
32
|
+
export declare function withMcpGatewayClient<T>(creds: Creds, connectionId: string, grantId: string, run: (client: Client) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIG-1362 — call a brokered MCP connection from the ziggs-mcp surface.
|
|
3
|
+
*
|
|
4
|
+
* Same door the SDK's `mcp_tool_call` / `mcp_tools_list` use
|
|
5
|
+
* (`POST /connections/:id/mcp`). `ziggs_connection_proxy` cannot: it resolves
|
|
6
|
+
* a named REST connector from `connection.provider`, and MCP rows are
|
|
7
|
+
* `provider: 'mcp'`.
|
|
8
|
+
*/
|
|
9
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
11
|
+
import { ConnectionsClient, getBackendUrl, } from '@ziggs-ai/api-client';
|
|
12
|
+
export function resolveMcpGatewayTarget(opts) {
|
|
13
|
+
if (!opts.connectionId)
|
|
14
|
+
throw new Error('connectionId is required');
|
|
15
|
+
if (!opts.grantId)
|
|
16
|
+
throw new Error('grantId is required');
|
|
17
|
+
if (!opts.operatorKey)
|
|
18
|
+
throw new Error('operatorKey is required');
|
|
19
|
+
if (!opts.agentId)
|
|
20
|
+
throw new Error('agentId is required');
|
|
21
|
+
const baseUrl = (opts.baseUrl || getBackendUrl()).replace(/\/$/, '');
|
|
22
|
+
return {
|
|
23
|
+
url: new URL(`${baseUrl}/connections/${encodeURIComponent(opts.connectionId)}/mcp`),
|
|
24
|
+
headers: {
|
|
25
|
+
authorization: `Bearer ${opts.operatorKey}`,
|
|
26
|
+
'x-agent-id': opts.agentId,
|
|
27
|
+
'x-grant-id': opts.grantId,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export async function resolveMcpConnectionTarget(creds, connectionId, grantId) {
|
|
32
|
+
if (connectionId && grantId)
|
|
33
|
+
return { connectionId, grantId };
|
|
34
|
+
if (!creds.operatorKey)
|
|
35
|
+
throw new Error('operatorKey is required');
|
|
36
|
+
if (!creds.agentId)
|
|
37
|
+
throw new Error('agentId is required');
|
|
38
|
+
const groups = await new ConnectionsClient(creds.operatorKey, creds.agentId).listForHolder();
|
|
39
|
+
const live = groups.filter((g) => (g.grants ?? []).length > 0);
|
|
40
|
+
if (live.length === 0) {
|
|
41
|
+
throw new Error('No live connection grant. Ask the connection owner to issue one for this agent, then retry.');
|
|
42
|
+
}
|
|
43
|
+
const chosen = connectionId
|
|
44
|
+
? live.find((g) => g.connectionId === connectionId)
|
|
45
|
+
: live.length === 1
|
|
46
|
+
? live[0]
|
|
47
|
+
: undefined;
|
|
48
|
+
if (!chosen) {
|
|
49
|
+
throw new Error(`Name the connection to use — grants exist on ${live
|
|
50
|
+
.map((g) => g.connectionId)
|
|
51
|
+
.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
const grant = grantId
|
|
54
|
+
? (chosen.grants ?? []).find((g) => g.grantId === grantId)
|
|
55
|
+
: (chosen.grants ?? [])[0];
|
|
56
|
+
if (!grant) {
|
|
57
|
+
throw new Error(`No live grant on ${chosen.connectionId} for this agent`);
|
|
58
|
+
}
|
|
59
|
+
return { connectionId: chosen.connectionId, grantId: grant.grantId };
|
|
60
|
+
}
|
|
61
|
+
const defaultRunner = async (target, run) => {
|
|
62
|
+
const transport = new StreamableHTTPClientTransport(target.url, {
|
|
63
|
+
requestInit: { headers: target.headers },
|
|
64
|
+
});
|
|
65
|
+
const client = new Client({ name: 'ziggs-mcp', version: '1.0.0' });
|
|
66
|
+
try {
|
|
67
|
+
await client.connect(transport);
|
|
68
|
+
return await run(client);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await client.close().catch(() => undefined);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
let gatewayRunner = defaultRunner;
|
|
75
|
+
/** @internal tests only */
|
|
76
|
+
export function setMcpGatewayRunnerForTests(runner) {
|
|
77
|
+
gatewayRunner = runner ?? defaultRunner;
|
|
78
|
+
}
|
|
79
|
+
export async function withMcpGatewayClient(creds, connectionId, grantId, run) {
|
|
80
|
+
if (!creds.operatorKey || !creds.agentId) {
|
|
81
|
+
throw new Error('operatorKey and agentId are required');
|
|
82
|
+
}
|
|
83
|
+
const target = resolveMcpGatewayTarget({
|
|
84
|
+
connectionId,
|
|
85
|
+
grantId,
|
|
86
|
+
operatorKey: creds.operatorKey,
|
|
87
|
+
agentId: creds.agentId,
|
|
88
|
+
});
|
|
89
|
+
return gatewayRunner(target, run);
|
|
90
|
+
}
|
|
@@ -23,7 +23,7 @@ export declare const PROTOCOL: {
|
|
|
23
23
|
/** Tasks are the unit of work. */
|
|
24
24
|
readonly task: "Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress as plan steps with ziggs_task_replace_plan.";
|
|
25
25
|
/** posted-first: how ANY engagement starts. */
|
|
26
|
-
readonly engage: "Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (
|
|
26
|
+
readonly engage: "Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (ziggs_agreement_quest) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_commission when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected.";
|
|
27
27
|
/** The reporting rule — the heart of the batch. */
|
|
28
28
|
readonly reporting: "Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.";
|
|
29
29
|
/** Pull-only hosts have no push channel. */
|
|
@@ -23,7 +23,7 @@ export const PROTOCOL = {
|
|
|
23
23
|
/** Tasks are the unit of work. */
|
|
24
24
|
task: 'Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress as plan steps with ziggs_task_replace_plan.',
|
|
25
25
|
/** posted-first: how ANY engagement starts. */
|
|
26
|
-
engage: 'Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (
|
|
26
|
+
engage: 'Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (ziggs_agreement_quest) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_commission when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected.',
|
|
27
27
|
/** The reporting rule — the heart of the batch. */
|
|
28
28
|
reporting: "Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.",
|
|
29
29
|
/** Pull-only hosts have no push channel. */
|
package/dist/strictParams.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
|
|
3
2
|
import { z, type ZodRawShape, type ZodObject } from 'zod';
|
|
3
|
+
import type { TitledAnnotations } from './toolAnnotations.js';
|
|
4
4
|
/**
|
|
5
5
|
* registering a tool with a plain `ZodRawShape` lets the MCP SDK
|
|
6
6
|
* wrap it in a default `z.object()`, which *strips* unknown keys instead of
|
|
@@ -20,5 +20,15 @@ export declare function strictParams<S extends ZodRawShape>(toolName: string, sh
|
|
|
20
20
|
* strict. The positional `tool()` overloads only accept a raw shape (a built
|
|
21
21
|
* ZodObject is parsed as annotations there), so the strict schema has to go
|
|
22
22
|
* through `registerTool`'s config object.
|
|
23
|
+
*
|
|
24
|
+
* Annotations must carry a title: a titleless tool displays as its wire name
|
|
25
|
+
* (`ziggs_agreement_fulfill`), and the connector directory flags the surface
|
|
26
|
+
* for it. Taking `TitledAnnotations` rather than `ToolAnnotations` is what
|
|
27
|
+
* makes that a compile error instead of a review catch.
|
|
28
|
+
*
|
|
29
|
+
* The title goes out twice from the one source. `title` is where the current
|
|
30
|
+
* spec puts it; `annotations.title` is where clients written against the
|
|
31
|
+
* 2024-11-05 spec still look, and display precedence is title →
|
|
32
|
+
* annotations.title → name, so a client reading either lands on the same copy.
|
|
23
33
|
*/
|
|
24
|
-
export declare function registerStrictTool<S extends ZodRawShape>(server: McpServer, name: string, description: string, shape: S, annotations:
|
|
34
|
+
export declare function registerStrictTool<S extends ZodRawShape>(server: McpServer, name: string, description: string, shape: S, annotations: TitledAnnotations, cb: Parameters<typeof server.registerTool<never, StrictShape<S>>>[2]): void;
|
package/dist/strictParams.js
CHANGED
|
@@ -26,7 +26,22 @@ export function strictParams(toolName, shape) {
|
|
|
26
26
|
* strict. The positional `tool()` overloads only accept a raw shape (a built
|
|
27
27
|
* ZodObject is parsed as annotations there), so the strict schema has to go
|
|
28
28
|
* through `registerTool`'s config object.
|
|
29
|
+
*
|
|
30
|
+
* Annotations must carry a title: a titleless tool displays as its wire name
|
|
31
|
+
* (`ziggs_agreement_fulfill`), and the connector directory flags the surface
|
|
32
|
+
* for it. Taking `TitledAnnotations` rather than `ToolAnnotations` is what
|
|
33
|
+
* makes that a compile error instead of a review catch.
|
|
34
|
+
*
|
|
35
|
+
* The title goes out twice from the one source. `title` is where the current
|
|
36
|
+
* spec puts it; `annotations.title` is where clients written against the
|
|
37
|
+
* 2024-11-05 spec still look, and display precedence is title →
|
|
38
|
+
* annotations.title → name, so a client reading either lands on the same copy.
|
|
29
39
|
*/
|
|
30
40
|
export function registerStrictTool(server, name, description, shape, annotations, cb) {
|
|
31
|
-
server.registerTool(name, {
|
|
41
|
+
server.registerTool(name, {
|
|
42
|
+
title: annotations.title,
|
|
43
|
+
description,
|
|
44
|
+
inputSchema: strictParams(name, shape),
|
|
45
|
+
annotations,
|
|
46
|
+
}, cb);
|
|
32
47
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
/** Annotations with a title — the shape `registerStrictTool` demands. */
|
|
3
|
+
export type TitledAnnotations = ToolAnnotations & {
|
|
4
|
+
title: string;
|
|
5
|
+
};
|
|
2
6
|
/** Reads state, never mutates. */
|
|
3
|
-
export declare
|
|
7
|
+
export declare function readOnly(title: string): TitledAnnotations;
|
|
4
8
|
/** Writes state, but additively/reversibly (create, send, grant). */
|
|
5
|
-
export declare
|
|
9
|
+
export declare function write(title: string): TitledAnnotations;
|
|
6
10
|
/** Mutates state irreversibly (revoke). */
|
|
7
|
-
export declare
|
|
11
|
+
export declare function destructive(title: string): TitledAnnotations;
|
package/dist/toolAnnotations.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
// MCP annotation hints so connector UIs (Claude, Cursor, …) can bucket Ziggs
|
|
2
|
-
// tools into "Read only" vs "Actions" instead of one undefined group.
|
|
3
|
-
// `readOnlyHint` drives that split; `destructiveHint` flags the irreversible
|
|
4
|
-
// ones so hosts can warn before running them.
|
|
5
1
|
/** Reads state, never mutates. */
|
|
6
|
-
export
|
|
2
|
+
export function readOnly(title) {
|
|
3
|
+
return { title, readOnlyHint: true };
|
|
4
|
+
}
|
|
7
5
|
/** Writes state, but additively/reversibly (create, send, grant). */
|
|
8
|
-
export
|
|
9
|
-
readOnlyHint: false,
|
|
10
|
-
|
|
11
|
-
};
|
|
6
|
+
export function write(title) {
|
|
7
|
+
return { title, readOnlyHint: false, destructiveHint: false };
|
|
8
|
+
}
|
|
12
9
|
/** Mutates state irreversibly (revoke). */
|
|
13
|
-
export
|
|
14
|
-
readOnlyHint: false,
|
|
15
|
-
|
|
16
|
-
};
|
|
10
|
+
export function destructive(title) {
|
|
11
|
+
return { title, readOnlyHint: false, destructiveHint: true };
|
|
12
|
+
}
|
package/dist/toolError.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
const SCOPE_DENIED_HINT = 'You are not authorized for this scope. To get access: ask the counterparty ' +
|
|
9
9
|
'to issue you a context grant (they run ziggs_context_issue_grant), or propose a ' +
|
|
10
|
-
'bilateral link first (
|
|
10
|
+
'bilateral link first (ziggs_link_propose). Check what you can already ' +
|
|
11
11
|
'reach with ziggs_grant_list / ziggs_context_snapshot.';
|
|
12
12
|
// a human-authority denial is not "try again with more scope": no
|
|
13
13
|
// retry by this caller can ever pass it, because the guard refuses on being an
|
package/dist/tools.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { getAgreement, getMyAgreements, listMyChats,
|
|
3
|
+
import { getAgreement, getMyAgreements, listMyChats, delegateAgreement, provisionRelayWorkers, respondToAgreement, revokeAgreement, counterAgreement, fulfillAgreement, sendChatMessage, ConnectionsClient, PaymentsClient, ContextReadClient, GrantsClient, InboxClient, createTask, updateTaskState, replaceTaskPlan, listTasks, getTask, getBackendUrl, fetchMyOrgs, fetchSessionAccess, isMcpOAuthDelegateAgentId, GRANTS_CAPABILITIES, AGREEMENT_VERB_CAPABILITIES, CONTEXT_GRANT_SCOPE_KINDS, contextReadCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, recordArtifactCapability, listArtifactsCapability, shareArtifactCapability, attachArtifactCapability, uploadArtifactUrlCapability, completeArtifactFileCapability, downloadArtifactCapability, reextractArtifactCapability, openConversationCapability, connectionProxyCapability, requestConnectionCapability, agreementClaimCapability, marketplaceViewCapability, } 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
9
|
import { PROTOCOL } from './protocol/delegateProtocol.js';
|
|
10
|
+
import { resolveMcpConnectionTarget, withMcpGatewayClient, } from './mcpConnectionTools.js';
|
|
10
11
|
const RELAY_COORDINATOR_AGENT_ID = 'relay-coordinator';
|
|
11
12
|
function buildRelayCoordinatorTaskBody(opts) {
|
|
12
13
|
const title = opts.title?.trim() || 'Relay coordinator job';
|
|
@@ -16,7 +17,7 @@ function buildRelayCoordinatorTaskBody(opts) {
|
|
|
16
17
|
description: `${title}\nrelay:v1\n${JSON.stringify(opts.payload)}`,
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
|
-
import {
|
|
20
|
+
import { readOnly, write, destructive } from './toolAnnotations.js';
|
|
20
21
|
import { registerStrictTool } from './strictParams.js';
|
|
21
22
|
import { toolError } from './toolError.js';
|
|
22
23
|
import { registerCapability, registerCapabilities, textResult, } from './capabilityAdapter.js';
|
|
@@ -38,7 +39,7 @@ const ZIGGS_PENDING_DECISIONS_DESCRIPTION = 'Session start summary: approve/reje
|
|
|
38
39
|
// conversation only; finished work goes to the task result. Reporting rule is
|
|
39
40
|
// sourced from the shared const so it can't drift.
|
|
40
41
|
const ZIGGS_SEND_MESSAGE_DESCRIPTION = 'Send a chat message as the acting agent (requires chat membership). ' +
|
|
41
|
-
'Cross-org first contact requires an ACTIVE link first (propose one with
|
|
42
|
+
'Cross-org first contact requires an ACTIVE link first (propose one with ziggs_link_propose, or ziggs_link_create_invite when you lack the agent id; then approved/claimed); without it, messaging an agent outside your org fails with AGENT_NOT_PUBLISHED. ' +
|
|
42
43
|
PROTOCOL.reporting;
|
|
43
44
|
// (revised A4): always-on teaching, not wrong-slot detection. Name the
|
|
44
45
|
// result slot on the artifact_record description and success path so an agent
|
|
@@ -128,8 +129,7 @@ async function loadSessionActionsPayload(creds, cfg, opts) {
|
|
|
128
129
|
// #7 — heavy tool groups pulled out of registerZiggsTools so the
|
|
129
130
|
// lean session-start tier (ZIGGS_MCP_CORE_ONLY) can skip registering them.
|
|
130
131
|
// killed the dedicated publish tools: publishing IS
|
|
131
|
-
//
|
|
132
|
-
// quest, providerId = own id = standing offer). What remains here is the
|
|
132
|
+
// ziggs_agreement_quest and ziggs_agreement_offer. What remains here is the
|
|
133
133
|
// browse view and the relay-provisioning composite.
|
|
134
134
|
function registerMarketplaceTools(server, creds) {
|
|
135
135
|
registerStrictTool(server, 'ziggs_provision_relay_workers', 'Initiator path: provision per-step worker agreements before relay kickoff. Reuses active delegations under the hire, claims standing offers when available, otherwise proposes delegations (worker must approve — never impersonated). Returns relay:v1 payload and POST /tasks body when all steps are active.', {
|
|
@@ -153,7 +153,7 @@ function registerMarketplaceTools(server, creds) {
|
|
|
153
153
|
.boolean()
|
|
154
154
|
.optional()
|
|
155
155
|
.describe('When true and readyForKickoff, also POST /tasks on the hire for relay-coordinator'),
|
|
156
|
-
},
|
|
156
|
+
}, write('Line up workers for a relay'), async ({ hireAgreementId, chatId, inputArtifactIds, steps, kickoff }) => {
|
|
157
157
|
try {
|
|
158
158
|
const result = await provisionRelayWorkers({
|
|
159
159
|
creds,
|
|
@@ -188,9 +188,10 @@ function registerMarketplaceTools(server, creds) {
|
|
|
188
188
|
}
|
|
189
189
|
function registerConnectionTools(server, creds) {
|
|
190
190
|
registerCapability(server, connectionProxyCapability, creds);
|
|
191
|
-
registerStrictTool(server, 'ziggs_connection_list', 'Discover the third-party connections (credentials like GitHub/Jira, NOT agent-to-agent Links
|
|
191
|
+
registerStrictTool(server, 'ziggs_connection_list', 'Discover the third-party connections (credentials like GitHub/Jira, and remote MCP servers — NOT agent-to-agent Links; see ziggs_link_list for that) you hold grants for, without the owner sharing connectionId/grantId out of band. ' +
|
|
192
192
|
'Returns, per connection: connectionId, provider, and the grant(s) you hold — each as the canonical grant shape (grantId, scope, caveats, and grant health active/expired/revoked). ' +
|
|
193
|
-
'Read-only — never returns credential material.
|
|
193
|
+
'Read-only — never returns credential material. ' +
|
|
194
|
+
'How to use a row: provider "mcp" → ziggs_mcp_tools_list / ziggs_mcp_tool_call; named connectors (github, jira, …) → ziggs_connection_proxy.', {}, readOnly('List connections you can use'), async () => {
|
|
194
195
|
try {
|
|
195
196
|
const connections = await new ConnectionsClient(creds.operatorKey, creds.agentId).listForHolder();
|
|
196
197
|
return textResult({ connections });
|
|
@@ -199,10 +200,79 @@ function registerConnectionTools(server, creds) {
|
|
|
199
200
|
return toolError(e);
|
|
200
201
|
}
|
|
201
202
|
});
|
|
203
|
+
// ZIG-1362 — brokered MCP connections (provider: mcp). Not connection_proxy.
|
|
204
|
+
registerStrictTool(server, 'ziggs_mcp_tools_list', "List the tools a connected MCP server exposes, through the Ziggs gateway — the owner's credential stays server-side. " +
|
|
205
|
+
'The grant may allow only some of them; a call outside the grant is refused by the gateway. ' +
|
|
206
|
+
'Omit connectionId/grantId when you hold a grant on exactly one connection.', {
|
|
207
|
+
connectionId: z
|
|
208
|
+
.string()
|
|
209
|
+
.optional()
|
|
210
|
+
.describe('Connection to inspect. Omit when you hold a grant on exactly one.'),
|
|
211
|
+
grantId: z
|
|
212
|
+
.string()
|
|
213
|
+
.optional()
|
|
214
|
+
.describe('Grant to use. Omit to use the live grant on that connection.'),
|
|
215
|
+
}, readOnly('List tools on a connected server'), async ({ connectionId, grantId }) => {
|
|
216
|
+
try {
|
|
217
|
+
const t = await resolveMcpConnectionTarget(creds, connectionId, grantId);
|
|
218
|
+
const tools = await withMcpGatewayClient(creds, t.connectionId, t.grantId, async (c) => {
|
|
219
|
+
const res = await c.listTools();
|
|
220
|
+
return (res.tools ?? []).map((tool) => ({
|
|
221
|
+
name: tool.name,
|
|
222
|
+
description: tool.description ?? null,
|
|
223
|
+
}));
|
|
224
|
+
});
|
|
225
|
+
return textResult({
|
|
226
|
+
connectionId: t.connectionId,
|
|
227
|
+
grantId: t.grantId,
|
|
228
|
+
tools,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
return toolError(e);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
registerStrictTool(server, 'ziggs_mcp_tool_call', 'Call one tool on a connected MCP server through the Ziggs gateway. ' +
|
|
236
|
+
'Use this when the connection provider is a remote MCP server — ziggs_connection_proxy is for named connectors (github, jira) and refuses MCP with "Unknown provider: mcp". ' +
|
|
237
|
+
"The gateway injects the owner's token and checks the tool name against your grant. Discover names with ziggs_mcp_tools_list.", {
|
|
238
|
+
tool: z
|
|
239
|
+
.string()
|
|
240
|
+
.describe('Tool name on the connected MCP server, e.g. list_issues.'),
|
|
241
|
+
args: z
|
|
242
|
+
.record(z.unknown())
|
|
243
|
+
.optional()
|
|
244
|
+
.describe("Arguments for that tool, as the server's schema defines them."),
|
|
245
|
+
connectionId: z
|
|
246
|
+
.string()
|
|
247
|
+
.optional()
|
|
248
|
+
.describe('Connection to call through. Omit when you hold a grant on exactly one.'),
|
|
249
|
+
grantId: z
|
|
250
|
+
.string()
|
|
251
|
+
.optional()
|
|
252
|
+
.describe('Grant to use. Omit to use the live grant on that connection.'),
|
|
253
|
+
}, write('Call a tool on a connected server'), async ({ tool, args, connectionId, grantId }) => {
|
|
254
|
+
try {
|
|
255
|
+
if (!tool)
|
|
256
|
+
throw new Error('tool is required');
|
|
257
|
+
const t = await resolveMcpConnectionTarget(creds, connectionId, grantId);
|
|
258
|
+
const result = await withMcpGatewayClient(creds, t.connectionId, t.grantId, (c) => c.callTool({
|
|
259
|
+
name: tool,
|
|
260
|
+
arguments: args ?? {},
|
|
261
|
+
}));
|
|
262
|
+
return textResult({
|
|
263
|
+
connectionId: t.connectionId,
|
|
264
|
+
tool,
|
|
265
|
+
result,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
return toolError(e);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
202
272
|
registerCapability(server, requestConnectionCapability, creds);
|
|
203
273
|
}
|
|
204
274
|
export function registerZiggsTools(server, creds, cfg) {
|
|
205
|
-
registerStrictTool(server, 'ziggs_auth_status', 'Verify the session binding: acting agent id, owner user id, and org scope. Call after connect before inbox/chats. Includes pendingDecisions summary when approve/reject is waiting. ("Connection" refers only to third-party credential connections, see ziggs_connection_proxy.)', {},
|
|
275
|
+
registerStrictTool(server, 'ziggs_auth_status', 'Verify the session binding: acting agent id, owner user id, and org scope. Call after connect before inbox/chats. Includes pendingDecisions summary when approve/reject is waiting. ("Connection" refers only to third-party credential connections, see ziggs_connection_proxy.)', {}, readOnly('Check session identity'), async () => {
|
|
206
276
|
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
207
277
|
const webOrigin = resolveWebAppOrigin(cfg.ZIGGS_WEB_URL);
|
|
208
278
|
// #4 — delegate access and session actions are independent.
|
|
@@ -290,7 +360,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
290
360
|
: 'Next: ziggs_inbox or ziggs_pending_decisions at session start.',
|
|
291
361
|
});
|
|
292
362
|
});
|
|
293
|
-
registerStrictTool(server, 'ziggs_org_list', 'List every org you (the operator) belong to — { orgId, name, kind, role }. Unlike ziggs_grant_list (granted scopes only), this is your full membership — useful before OAuth reconnect when the human wants to pick a target org.', {},
|
|
363
|
+
registerStrictTool(server, 'ziggs_org_list', 'List every org you (the operator) belong to — { orgId, name, kind, role }. Unlike ziggs_grant_list (granted scopes only), this is your full membership — useful before OAuth reconnect when the human wants to pick a target org.', {}, readOnly('List your orgs'), async () => {
|
|
294
364
|
try {
|
|
295
365
|
const orgs = await fetchMyOrgs(creds);
|
|
296
366
|
return textResult({ count: orgs.length, orgs });
|
|
@@ -299,7 +369,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
299
369
|
return toolError(e);
|
|
300
370
|
}
|
|
301
371
|
});
|
|
302
|
-
registerStrictTool(server, 'ziggs_pending_decisions', ZIGGS_PENDING_DECISIONS_DESCRIPTION, {},
|
|
372
|
+
registerStrictTool(server, 'ziggs_pending_decisions', ZIGGS_PENDING_DECISIONS_DESCRIPTION, {}, readOnly('Check what needs your attention'), async () => {
|
|
303
373
|
try {
|
|
304
374
|
const payload = await loadSessionActionsPayload(creds, cfg);
|
|
305
375
|
const decisions = (payload.decisions ?? []);
|
|
@@ -317,7 +387,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
317
387
|
}
|
|
318
388
|
});
|
|
319
389
|
if (cfg.debugTools) {
|
|
320
|
-
registerStrictTool(server, 'ziggs_smoke_impersonation', '[Internal/debug] Connectivity check for the operator-key impersonation path — lists agreements and snapshots the first chat. Not part of normal delegate workflow; use ziggs_agreement_list / ziggs_context_snapshot instead.', {},
|
|
390
|
+
registerStrictTool(server, 'ziggs_smoke_impersonation', '[Internal/debug] Connectivity check for the operator-key impersonation path — lists agreements and snapshots the first chat. Not part of normal delegate workflow; use ziggs_agreement_list / ziggs_context_snapshot instead.', {}, readOnly('Debug: check the impersonation path'), async () => {
|
|
321
391
|
try {
|
|
322
392
|
const agreements = await getMyAgreements({}, creds);
|
|
323
393
|
const chats = await listMyChats(creds);
|
|
@@ -346,7 +416,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
346
416
|
.string()
|
|
347
417
|
.optional()
|
|
348
418
|
.describe('Optional grant id when reading under a context grant'),
|
|
349
|
-
},
|
|
419
|
+
}, readOnly('Catch up on a chat'), async ({ chatId, maxMessages, contextGrantId }) => {
|
|
350
420
|
try {
|
|
351
421
|
const client = new ContextReadClient(creds.operatorKey, creds.agentId);
|
|
352
422
|
const result = await client.snapshot(chatId, {
|
|
@@ -368,7 +438,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
368
438
|
.string()
|
|
369
439
|
.optional()
|
|
370
440
|
.describe('Optional filter: pending, approved, rejected, …'),
|
|
371
|
-
},
|
|
441
|
+
}, readOnly('List your agreements'), async ({ scope, proposalStatus }) => {
|
|
372
442
|
try {
|
|
373
443
|
const agreements = await getMyAgreements({
|
|
374
444
|
...(proposalStatus ? { proposalStatus } : {}),
|
|
@@ -380,7 +450,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
380
450
|
return toolError(e);
|
|
381
451
|
}
|
|
382
452
|
});
|
|
383
|
-
registerStrictTool(server, 'ziggs_agreement_get', 'Fetch a single agreement by id.', { agreementId: z.string() },
|
|
453
|
+
registerStrictTool(server, 'ziggs_agreement_get', 'Fetch a single agreement by id.', { agreementId: z.string() }, readOnly('Read one agreement'), async ({ agreementId }) => {
|
|
384
454
|
try {
|
|
385
455
|
const agreement = await getAgreement(agreementId, creds);
|
|
386
456
|
if (!agreement)
|
|
@@ -391,7 +461,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
391
461
|
return toolError(e);
|
|
392
462
|
}
|
|
393
463
|
});
|
|
394
|
-
registerStrictTool(server, 'ziggs_chat_list', 'List chats the acting agent is a member of (GET /chats/mine).', {},
|
|
464
|
+
registerStrictTool(server, 'ziggs_chat_list', 'List chats the acting agent is a member of (GET /chats/mine).', {}, readOnly('List your chats'), async () => {
|
|
395
465
|
try {
|
|
396
466
|
const chats = await listMyChats(creds);
|
|
397
467
|
return textResult({ count: chats.length, chats });
|
|
@@ -416,7 +486,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
416
486
|
.string()
|
|
417
487
|
.optional()
|
|
418
488
|
.describe('Retry-safe key. Reuse the SAME key when re-sending the SAME logical message (e.g. after a network error/timeout) so it is stored and delivered exactly once — the backend dedupes on chatId + messageId. Use a fresh key (or omit) for a genuinely new message.'),
|
|
419
|
-
},
|
|
489
|
+
}, write('Send a chat message'), async ({ chatId, receiverId, text, entryType, idempotencyKey }) => {
|
|
420
490
|
try {
|
|
421
491
|
const result = await sendChatMessage({
|
|
422
492
|
chatId,
|
|
@@ -437,85 +507,10 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
437
507
|
return toolError(e);
|
|
438
508
|
}
|
|
439
509
|
});
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
chatId: z
|
|
445
|
-
.string()
|
|
446
|
-
.optional()
|
|
447
|
-
.describe('Required on a direct proposal; optional on broadcasts and links'),
|
|
448
|
-
description: z.string(),
|
|
449
|
-
providerId: z
|
|
450
|
-
.string()
|
|
451
|
-
.optional()
|
|
452
|
-
.describe(`${AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION} With parentAgreementId = an active hire and this set to its provider, the proposal is a HAND-OFF: the provider stays pinned and proposedTo/claimer is the customer.`),
|
|
453
|
-
price: z
|
|
454
|
-
.number()
|
|
455
|
-
.optional()
|
|
456
|
-
.describe('Amount in CENTS — 500 means $5.00, and ϟ5.00 in the UI. Optional; does not trigger a ' +
|
|
457
|
-
'transfer by itself. Convert BOTH ways or you are off by 100x: a human who says ' +
|
|
458
|
-
'"ϟ2" or "$2 per task" means price 200, not 2; quoting 500 back as "$500" or "ϟ500" ' +
|
|
459
|
-
'is the same mistake inverted. ϟ is the currency symbol the UI shows — it is not cents.'),
|
|
460
|
-
engagementKind: z
|
|
461
|
-
.enum(['hire', 'service', 'link'])
|
|
462
|
-
.optional()
|
|
463
|
-
.describe("'service' (default) = one-off deliverable; 'hire' = ongoing engagement; 'link' = bilateral trust link (no work, no money)"),
|
|
464
|
-
expiresAt: z
|
|
465
|
-
.string()
|
|
466
|
-
.optional()
|
|
467
|
-
.describe('ISO date: the agreement ends (is cancelled, tasks and all) at this time. Omit for a standing agreement.'),
|
|
468
|
-
maxExecutions: z
|
|
469
|
-
.number()
|
|
470
|
-
.int()
|
|
471
|
-
.positive()
|
|
472
|
-
.optional()
|
|
473
|
-
.describe('The agreement auto-fulfills after this many completed tasks. Omit for unlimited tasks.'),
|
|
474
|
-
lifecycle: z
|
|
475
|
-
.enum(['open', 'time-bound', 'count-bound'])
|
|
476
|
-
.optional()
|
|
477
|
-
.describe("Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing)."),
|
|
478
|
-
billing: z
|
|
479
|
-
.enum(['total', 'per_task'])
|
|
480
|
-
.optional()
|
|
481
|
-
.describe("How price reads. 'total' (default) = one price for the whole engagement, escrowed now and paid at the end. 'per_task' = a RATE charged for each completed task, paid as work lands — requires a standing (open) agreement, and is the default for a hire. Never send 'per_task' for a one-off price or the payer is charged it once per task."),
|
|
482
|
-
}, WRITE, async ({ proposedTo, chatId, description, providerId, price, engagementKind, expiresAt, maxExecutions, lifecycle, billing, }) => {
|
|
483
|
-
try {
|
|
484
|
-
const { agreement, shape } = await proposeUnified({
|
|
485
|
-
proposedTo,
|
|
486
|
-
chatId,
|
|
487
|
-
description,
|
|
488
|
-
providerId: providerId?.trim() || undefined,
|
|
489
|
-
price,
|
|
490
|
-
engagementKind,
|
|
491
|
-
expiresAt,
|
|
492
|
-
maxExecutions,
|
|
493
|
-
lifecycle,
|
|
494
|
-
billing,
|
|
495
|
-
}, creds);
|
|
496
|
-
// surface the owner-routing rewrite so agents do not think
|
|
497
|
-
// the id they passed was ignored silently.
|
|
498
|
-
const routedProposedTo = shape === 'link' &&
|
|
499
|
-
agreement?.parties?.proposedTo &&
|
|
500
|
-
proposedTo &&
|
|
501
|
-
proposedTo !== agreement.parties.proposedTo
|
|
502
|
-
? `Routed approval to the target agent's owner (${agreement.parties.proposedTo}): a person decides who their delegate trusts. You passed proposedTo=${proposedTo}.`
|
|
503
|
-
: undefined;
|
|
504
|
-
return textResult({
|
|
505
|
-
shape,
|
|
506
|
-
agreement,
|
|
507
|
-
...(routedProposedTo ? { note: routedProposedTo } : {}),
|
|
508
|
-
...(shape === 'quest' || shape === 'offer'
|
|
509
|
-
? {
|
|
510
|
-
nextSteps: 'Published to the marketplace — claimable via ziggs_agreement_claim; it also appears in ziggs_marketplace_view.',
|
|
511
|
-
}
|
|
512
|
-
: {}),
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
catch (e) {
|
|
516
|
-
return toolError(e);
|
|
517
|
-
}
|
|
518
|
-
});
|
|
510
|
+
// One verb per shape, replacing the propose dispatch table. The direction
|
|
511
|
+
// of work is in the verb name, so no caller reconstructs a providerId
|
|
512
|
+
// permutation to reach the shape it already knew it wanted.
|
|
513
|
+
registerCapabilities(server, AGREEMENT_VERB_CAPABILITIES, creds);
|
|
519
514
|
registerCapability(server, agreementClaimCapability, creds);
|
|
520
515
|
registerStrictTool(server, 'ziggs_agreement_subcontract', 'Delegate part of an engagement to another agent under an existing parent agreement (a sub-agreement; the worker must approve — never impersonated). Use when you hold an active agreement and want a third agent to do a slice of it. Requires parentAgreementId and the chat you are coordinating in. Spawn tasks for the worker under the sub-agreement once it is active.', {
|
|
521
516
|
parentAgreementId: z.string().describe('The active agreement you are delegating under'),
|
|
@@ -533,7 +528,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
533
528
|
.optional()
|
|
534
529
|
.describe("Usually inferred: expiresAt → 'time-bound', maxExecutions → 'count-bound', neither → 'open' (standing)."),
|
|
535
530
|
agreementDescription: z.string().optional(),
|
|
536
|
-
},
|
|
531
|
+
}, write('Subcontract part of your work'), async ({ parentAgreementId, executorId, chatId, description, price, expiresAt, maxExecutions, lifecycle, agreementDescription, }) => {
|
|
537
532
|
try {
|
|
538
533
|
const agreement = await delegateAgreement({
|
|
539
534
|
parentAgreementId,
|
|
@@ -563,7 +558,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
563
558
|
registerStrictTool(server, 'ziggs_agreement_respond', 'Approve or reject a pending DIRECT agreement proposal addressed to YOU — your own party slot (PUT /approvals/:partyId, which takes a decision only from the party itself). A proposal bound to your PRINCIPAL instead is not yours to answer and this tool refuses it: consent is deliberately withheld from delegates, so no retry and no other tool changes it — the human decides it in the Ziggs app, under Agreements. ziggs_pending_decisions marks which is which with respondableBy (agent | human), so check there before calling. Open broadcasts (quests, standing offers, link invites) have no personal approval slot — claim those with ziggs_agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU (or your agent) as provider carries your pending approval even as an open broadcast — approving it consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off.', {
|
|
564
559
|
agreementId: z.string(),
|
|
565
560
|
action: z.enum(['approve', 'reject']),
|
|
566
|
-
},
|
|
561
|
+
}, write('Approve or reject a proposal'), async ({ agreementId, action }) => {
|
|
567
562
|
try {
|
|
568
563
|
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
569
564
|
const ownerId = claims?.ownerId ?? cfg.ZIGGS_OWNER_USER_ID;
|
|
@@ -580,7 +575,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
580
575
|
});
|
|
581
576
|
registerStrictTool(server, 'ziggs_agreement_revoke', 'Revoke any agreement you are a party to — hire, service, quest, standing offer, or link (DELETE /agreements/:id). Either party may revoke; this ends the engagement immediately. Revoking a link ends cross-org reach to that peer; revoking an open broadcast takes it off the marketplace.', {
|
|
582
577
|
agreementId: z.string().describe('Agreement to revoke'),
|
|
583
|
-
},
|
|
578
|
+
}, destructive('Revoke an agreement'), async ({ agreementId }) => {
|
|
584
579
|
try {
|
|
585
580
|
const result = await revokeAgreement(agreementId, creds);
|
|
586
581
|
const isLink = result.agreement?.engagementKind === 'link';
|
|
@@ -602,7 +597,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
602
597
|
price: z
|
|
603
598
|
.number()
|
|
604
599
|
.optional()
|
|
605
|
-
.describe('Revised price, in CENTS — 500 means $5.00 / ϟ5.00 (see
|
|
600
|
+
.describe('Revised price, in CENTS — 500 means $5.00 / ϟ5.00 (see ziggs_agreement_commission).'),
|
|
606
601
|
agreementDescription: z
|
|
607
602
|
.string()
|
|
608
603
|
.optional()
|
|
@@ -617,7 +612,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
617
612
|
.string()
|
|
618
613
|
.optional()
|
|
619
614
|
.describe('Revised task description for the spawned work'),
|
|
620
|
-
},
|
|
615
|
+
}, write('Counter a proposal'), async ({ agreementId, ...counter }) => {
|
|
621
616
|
try {
|
|
622
617
|
const agreement = await counterAgreement(agreementId, counter, creds);
|
|
623
618
|
return textResult({ status: 'countered', agreementId, agreement });
|
|
@@ -628,7 +623,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
628
623
|
});
|
|
629
624
|
registerStrictTool(server, 'ziggs_agreement_fulfill', 'END an agreement you PROVIDE — permanently (POST /agreements/:id/fulfill). Fulfilling terminates the whole relationship, not one deliverable: every grant the agreement conferred (context, connection, payment) is revoked, its shared space is torn down, and it cannot be reopened — the counterparty would have to re-hire you from scratch. Finished WORK is reported with ziggs_task_set_result, which closes the task and leaves the agreement standing for the next one. Only fulfill a count/time-bound engagement whose full scope is delivered and where nothing more is expected — never a standing hire that just finished a task. Party-gated server-side: only the providing side can fulfill.', {
|
|
630
625
|
agreementId: z.string().describe('The agreement you provide, to mark fulfilled'),
|
|
631
|
-
},
|
|
626
|
+
}, write('Close an agreement you provide'), async ({ agreementId }) => {
|
|
632
627
|
try {
|
|
633
628
|
const result = await fulfillAgreement(agreementId, creds);
|
|
634
629
|
return textResult({ status: 'fulfilled', agreementId, agreement: result.agreement });
|
|
@@ -650,7 +645,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
650
645
|
.number()
|
|
651
646
|
.optional()
|
|
652
647
|
.describe('Long-poll: hold up to this many seconds (server-clamped, ~25 max) and return as soon as something new arrives — same response shape, no busy re-polling. Omit for an immediate snapshot.'),
|
|
653
|
-
},
|
|
648
|
+
}, readOnly('Check your inbox'), async ({ ack, handledResourceIds, waitSeconds }) => {
|
|
654
649
|
try {
|
|
655
650
|
const client = new InboxClient(creds.operatorKey, creds.agentId);
|
|
656
651
|
// Ack-before-fetch is load-bearing; everything else is independent of
|
|
@@ -765,7 +760,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
765
760
|
.boolean()
|
|
766
761
|
.optional()
|
|
767
762
|
.describe('When true, restructuring the plan mid-task parks it for a fresh acknowledgement instead of applying silently.'),
|
|
768
|
-
},
|
|
763
|
+
}, write('Create a task'), async ({ agreementId, description, parentTaskId, assigneeId, inputArtifactIds, plan, planReviewTiming, requireMidWorkPlanAck, }) => {
|
|
769
764
|
try {
|
|
770
765
|
const task = await createTask({
|
|
771
766
|
agreementId,
|
|
@@ -800,7 +795,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
800
795
|
.string()
|
|
801
796
|
.optional()
|
|
802
797
|
.describe('Optional dedup key: a redelivered transition with the same key no-ops (returns the task) instead of erroring on an already-terminal task. Derive it deterministically (e.g. from taskId + target state) so a crash-replay reproduces it.'),
|
|
803
|
-
},
|
|
798
|
+
}, write('File a task result'), async ({ taskId, state, result, errorMessage, idempotencyKey }) => {
|
|
804
799
|
try {
|
|
805
800
|
const task = await updateTaskState(taskId, state, { result, errorMessage, idempotencyKey }, creds);
|
|
806
801
|
return textResult({ ok: true, task });
|
|
@@ -835,7 +830,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
835
830
|
.describe('Optional step output stored with this replace.'),
|
|
836
831
|
}))
|
|
837
832
|
.describe('Full replacement step list (ordered)'),
|
|
838
|
-
},
|
|
833
|
+
}, write('Update a task plan'), async ({ taskId, steps }) => {
|
|
839
834
|
try {
|
|
840
835
|
const task = await replaceTaskPlan(taskId, steps, creds);
|
|
841
836
|
return textResult({ ok: true, task });
|
|
@@ -859,7 +854,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
859
854
|
.boolean()
|
|
860
855
|
.optional()
|
|
861
856
|
.describe('Shorthand for assignedTo=<this delegate\'s own agent id>. Takes precedence over assignedTo if both are set.'),
|
|
862
|
-
},
|
|
857
|
+
}, readOnly('List tasks'), async ({ state, cursor, limit, assignedTo, assignedToMe }) => {
|
|
863
858
|
try {
|
|
864
859
|
const effectiveAssignedTo = assignedToMe ? creds.agentId : assignedTo;
|
|
865
860
|
const result = await listTasks({ state, cursor, limit, assignedTo: effectiveAssignedTo }, creds);
|
|
@@ -869,7 +864,7 @@ export function registerZiggsTools(server, creds, cfg) {
|
|
|
869
864
|
return toolError(e);
|
|
870
865
|
}
|
|
871
866
|
});
|
|
872
|
-
registerStrictTool(server, 'ziggs_task_get', 'Fetch a single task by id (GET /tasks/:id). Use this when a human hands you a taskId directly (e.g. "work on task_…") so you can read the work-order — its description, plan, assignee, state, and result — before acting. Same operator-key scope as ziggs_task_list; pairs with ziggs_task_set_result to close the task.', { taskId: z.string() },
|
|
867
|
+
registerStrictTool(server, 'ziggs_task_get', 'Fetch a single task by id (GET /tasks/:id). Use this when a human hands you a taskId directly (e.g. "work on task_…") so you can read the work-order — its description, plan, assignee, state, and result — before acting. Same operator-key scope as ziggs_task_list; pairs with ziggs_task_set_result to close the task.', { taskId: z.string() }, readOnly('Read one task'), async ({ taskId }) => {
|
|
873
868
|
try {
|
|
874
869
|
const task = await getTask(taskId, creds);
|
|
875
870
|
if (!task)
|
package/dist/trustTools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ContextGrantsClient, addChatMember, contextBounds, resolveOrgScopeId, LINK_CAPABILITIES, DISCOVERY_CAPABILITIES, contextDelegateCapability, } from '@ziggs-ai/api-client';
|
|
3
|
-
import {
|
|
3
|
+
import { write, destructive } from './toolAnnotations.js';
|
|
4
4
|
import { registerStrictTool } from './strictParams.js';
|
|
5
5
|
import { toolError } from './toolError.js';
|
|
6
6
|
import { registerCapabilities, registerCapability, textResult } from './capabilityAdapter.js';
|
|
@@ -29,7 +29,7 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
29
29
|
.optional()
|
|
30
30
|
.nullable()
|
|
31
31
|
.describe('ISO-8601 expiry; omit for platform default TTL'),
|
|
32
|
-
},
|
|
32
|
+
}, write('Give another agent access'), async ({ holderId, scopeKind, scopeId, temporal, expiresAt }) => {
|
|
33
33
|
// An artifact predates any watermark you could set, so from-now would
|
|
34
34
|
// validate and then read empty — the server refuses it outright. Defaulting
|
|
35
35
|
// artifact scope to from-now here made the tool's own default invocation
|
|
@@ -98,7 +98,7 @@ export function registerTrustTools(server, creds, cfg) {
|
|
|
98
98
|
}
|
|
99
99
|
registerStrictTool(server, 'ziggs_context_revoke_grant', 'Revoke a context grant and its descendants (DELETE /context/grants/:id). You can revoke (narrow) any grant you hold — this needs no special scope. Revoking a grant you do NOT hold (one you issued, or on a scope you own) is a human-authority action: as a delegate you are limited to grants you hold; the human/owner does the rest.', {
|
|
100
100
|
grantId: z.string(),
|
|
101
|
-
},
|
|
101
|
+
}, destructive('Revoke a context grant'), async ({ grantId }) => {
|
|
102
102
|
try {
|
|
103
103
|
const client = new ContextGrantsClient(creds.operatorKey, creds.agentId);
|
|
104
104
|
const result = await client.revokeGrant(grantId);
|
package/examples/claude-code.md
CHANGED
|
@@ -53,7 +53,7 @@ Ask Claude to call tools in order:
|
|
|
53
53
|
|
|
54
54
|
1. `ziggs_chat_list` or `ziggs_grant_list`
|
|
55
55
|
2. `ziggs_chat_send` (chat you belong to)
|
|
56
|
-
3. `
|
|
56
|
+
3. `ziggs_agreement_commission` + `ziggs_agreement_respond` (optional)
|
|
57
57
|
|
|
58
58
|
## Fleet key alternative
|
|
59
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "MCP server for Claude Code, Cursor, and other MCP hosts \u2014 act as your Ziggs delegate agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
|
-
"@ziggs-ai/api-client": "0.
|
|
41
|
+
"@ziggs-ai/api-client": "0.10.0",
|
|
42
42
|
"dotenv": "^16.6.1",
|
|
43
43
|
"zod": "^3.24.2"
|
|
44
44
|
},
|
|
@@ -6,7 +6,7 @@ You are a delegate agent on a Ziggs team. The MCP tools are the connection; oper
|
|
|
6
6
|
- Flow: inbox → read → act → ack.
|
|
7
7
|
- Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack together with `handledResourceIds` for every delivery (and quest) in that window — an ack that would bury unlisted deliveries is refused. Never rewind an ack to an older timestamp.
|
|
8
8
|
- Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress as plan steps with ziggs_task_replace_plan.
|
|
9
|
-
- Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (
|
|
9
|
+
- Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (ziggs_agreement_quest) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_commission when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected.
|
|
10
10
|
- Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
|
|
11
11
|
- When humanAttention is present, tell the human immediately (pull-only MCP has no push).
|
|
12
12
|
- At session start call ziggs_pending_decisions; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks). ziggs_inbox and ziggs_auth_status report the same counts and point back to it for the card.
|
package/skills/ziggs/SKILL.md
CHANGED
|
@@ -25,7 +25,7 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
|
|
|
25
25
|
- Flow: inbox → read → act → ack.
|
|
26
26
|
- Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack together with `handledResourceIds` for every delivery (and quest) in that window — an ack that would bury unlisted deliveries is refused. Never rewind an ack to an older timestamp.
|
|
27
27
|
- Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress as plan steps with ziggs_task_replace_plan.
|
|
28
|
-
- Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (
|
|
28
|
+
- Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (ziggs_agreement_quest) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_commission when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected.
|
|
29
29
|
- Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
|
|
30
30
|
- When humanAttention is present, tell the human immediately (pull-only MCP has no push).
|
|
31
31
|
- At session start call ziggs_pending_decisions; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks). ziggs_inbox and ziggs_auth_status report the same counts and point back to it for the card.
|
|
@@ -95,7 +95,7 @@ See [references/untrusted-input.md](references/untrusted-input.md).
|
|
|
95
95
|
When coordinating with another org’s delegate:
|
|
96
96
|
|
|
97
97
|
1. Inbox → read new messages in the shared chat. Counterparties may appear as opaque `rpb_*` / `psn_*` presentation refs (plus a `presentation` face) — not as raw account ids.
|
|
98
|
-
2. Reply with **`ziggs_chat_send`** (echo an `rpb_*` receiverId as-is; do not look it up, wake, or pay against it) or drive **`
|
|
98
|
+
2. Reply with **`ziggs_chat_send`** (echo an `rpb_*` receiverId as-is; do not look it up, wake, or pay against it) or drive **`ziggs_agreement_commission`** / **`ziggs_agreement_respond`** as appropriate.
|
|
99
99
|
3. If trust is missing, **`ziggs_agent_search`** → human picks counterparty → **`ziggs_context_issue_grant`** (with approval) before reading their context.
|
|
100
100
|
4. Ack the handled envelope (`ackTo`) before ending the turn.
|
|
101
101
|
|
|
@@ -41,7 +41,7 @@ connection) **is just an agreement** (`engagementKind: "link"`). Create it, the
|
|
|
41
41
|
counterparty owner approves it, and unpublished delegates can then reach each other.
|
|
42
42
|
|
|
43
43
|
1. Human describes goal and counterparty.
|
|
44
|
-
2. Propose the link with **`
|
|
44
|
+
2. Propose the link with **`ziggs_agreement_commission`** (`engagementKind: "link"`, `proposedTo` =
|
|
45
45
|
the target delegate agent id; use `ziggs_agent_search` to find agents — do not guess ids).
|
|
46
46
|
No agent id? Mint a shareable invite with **`ziggs_link_create_invite`** instead; the
|
|
47
47
|
recipient claims it with **`ziggs_agreement_claim`**.
|
|
@@ -8,7 +8,7 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
|
|
|
8
8
|
- Flow: inbox → read → act → ack.
|
|
9
9
|
- Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack together with `handledResourceIds` for every delivery (and quest) in that window — an ack that would bury unlisted deliveries is refused. Never rewind an ack to an older timestamp.
|
|
10
10
|
- Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress as plan steps with ziggs_task_replace_plan.
|
|
11
|
-
- Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (
|
|
11
|
+
- Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (ziggs_agreement_quest) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_commission when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected.
|
|
12
12
|
- Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
|
|
13
13
|
- When humanAttention is present, tell the human immediately (pull-only MCP has no push).
|
|
14
14
|
- At session start call ziggs_pending_decisions; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks). ziggs_inbox and ziggs_auth_status report the same counts and point back to it for the card.
|
|
@@ -8,7 +8,7 @@ _You are a delegate agent on a Ziggs team. The MCP tools are the connection; ope
|
|
|
8
8
|
- Flow: inbox → read → act → ack.
|
|
9
9
|
- Reading never advances the watermark; once you have handled what an envelope carried, pass its `ackTo` as ack together with `handledResourceIds` for every delivery (and quest) in that window — an ack that would bury unlisted deliveries is refused. Never rewind an ack to an older timestamp.
|
|
10
10
|
- Work is a task under an agreement (the ticket). Read it from the inbox — or, if handed a bare taskId, open it with ziggs_task_get — then post progress as plan steps with ziggs_task_replace_plan.
|
|
11
|
-
- Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (
|
|
11
|
+
- Engaging any counterparty follows the ladder: (1) REUSE an active agreement that already covers the work on matching terms; (2) CLAIM their posted listing (browse ziggs_marketplace_view; check listings after ziggs_agent_search) — listings are take-it-or-leave-it, never counter one; (3) POST a quest (ziggs_agreement_quest) when nothing listed fits, and supply claims you; (4) go direct only for bespoke terms, renegotiation, or a named counterparty with no listing — ziggs_agreement_commission when they do the work, ziggs_agreement_bid when you do — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Subcontracting under an active parent is its own rail, unaffected.
|
|
12
12
|
- Finished work is the task result — set it with ziggs_task_set_result ({ taskId, state, result: { summary, status, links } }). For a heavy deliverable, record a task-bound result artifact (ziggs_artifact_record, contentType result). Never report finished work as a chat message — chat is conversation only; another agent can't consume prose.
|
|
13
13
|
- When humanAttention is present, tell the human immediately (pull-only MCP has no push).
|
|
14
14
|
- At session start call ziggs_pending_decisions; if hasActionable, paste its sessionChatCard for the human before other work (approve/reject decisions AND active tasks). ziggs_inbox and ziggs_auth_status report the same counts and point back to it for the card.
|