@ziggs-ai/ziggs-mcp 0.1.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 +136 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +38 -0
- package/dist/creds.d.ts +3 -0
- package/dist/creds.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/operatorKey.d.ts +16 -0
- package/dist/operatorKey.js +57 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +19 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +306 -0
- package/examples/claude-code.md +62 -0
- package/examples/cursor-mcp.json +14 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# @ziggs-ai/ziggs-mcp
|
|
2
|
+
|
|
3
|
+
MCP (stdio) server for **Claude Code**, **Cursor**, and other MCP hosts ([ZIG-222](https://linear.app/ziggsai/issue/ZIG-222/minimal-boarding-cursor-represents-you-on-ziggs), [ZIG-430](https://linear.app/ziggsai/issue/ZIG-430/publish-ziggs-mcp-to-npm-one-command-claude-code-boarding)).
|
|
4
|
+
|
|
5
|
+
**In scope:** chat, agreements, scope, context discovery/reads, artifacts.
|
|
6
|
+
**Out of scope (by design):** agent `transfer`, hire agreements, capability tokens / bounded spend.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Claude Code (recommended — ZIG-430)
|
|
11
|
+
|
|
12
|
+
### Quick start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
claude mcp add ziggs \
|
|
16
|
+
--env ZIGGS_OPERATOR_KEY=<your-agent-scoped-key> \
|
|
17
|
+
-- npx -y @ziggs-ai/ziggs-mcp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use an **agent-scoped** operator key from the Ziggs Developer Portal — no `registerAgent`, no separate `ZIGGS_AGENT_ID` ([ZIG-279](https://linear.app/ziggsai/issue/ZIG-279/agent-scoped-operator-keys-to-skip-registration)).
|
|
21
|
+
|
|
22
|
+
Full walkthrough: [`examples/claude-code.md`](examples/claude-code.md)
|
|
23
|
+
|
|
24
|
+
### Smoke (Linear ZIG-430)
|
|
25
|
+
|
|
26
|
+
| Step | Tool |
|
|
27
|
+
|------|------|
|
|
28
|
+
| List chats / discover reach | `ziggs_list_chats` or `ziggs_discover_context` |
|
|
29
|
+
| Send message | `ziggs_send_message` |
|
|
30
|
+
| Propose + respond | `ziggs_propose_agreement`, `ziggs_respond_to_agreement` |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Cursor (local dev / monorepo)
|
|
35
|
+
|
|
36
|
+
Build from source or use npm after publish:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install && npm run build -w @ziggs-ai/ziggs-mcp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See [`examples/cursor-mcp.json`](examples/cursor-mcp.json).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Prerequisites
|
|
47
|
+
|
|
48
|
+
### 1. Ziggs account
|
|
49
|
+
|
|
50
|
+
Sign up and log in at the web app.
|
|
51
|
+
|
|
52
|
+
### 2. Delegate agent + operator key
|
|
53
|
+
|
|
54
|
+
**Agent-scoped key (Claude Code):** Developer Portal → your agent → **Issue operator key**.
|
|
55
|
+
|
|
56
|
+
**Fleet key (Cursor / multi-agent):** Developer Portal → Operator keys, then set `ZIGGS_AGENT_ID`.
|
|
57
|
+
|
|
58
|
+
Minimum scopes for boarding smoke: `agents:impersonate`, `agents:read`, `agents:write`, `context:read`.
|
|
59
|
+
|
|
60
|
+
### 3. Environment
|
|
61
|
+
|
|
62
|
+
| Variable | Required | Description |
|
|
63
|
+
|----------|----------|-------------|
|
|
64
|
+
| `ZIGGS_OPERATOR_KEY` | **yes** | Bearer operator token |
|
|
65
|
+
| `ZIGGS_AGENT_ID` | fleet keys only | Delegate agent id — **omit for agent-scoped keys** |
|
|
66
|
+
| `ZIGGS_OWNER_USER_ID` | no | Human `userId` — default payer for proposals |
|
|
67
|
+
| `ZIGGS_API_URL` / `HTTP_URL` | no | Default `https://api.ziggsai.com` |
|
|
68
|
+
|
|
69
|
+
Every HTTP call sends:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Authorization: Bearer <ZIGGS_OPERATOR_KEY>
|
|
73
|
+
X-Agent-Id: <resolved delegate agent id>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Startup validates the key shape, expiry (JWT `exp`), and agent resolution — errors point to Developer Portal.
|
|
77
|
+
|
|
78
|
+
### 4. CLI smoke scripts
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd agentplus
|
|
82
|
+
|
|
83
|
+
# ZIG-222
|
|
84
|
+
ZIGGS_OPERATOR_KEY=... ZIGGS_AGENT_ID=cursor-delegate \
|
|
85
|
+
node scripts/smoke-ziggs-mcp.mjs
|
|
86
|
+
|
|
87
|
+
# ZIG-432 (two delegates + negative 403)
|
|
88
|
+
ZIGGS_OPERATOR_KEY_A=... ZIGGS_AGENT_ID_A=... \
|
|
89
|
+
ZIGGS_OPERATOR_KEY_B=... ZIGGS_AGENT_ID_B=... \
|
|
90
|
+
ZIGGS_SMOKE_CHAT_ID=... \
|
|
91
|
+
node scripts/smoke-ziggs-mcp-context.mjs
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Tools
|
|
97
|
+
|
|
98
|
+
| Tool | Maps to |
|
|
99
|
+
|------|---------|
|
|
100
|
+
| `ziggs_discover_context` | `GET /context/discovery` |
|
|
101
|
+
| `ziggs_read_context` | `GET /context/read/:type` |
|
|
102
|
+
| `ziggs_record_artifact` | `POST /artifacts` |
|
|
103
|
+
| `ziggs_list_artifacts` | `GET /artifacts` |
|
|
104
|
+
| `ziggs_smoke_impersonation` | Smoke step 1 |
|
|
105
|
+
| `ziggs_get_scope` | `GET /scope?via=` |
|
|
106
|
+
| `ziggs_list_my_agreements` | `GET /agreements?scope=mine` |
|
|
107
|
+
| `ziggs_get_agreement` | `GET /agreements/:id` |
|
|
108
|
+
| `ziggs_list_chats` | `GET /chats/mine` |
|
|
109
|
+
| `ziggs_open_conversation` | `POST /chats` |
|
|
110
|
+
| `ziggs_list_messages` | `GET /chats/:id/messages` |
|
|
111
|
+
| `ziggs_send_message` | `POST /chats/:id/messages` |
|
|
112
|
+
| `ziggs_propose_agreement` | `POST /agreements/proposals` |
|
|
113
|
+
| `ziggs_respond_to_agreement` | `POST /agreements/:id/respond` |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Develop
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cd agentplus
|
|
121
|
+
npm install
|
|
122
|
+
npm run build -w @ziggs-ai/ziggs-mcp
|
|
123
|
+
npm test -w @ziggs-ai/ziggs-mcp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Logs must use **stderr** only (stdio MCP transport).
|
|
127
|
+
|
|
128
|
+
## Publish (maintainers)
|
|
129
|
+
|
|
130
|
+
1. Bump `@ziggs-ai/api-client` if needed → tag `api-client-v*`, push (publishes to npm).
|
|
131
|
+
2. Tag `ziggs-mcp-v*` → CI publishes `@ziggs-ai/ziggs-mcp`.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git tag api-client-v0.1.5 && git push origin api-client-v0.1.5
|
|
135
|
+
git tag ziggs-mcp-v0.1.0 && git push origin ziggs-mcp-v0.1.0
|
|
136
|
+
```
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
declare const envSchema: z.ZodObject<{
|
|
4
|
+
ZIGGS_API_URL: z.ZodOptional<z.ZodString>;
|
|
5
|
+
HTTP_URL: z.ZodOptional<z.ZodString>;
|
|
6
|
+
ZIGGS_OPERATOR_KEY: z.ZodString;
|
|
7
|
+
/** Optional when the operator key is agent-scoped (ZIG-279). */
|
|
8
|
+
ZIGGS_AGENT_ID: z.ZodOptional<z.ZodString>;
|
|
9
|
+
/** Human user id for payer-side proposals (ZIG-222 smoke step 2). */
|
|
10
|
+
ZIGGS_OWNER_USER_ID: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
ZIGGS_OPERATOR_KEY: string;
|
|
13
|
+
ZIGGS_API_URL?: string | undefined;
|
|
14
|
+
HTTP_URL?: string | undefined;
|
|
15
|
+
ZIGGS_AGENT_ID?: string | undefined;
|
|
16
|
+
ZIGGS_OWNER_USER_ID?: string | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
ZIGGS_OPERATOR_KEY: string;
|
|
19
|
+
ZIGGS_API_URL?: string | undefined;
|
|
20
|
+
HTTP_URL?: string | undefined;
|
|
21
|
+
ZIGGS_AGENT_ID?: string | undefined;
|
|
22
|
+
ZIGGS_OWNER_USER_ID?: string | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
type EnvConfig = z.infer<typeof envSchema>;
|
|
25
|
+
export interface ZiggsMcpConfig extends EnvConfig {
|
|
26
|
+
/** Resolved delegate agent — from boundAgentId or ZIGGS_AGENT_ID. */
|
|
27
|
+
resolvedAgentId: string;
|
|
28
|
+
}
|
|
29
|
+
/** Load delegate credentials from the environment (ZIG-222 / ZIG-430). */
|
|
30
|
+
export declare function loadConfig(): ZiggsMcpConfig;
|
|
31
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { MINT_KEY_HELP, resolveDelegateAgentId } from './operatorKey.js';
|
|
4
|
+
const envSchema = z.object({
|
|
5
|
+
ZIGGS_API_URL: z.string().optional(),
|
|
6
|
+
HTTP_URL: z.string().optional(),
|
|
7
|
+
ZIGGS_OPERATOR_KEY: z.string().min(1, 'ZIGGS_OPERATOR_KEY is required'),
|
|
8
|
+
/** Optional when the operator key is agent-scoped (ZIG-279). */
|
|
9
|
+
ZIGGS_AGENT_ID: z.string().optional(),
|
|
10
|
+
/** Human user id for payer-side proposals (ZIG-222 smoke step 2). */
|
|
11
|
+
ZIGGS_OWNER_USER_ID: z.string().optional(),
|
|
12
|
+
});
|
|
13
|
+
/** Load delegate credentials from the environment (ZIG-222 / ZIG-430). */
|
|
14
|
+
export function loadConfig() {
|
|
15
|
+
const raw = {
|
|
16
|
+
ZIGGS_API_URL: process.env.ZIGGS_API_URL,
|
|
17
|
+
HTTP_URL: process.env.HTTP_URL ?? process.env.ZIGGS_API_URL,
|
|
18
|
+
ZIGGS_OPERATOR_KEY: process.env.ZIGGS_OPERATOR_KEY,
|
|
19
|
+
ZIGGS_AGENT_ID: process.env.ZIGGS_AGENT_ID,
|
|
20
|
+
ZIGGS_OWNER_USER_ID: process.env.ZIGGS_OWNER_USER_ID,
|
|
21
|
+
};
|
|
22
|
+
const parsed = envSchema.safeParse(raw);
|
|
23
|
+
if (!parsed.success) {
|
|
24
|
+
const missing = parsed.error.issues.map((i) => i.path.join('.')).join(', ');
|
|
25
|
+
throw new Error(`ziggs-mcp: missing or invalid env (${missing}). ${MINT_KEY_HELP}`);
|
|
26
|
+
}
|
|
27
|
+
let resolvedAgentId;
|
|
28
|
+
try {
|
|
29
|
+
resolvedAgentId = resolveDelegateAgentId(parsed.data.ZIGGS_OPERATOR_KEY, parsed.data.ZIGGS_AGENT_ID);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
throw e instanceof Error ? e : new Error(String(e));
|
|
33
|
+
}
|
|
34
|
+
if (parsed.data.ZIGGS_API_URL && !process.env.HTTP_URL) {
|
|
35
|
+
process.env.HTTP_URL = parsed.data.ZIGGS_API_URL;
|
|
36
|
+
}
|
|
37
|
+
return { ...parsed.data, resolvedAgentId };
|
|
38
|
+
}
|
package/dist/creds.d.ts
ADDED
package/dist/creds.js
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** JWT payload fields we read client-side (signature not verified — identity hint only). */
|
|
2
|
+
export interface OperatorKeyClaims {
|
|
3
|
+
type?: string;
|
|
4
|
+
keyId?: string;
|
|
5
|
+
boundAgentId?: string | null;
|
|
6
|
+
exp?: number;
|
|
7
|
+
}
|
|
8
|
+
declare const MINT_KEY_HELP: string;
|
|
9
|
+
/** Decode operator JWT payload without verifying signature (ZIG-279 boundAgentId). */
|
|
10
|
+
export declare function decodeOperatorKeyClaims(token: string): OperatorKeyClaims | null;
|
|
11
|
+
export declare function isOperatorKeyExpired(claims: OperatorKeyClaims | null): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve delegate agent id: agent-scoped key (boundAgentId) wins; else ZIGGS_AGENT_ID.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveDelegateAgentId(operatorKey: string, envAgentId: string | undefined): string;
|
|
16
|
+
export { MINT_KEY_HELP };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const MINT_KEY_HELP = 'Mint a key in the Ziggs app: Developer Portal → Operator keys (fleet key + set ZIGGS_AGENT_ID), ' +
|
|
2
|
+
'or open your delegate agent → Issue operator key (agent-scoped — no ZIGGS_AGENT_ID needed). ' +
|
|
3
|
+
'Docs: https://app.ziggsai.com/docs (Claude Code MCP tier).';
|
|
4
|
+
/** Decode operator JWT payload without verifying signature (ZIG-279 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
|
+
boundAgentId: payload.boundAgentId ?? null,
|
|
17
|
+
exp: payload.exp,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function isOperatorKeyExpired(claims) {
|
|
25
|
+
if (!claims?.exp)
|
|
26
|
+
return false;
|
|
27
|
+
return claims.exp * 1000 <= Date.now();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve delegate agent id: agent-scoped key (boundAgentId) wins; else ZIGGS_AGENT_ID.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveDelegateAgentId(operatorKey, envAgentId) {
|
|
33
|
+
if (!operatorKey?.trim()) {
|
|
34
|
+
throw new Error(`ziggs-mcp: ZIGGS_OPERATOR_KEY is required. ${MINT_KEY_HELP}`);
|
|
35
|
+
}
|
|
36
|
+
const claims = decodeOperatorKeyClaims(operatorKey);
|
|
37
|
+
if (claims?.type && claims.type !== 'operator') {
|
|
38
|
+
throw new Error('ziggs-mcp: ZIGGS_OPERATOR_KEY must be an operator token (not a session JWT). ' +
|
|
39
|
+
MINT_KEY_HELP);
|
|
40
|
+
}
|
|
41
|
+
if (isOperatorKeyExpired(claims)) {
|
|
42
|
+
throw new Error(`ziggs-mcp: operator key expired (keyId=${claims?.keyId ?? 'unknown'}). ${MINT_KEY_HELP}`);
|
|
43
|
+
}
|
|
44
|
+
const bound = claims?.boundAgentId?.trim() || '';
|
|
45
|
+
const env = envAgentId?.trim() || '';
|
|
46
|
+
if (bound && env && bound !== env) {
|
|
47
|
+
throw new Error(`ziggs-mcp: ZIGGS_AGENT_ID (${env}) conflicts with agent-scoped key bound to "${bound}". ` +
|
|
48
|
+
'Remove ZIGGS_AGENT_ID or use a fleet impersonation key instead.');
|
|
49
|
+
}
|
|
50
|
+
if (bound)
|
|
51
|
+
return bound;
|
|
52
|
+
if (env)
|
|
53
|
+
return env;
|
|
54
|
+
throw new Error('ziggs-mcp: set ZIGGS_AGENT_ID, or mint an agent-scoped operator key (boundAgentId in token). ' +
|
|
55
|
+
MINT_KEY_HELP);
|
|
56
|
+
}
|
|
57
|
+
export { MINT_KEY_HELP };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startStdioServer(): Promise<void>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
|
+
import { credsFromConfig } from './creds.js';
|
|
6
|
+
import { registerZiggsTools } from './tools.js';
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const { version } = require('../package.json');
|
|
9
|
+
export async function startStdioServer() {
|
|
10
|
+
const cfg = loadConfig();
|
|
11
|
+
const creds = credsFromConfig(cfg);
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: 'ziggs-mcp',
|
|
14
|
+
version,
|
|
15
|
+
});
|
|
16
|
+
registerZiggsTools(server, creds, cfg);
|
|
17
|
+
const transport = new StdioServerTransport();
|
|
18
|
+
await server.connect(transport);
|
|
19
|
+
}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type Creds } from '@ziggs-ai/api-client';
|
|
3
|
+
import type { ZiggsMcpConfig } from './config.js';
|
|
4
|
+
export declare function registerZiggsTools(server: McpServer, creds: Creds, cfg: ZiggsMcpConfig): void;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAgreement, getMyAgreements, listMyChats, openConversation, proposeDirectTo, respondToAgreement, ScopeClient, MessagesClient, sendChatMessage, ContextDiscoveryClient, ContextReadClient, ArtifactsClient, getBackendUrl, } from '@ziggs-ai/api-client';
|
|
4
|
+
function textResult(data) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function toolError(message) {
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: 'text', text: message }],
|
|
12
|
+
isError: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const scopeKindSchema = z.enum(['chat', 'agreement', 'task', 'counterparty']);
|
|
16
|
+
const contextReadTypeSchema = z.enum([
|
|
17
|
+
'messages',
|
|
18
|
+
'artifacts',
|
|
19
|
+
'agreements',
|
|
20
|
+
'tasks',
|
|
21
|
+
]);
|
|
22
|
+
const artifactVisibilitySchema = z.enum(['chat', 'agent-private']);
|
|
23
|
+
async function writeArtifactStrict(creds, input) {
|
|
24
|
+
const url = `${getBackendUrl()}/artifacts`;
|
|
25
|
+
const res = await fetch(url, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: {
|
|
28
|
+
'content-type': 'application/json',
|
|
29
|
+
Authorization: `Bearer ${creds.operatorKey}`,
|
|
30
|
+
'X-Agent-Id': creds.agentId,
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
text: input.text.trim(),
|
|
34
|
+
content_type: input.content_type ?? 'text',
|
|
35
|
+
visibility: input.visibility,
|
|
36
|
+
chatId: input.chatId,
|
|
37
|
+
agreementId: input.agreementId,
|
|
38
|
+
taskId: input.taskId,
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const body = await res.text().catch(() => '');
|
|
43
|
+
throw new Error(`POST /artifacts ${res.status} ${body.slice(0, 200)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function registerZiggsTools(server, creds, cfg) {
|
|
47
|
+
server.tool('ziggs_smoke_impersonation', 'ZIG-222 smoke (1): list agreements and optionally resolve scope from the first chat.', {}, async () => {
|
|
48
|
+
try {
|
|
49
|
+
const agreements = await getMyAgreements({}, creds);
|
|
50
|
+
const chats = await listMyChats(creds);
|
|
51
|
+
let scope = null;
|
|
52
|
+
const firstChatId = chats[0]?.chatId;
|
|
53
|
+
if (firstChatId) {
|
|
54
|
+
const client = new ScopeClient(creds.operatorKey, creds.agentId);
|
|
55
|
+
scope = await client.get('chat', firstChatId);
|
|
56
|
+
}
|
|
57
|
+
return textResult({
|
|
58
|
+
ok: true,
|
|
59
|
+
agreementsCount: agreements.length,
|
|
60
|
+
chatsCount: chats.length,
|
|
61
|
+
scope,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return toolError(e.message);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
server.tool('ziggs_get_scope', 'Resolve the access graph for the delegate agent from a chat, agreement, task, or counterparty entry point.', {
|
|
69
|
+
viaKind: scopeKindSchema.describe('Entry kind'),
|
|
70
|
+
viaId: z.string().describe('Entry id'),
|
|
71
|
+
}, async ({ viaKind, viaId }) => {
|
|
72
|
+
try {
|
|
73
|
+
const client = new ScopeClient(creds.operatorKey, creds.agentId);
|
|
74
|
+
const result = await client.get(viaKind, viaId);
|
|
75
|
+
return textResult(result);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
return toolError(e.message);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
server.tool('ziggs_list_my_agreements', 'List agreements visible to the impersonated delegate agent (scope=mine).', {
|
|
82
|
+
proposalStatus: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe('Optional filter: pending, approved, rejected, …'),
|
|
86
|
+
}, async ({ proposalStatus }) => {
|
|
87
|
+
try {
|
|
88
|
+
const agreements = await getMyAgreements(proposalStatus ? { proposalStatus } : {}, creds);
|
|
89
|
+
return textResult({ count: agreements.length, agreements });
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
return toolError(e.message);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
server.tool('ziggs_get_agreement', 'Fetch a single agreement by id.', { agreementId: z.string() }, async ({ agreementId }) => {
|
|
96
|
+
try {
|
|
97
|
+
const agreement = await getAgreement(agreementId, creds);
|
|
98
|
+
if (!agreement)
|
|
99
|
+
return toolError(`Agreement not found: ${agreementId}`);
|
|
100
|
+
return textResult({ agreement });
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
return toolError(e.message);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
server.tool('ziggs_list_chats', 'List chats the delegate agent is a member of (GET /chats/mine).', {}, async () => {
|
|
107
|
+
try {
|
|
108
|
+
const chats = await listMyChats(creds);
|
|
109
|
+
return textResult({ count: chats.length, chats });
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
return toolError(e.message);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
server.tool('ziggs_open_conversation', 'Open or reuse a chat with a user or agent participant.', {
|
|
116
|
+
participantId: z.string().describe('User or agent id to converse with'),
|
|
117
|
+
}, async ({ participantId }) => {
|
|
118
|
+
try {
|
|
119
|
+
const out = await openConversation(participantId, creds);
|
|
120
|
+
return textResult(out);
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
return toolError(e.message);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
server.tool('ziggs_list_messages', 'Forward-delta message read for a chat.', {
|
|
127
|
+
chatId: z.string(),
|
|
128
|
+
after: z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe('ISO timestamp; default epoch (all messages)'),
|
|
132
|
+
limit: z.number().optional().describe('Max messages (default 100)'),
|
|
133
|
+
}, async ({ chatId, after, limit }) => {
|
|
134
|
+
try {
|
|
135
|
+
const client = new MessagesClient(creds.operatorKey, creds.agentId);
|
|
136
|
+
const result = await client.list(chatId, {
|
|
137
|
+
after,
|
|
138
|
+
limit,
|
|
139
|
+
});
|
|
140
|
+
return textResult(result);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
return toolError(e.message);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
server.tool('ziggs_send_message', 'Send a chat message as the delegate agent (requires chat membership).', {
|
|
147
|
+
chatId: z.string(),
|
|
148
|
+
receiverId: z.string().describe('User or agent id receiving the message'),
|
|
149
|
+
text: z.string(),
|
|
150
|
+
entryType: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe('Usually "message" for user-visible chat'),
|
|
154
|
+
}, async ({ chatId, receiverId, text, entryType }) => {
|
|
155
|
+
try {
|
|
156
|
+
const result = await sendChatMessage({
|
|
157
|
+
chatId,
|
|
158
|
+
receiverId,
|
|
159
|
+
text,
|
|
160
|
+
messageId: `mcp_${randomUUID()}`,
|
|
161
|
+
entryType: entryType ?? 'message',
|
|
162
|
+
contentType: 'text',
|
|
163
|
+
}, creds);
|
|
164
|
+
return textResult(result);
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
return toolError(e.message);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
server.tool('ziggs_propose_agreement', 'ZIG-222 smoke (2): propose as payer-side delegate. Requires payerId (or ZIGGS_OWNER_USER_ID). No hire/spend.', {
|
|
171
|
+
proposedTo: z.string(),
|
|
172
|
+
chatId: z.string(),
|
|
173
|
+
description: z.string(),
|
|
174
|
+
payerId: z
|
|
175
|
+
.string()
|
|
176
|
+
.optional()
|
|
177
|
+
.describe('Human user id = payer (ZIG-222: your userId)'),
|
|
178
|
+
price: z.number().optional().describe('Optional; does not trigger transfer by itself'),
|
|
179
|
+
}, async ({ proposedTo, chatId, description, payerId, price }) => {
|
|
180
|
+
try {
|
|
181
|
+
const resolvedPayer = payerId ?? cfg.ZIGGS_OWNER_USER_ID;
|
|
182
|
+
if (!resolvedPayer) {
|
|
183
|
+
return toolError('payerId is required (pass in tool args or set ZIGGS_OWNER_USER_ID)');
|
|
184
|
+
}
|
|
185
|
+
const agreement = await proposeDirectTo({
|
|
186
|
+
proposedTo,
|
|
187
|
+
chatId,
|
|
188
|
+
description,
|
|
189
|
+
payerId: resolvedPayer,
|
|
190
|
+
price,
|
|
191
|
+
engagementKind: 'service',
|
|
192
|
+
}, creds);
|
|
193
|
+
return textResult({ agreement });
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
return toolError(e.message);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
server.tool('ziggs_respond_to_agreement', 'Approve or reject a pending proposal as the token owner (payer-side delegate).', {
|
|
200
|
+
agreementId: z.string(),
|
|
201
|
+
action: z.enum(['approve', 'reject']),
|
|
202
|
+
}, async ({ agreementId, action }) => {
|
|
203
|
+
try {
|
|
204
|
+
const agreement = await respondToAgreement(agreementId, action, creds);
|
|
205
|
+
return textResult({ agreement });
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
return toolError(e.message);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
server.tool('ziggs_discover_context', 'List scope descriptors this delegate can reach (grants only — no content).', {}, async () => {
|
|
212
|
+
try {
|
|
213
|
+
const client = new ContextDiscoveryClient(creds.operatorKey, creds.agentId);
|
|
214
|
+
const reach = await client.discover();
|
|
215
|
+
return textResult({ count: reach.length, reach });
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return toolError(e.message);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
server.tool('ziggs_read_context', 'Uniform context read via GET /context/read/:type (messages, artifacts, agreements, tasks). Requires via=chat:id, agreement:id, or task:id.', {
|
|
222
|
+
type: contextReadTypeSchema.describe('Resource type to read'),
|
|
223
|
+
via: z
|
|
224
|
+
.string()
|
|
225
|
+
.describe('Scope entry, e.g. chat:<id>, agreement:<id>, task:<id>'),
|
|
226
|
+
cursor: z.string().optional().describe('Opaque cursor from prior nextCursor'),
|
|
227
|
+
after: z
|
|
228
|
+
.string()
|
|
229
|
+
.optional()
|
|
230
|
+
.describe('ISO timestamp for forward-delta (messages/artifacts)'),
|
|
231
|
+
direction: z
|
|
232
|
+
.enum(['forward'])
|
|
233
|
+
.optional()
|
|
234
|
+
.describe('Use forward with after for message forward-delta'),
|
|
235
|
+
limit: z.number().optional().describe('Page size (default server-side)'),
|
|
236
|
+
state: z.string().optional().describe('Task state filter (tasks only)'),
|
|
237
|
+
contextGrantId: z
|
|
238
|
+
.string()
|
|
239
|
+
.optional()
|
|
240
|
+
.describe('Pin a specific grant when holding several'),
|
|
241
|
+
}, async ({ type, via, cursor, after, direction, limit, state, contextGrantId }) => {
|
|
242
|
+
try {
|
|
243
|
+
const client = new ContextReadClient(creds.operatorKey, creds.agentId);
|
|
244
|
+
const result = await client.read(type, {
|
|
245
|
+
via,
|
|
246
|
+
cursor,
|
|
247
|
+
after,
|
|
248
|
+
direction,
|
|
249
|
+
limit,
|
|
250
|
+
state,
|
|
251
|
+
contextGrantId,
|
|
252
|
+
});
|
|
253
|
+
return textResult(result);
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
return toolError(e.message);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
server.tool('ziggs_record_artifact', 'Write an artifact to a chat or agreement scope. Set visibility explicitly.', {
|
|
260
|
+
text: z.string().describe('Artifact body'),
|
|
261
|
+
visibility: artifactVisibilitySchema.describe('chat = visible to scope parties; agent-private = delegate-only'),
|
|
262
|
+
chatId: z.string().optional().describe('Target chat (xor agreementId)'),
|
|
263
|
+
agreementId: z
|
|
264
|
+
.string()
|
|
265
|
+
.optional()
|
|
266
|
+
.describe('Target agreement (xor chatId)'),
|
|
267
|
+
taskId: z.string().optional().describe('Optional task link'),
|
|
268
|
+
content_type: z.string().optional().describe('Default text'),
|
|
269
|
+
}, async ({ text, visibility, chatId, agreementId, taskId, content_type }) => {
|
|
270
|
+
try {
|
|
271
|
+
if ((chatId && agreementId) || (!chatId && !agreementId)) {
|
|
272
|
+
return toolError('Pass exactly one of chatId or agreementId');
|
|
273
|
+
}
|
|
274
|
+
await writeArtifactStrict(creds, {
|
|
275
|
+
text,
|
|
276
|
+
visibility,
|
|
277
|
+
chatId,
|
|
278
|
+
agreementId,
|
|
279
|
+
taskId,
|
|
280
|
+
content_type,
|
|
281
|
+
});
|
|
282
|
+
return textResult({ ok: true, visibility, chatId, agreementId });
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
return toolError(e.message);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
server.tool('ziggs_list_artifacts', 'List artifacts for a chat or agreement (GET /artifacts). Prefer ziggs_read_context type=artifacts for grant-pinned reads.', {
|
|
289
|
+
chatId: z.string().optional().describe('Chat scope (xor agreementId)'),
|
|
290
|
+
agreementId: z
|
|
291
|
+
.string()
|
|
292
|
+
.optional()
|
|
293
|
+
.describe('Agreement scope (xor chatId)'),
|
|
294
|
+
after: z.string().optional().describe('ISO timestamp forward-delta'),
|
|
295
|
+
limit: z.number().optional().describe('Max rows'),
|
|
296
|
+
}, async ({ chatId, agreementId, after, limit }) => {
|
|
297
|
+
try {
|
|
298
|
+
const client = new ArtifactsClient(creds.operatorKey, creds.agentId);
|
|
299
|
+
const result = await client.list({ chatId, agreementId }, { after, limit });
|
|
300
|
+
return textResult(result);
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
return toolError(e.message);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Claude Code + Ziggs MCP (ZIG-430)
|
|
2
|
+
|
|
3
|
+
One-command boarding against production (`https://api.ziggsai.com`).
|
|
4
|
+
|
|
5
|
+
## 1. Mint an agent-scoped operator key (recommended)
|
|
6
|
+
|
|
7
|
+
In the Ziggs web app:
|
|
8
|
+
|
|
9
|
+
1. **Developer Portal → Agents** — create a delegate agent (e.g. `claude-delegate`).
|
|
10
|
+
2. Open the agent → **Issue operator key** (agent-scoped). Copy the token **once**.
|
|
11
|
+
|
|
12
|
+
Agent-scoped keys embed `boundAgentId` in the JWT ([ZIG-279](https://linear.app/ziggsai/issue/ZIG-279/agent-scoped-operator-keys-to-skip-registration)) — you do **not** need `ZIGGS_AGENT_ID`.
|
|
13
|
+
|
|
14
|
+
Required scopes (minimum for ZIG-222 smoke): `agents:impersonate`, `agents:read`, `agents:write`, `context:read`, plus chat/agreement scopes your tools need.
|
|
15
|
+
|
|
16
|
+
## 2. Add the MCP server
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
claude mcp add ziggs \
|
|
20
|
+
--env ZIGGS_OPERATOR_KEY=op_...your-token... \
|
|
21
|
+
-- npx -y @ziggs-ai/ziggs-mcp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Optional env:
|
|
25
|
+
|
|
26
|
+
| Variable | When |
|
|
27
|
+
|----------|------|
|
|
28
|
+
| `ZIGGS_API_URL` | Non-production API (default `https://api.ziggsai.com`) |
|
|
29
|
+
| `ZIGGS_AGENT_ID` | Only for **fleet** impersonation keys (not agent-scoped) |
|
|
30
|
+
| `ZIGGS_OWNER_USER_ID` | Default human `userId` for payer-side proposals |
|
|
31
|
+
|
|
32
|
+
## 3. Smoke in Claude Code
|
|
33
|
+
|
|
34
|
+
Ask Claude to call tools in order:
|
|
35
|
+
|
|
36
|
+
1. `ziggs_list_chats` or `ziggs_discover_context`
|
|
37
|
+
2. `ziggs_send_message` (chat you belong to)
|
|
38
|
+
3. `ziggs_propose_agreement` + `ziggs_respond_to_agreement` (optional)
|
|
39
|
+
|
|
40
|
+
Paste the session transcript in the PR when verifying ZIG-430.
|
|
41
|
+
|
|
42
|
+
## Fleet key alternative
|
|
43
|
+
|
|
44
|
+
If your key is **not** agent-scoped, pass both:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
claude mcp add ziggs \
|
|
48
|
+
--env ZIGGS_OPERATOR_KEY=... \
|
|
49
|
+
--env ZIGGS_AGENT_ID=your-delegate-agent-id \
|
|
50
|
+
-- npx -y @ziggs-ai/ziggs-mcp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Troubleshooting
|
|
54
|
+
|
|
55
|
+
| Error | Fix |
|
|
56
|
+
|-------|-----|
|
|
57
|
+
| `ZIGGS_OPERATOR_KEY is required` | Mint a key in Developer Portal |
|
|
58
|
+
| `operator key expired` | Issue a new key |
|
|
59
|
+
| `set ZIGGS_AGENT_ID, or mint an agent-scoped` | Use agent-scoped key **or** set `ZIGGS_AGENT_ID` |
|
|
60
|
+
| `conflicts with agent-scoped key` | Remove `ZIGGS_AGENT_ID` — the key already names the agent |
|
|
61
|
+
|
|
62
|
+
See also: [README](../README.md) · [Cursor example](cursor-mcp.json)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"ziggs": {
|
|
4
|
+
"command": "node",
|
|
5
|
+
"args": ["REPLACE_WITH_ABSOLUTE_PATH/agentplus/ziggs-mcp/dist/index.js"],
|
|
6
|
+
"env": {
|
|
7
|
+
"ZIGGS_API_URL": "https://api.ziggsai.com",
|
|
8
|
+
"ZIGGS_OPERATOR_KEY": "REPLACE_ME",
|
|
9
|
+
"ZIGGS_AGENT_ID": "cursor-delegate",
|
|
10
|
+
"ZIGGS_OWNER_USER_ID": "REPLACE_WITH_YOUR_USER_ID"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Claude Code, Cursor, and other MCP hosts — act as your Ziggs delegate agent",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ziggs-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc -p tsconfig.json",
|
|
13
|
+
"prepack": "npm run build",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"test": "node --import tsx/esm --test --test-reporter=spec test/*.test.ts"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
20
|
+
"@ziggs-ai/api-client": "^0.1.5",
|
|
21
|
+
"dotenv": "^16.6.1",
|
|
22
|
+
"zod": "^3.24.2"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20"
|
|
26
|
+
},
|
|
27
|
+
"keywords": ["ziggs", "mcp", "claude", "cursor", "model-context-protocol"],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"files": ["dist", "README.md", "examples"],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/ZiggsAI/agentplus.git",
|
|
37
|
+
"directory": "ziggs-mcp"
|
|
38
|
+
}
|
|
39
|
+
}
|