@ziggs-ai/ziggs-mcp 0.34.0 → 0.34.1
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.
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type Creds } from '@ziggs-ai/api-client';
|
|
3
|
+
export declare function installConnectionNotices(server: McpServer, creds: Creds, version: string): void;
|
|
4
|
+
export declare function readConnectionNotices(server: McpServer, fresh?: boolean): Promise<string[]>;
|
|
5
|
+
export declare function appendConnectionNotices(server: McpServer, name: string, value: unknown, providedNotices?: string[]): Promise<unknown>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { workspaceRouter } from './workspaceRouting.js';
|
|
2
|
+
import { decodeOperatorKeyClaims } from './operatorKey.js';
|
|
3
|
+
import { getBackendUrl } from '@ziggs-ai/api-client';
|
|
4
|
+
const readers = new WeakMap();
|
|
5
|
+
/** A notice changes when the person reconnects or updates; a minute is soon enough. */
|
|
6
|
+
const NOTICE_TTL_MS = 60_000;
|
|
7
|
+
/** The session-start reads always ask the server, so a reconnect shows on the next one. */
|
|
8
|
+
const SESSION_READS = new Set(['ziggs_auth_status', 'ziggs_inbox']);
|
|
9
|
+
export function installConnectionNotices(server, creds, version) {
|
|
10
|
+
if (!decodeOperatorKeyClaims(creds.operatorKey)?.keyId)
|
|
11
|
+
return;
|
|
12
|
+
let cached = null;
|
|
13
|
+
readers.set(server, async (fresh) => {
|
|
14
|
+
if (!fresh && cached && Date.now() - cached.readAt < NOTICE_TTL_MS)
|
|
15
|
+
return cached.notices;
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(`${getBackendUrl().replace(/\/$/, '')}/oauth/connection-notices`, {
|
|
18
|
+
headers: { Authorization: `Bearer ${creds.operatorKey}`, 'X-Ziggs-Mcp-Version': version },
|
|
19
|
+
signal: AbortSignal.timeout(5000),
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok)
|
|
22
|
+
return cached?.notices ?? [];
|
|
23
|
+
const body = await response.json();
|
|
24
|
+
if (body.assistantConnection)
|
|
25
|
+
workspaceRouter(server)?.activate();
|
|
26
|
+
const notices = Array.isArray(body.notices) ? body.notices.filter((n) => typeof n === 'string') : [];
|
|
27
|
+
cached = { readAt: Date.now(), notices };
|
|
28
|
+
return notices;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return cached?.notices ?? [];
|
|
32
|
+
} // A notice lookup must never repeat or suppress a tool's write.
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export async function readConnectionNotices(server, fresh = false) {
|
|
36
|
+
return await readers.get(server)?.(fresh) ?? [];
|
|
37
|
+
}
|
|
38
|
+
export async function appendConnectionNotices(server, name, value, providedNotices) {
|
|
39
|
+
const notices = providedNotices ?? await readConnectionNotices(server, SESSION_READS.has(name));
|
|
40
|
+
if (!notices?.length || !value || typeof value !== 'object')
|
|
41
|
+
return value;
|
|
42
|
+
const result = value;
|
|
43
|
+
const content = (result.content ?? []).map(item => ({ ...item }));
|
|
44
|
+
if (name === 'ziggs_auth_status' || name === 'ziggs_inbox') {
|
|
45
|
+
const text = content.find(item => item.type === 'text' && item.text);
|
|
46
|
+
if (text?.text)
|
|
47
|
+
try {
|
|
48
|
+
const payload = JSON.parse(text.text);
|
|
49
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
50
|
+
text.text = JSON.stringify({ ...payload, session: { ...payload.session, connectionNotices: notices } });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch { /* Non-JSON content still gets the plain sentence below. */ }
|
|
54
|
+
}
|
|
55
|
+
for (const notice of notices)
|
|
56
|
+
if (!content.some(item => item.text === notice))
|
|
57
|
+
content.push({ type: 'text', text: notice });
|
|
58
|
+
return { ...value, content };
|
|
59
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { installConnectionNotices } from './connectionNotices.js';
|
|
1
2
|
import { installWorkspaceRouting } from './workspaceRouting.js';
|
|
2
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -24,6 +25,7 @@ export function createZiggsMcpServer(creds, cfg) {
|
|
|
24
25
|
// since both build the server through this factory.
|
|
25
26
|
instructions: renderInstructions(),
|
|
26
27
|
});
|
|
28
|
+
installConnectionNotices(server, creds, version);
|
|
27
29
|
const routedCreds = installWorkspaceRouting(server, creds);
|
|
28
30
|
registerZiggsTools(server, routedCreds, cfg);
|
|
29
31
|
// Decide what a caller loads natively and what it reaches through
|
package/dist/strictParams.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendConnectionNotices, readConnectionNotices } from './connectionNotices.js';
|
|
1
2
|
import { workspaceRouter } from './workspaceRouting.js';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { recordCatalogEntry } from './toolCatalog.js';
|
|
@@ -51,6 +52,11 @@ export function registerStrictTool(server, name, description, shape, annotations
|
|
|
51
52
|
shape = { ...shape, orgId: shape.orgId?.optional() ?? z.string().optional().describe('Workspace named by your human. Required for new actions when more than one workspace fits.') };
|
|
52
53
|
cb = router.wrap(name, annotations.readOnlyHint === true, cb, nativeOrgId);
|
|
53
54
|
}
|
|
55
|
+
const routed = cb;
|
|
56
|
+
cb = (async (...args) => {
|
|
57
|
+
const notices = await readConnectionNotices(server, name === 'ziggs_auth_status' || name === 'ziggs_inbox');
|
|
58
|
+
return appendConnectionNotices(server, name, await routed(...args), notices);
|
|
59
|
+
});
|
|
54
60
|
const params = strictParams(name, shape);
|
|
55
61
|
const handle = server.registerTool(name, {
|
|
56
62
|
title: annotations.title,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type AssistantWorkspace, type Creds } from '@ziggs-ai/api-client';
|
|
3
3
|
/**
|
|
4
|
-
* One
|
|
5
|
-
*
|
|
4
|
+
* One assistant identity across workspace hires, with each tool call choosing
|
|
5
|
+
* the workspace that owns its work.
|
|
6
6
|
*
|
|
7
7
|
* The connection's roster (`GET /oauth/workspaces`) is read once a minute and
|
|
8
8
|
* on every session-start read, so a workspace joined mid-session shows up
|
|
@@ -40,12 +40,14 @@ export declare function workspaceRouter(server: McpServer): WorkspaceRouter | un
|
|
|
40
40
|
*/
|
|
41
41
|
export declare function installWorkspaceRouting(server: McpServer, creds: Creds): Creds;
|
|
42
42
|
export declare class WorkspaceRouter {
|
|
43
|
+
private enabled;
|
|
43
44
|
private readonly active;
|
|
44
45
|
/** The base credentials with `agentId` answered by the call in progress. */
|
|
45
46
|
readonly creds: Creds;
|
|
46
47
|
private readonly client;
|
|
47
48
|
private roster;
|
|
48
|
-
constructor(base: Creds);
|
|
49
|
+
constructor(base: Creds, enabled?: boolean);
|
|
50
|
+
activate(): void;
|
|
49
51
|
acceptsWorkspace(name: string): boolean;
|
|
50
52
|
/** The connection's workspaces, re-read after {@link WORKSPACES_TTL_MS} or on demand. */
|
|
51
53
|
workspaces(fresh?: boolean): Promise<AssistantWorkspace[]>;
|
|
@@ -53,6 +55,7 @@ export declare class WorkspaceRouter {
|
|
|
53
55
|
/** The existing thing a call is about, from the argument that names it. */
|
|
54
56
|
private target;
|
|
55
57
|
wrap(name: string, readOnly: boolean, handler: Handler, nativeOrgId?: boolean): Handler;
|
|
58
|
+
private wrapConnected;
|
|
56
59
|
/** The connection's own reads and acks: never routed, always live. */
|
|
57
60
|
private connectionTool;
|
|
58
61
|
/**
|
package/dist/workspaceRouting.js
CHANGED
|
@@ -47,26 +47,30 @@ export function workspaceRouter(server) {
|
|
|
47
47
|
* a fleet key with ZIGGS_AGENT_ID) names its agent itself and is left alone.
|
|
48
48
|
*/
|
|
49
49
|
export function installWorkspaceRouting(server, creds) {
|
|
50
|
-
|
|
50
|
+
const claims = decodeOperatorKeyClaims(creds.operatorKey);
|
|
51
|
+
if (!claims?.connectionClientId && !claims?.boundAgentId)
|
|
51
52
|
return creds;
|
|
52
|
-
const router = new WorkspaceRouter(creds);
|
|
53
|
+
const router = new WorkspaceRouter(creds, !!claims.connectionClientId);
|
|
53
54
|
routers.set(server, router);
|
|
54
55
|
return router.creds;
|
|
55
56
|
}
|
|
56
57
|
export class WorkspaceRouter {
|
|
58
|
+
enabled;
|
|
57
59
|
active = new AsyncLocalStorage();
|
|
58
60
|
/** The base credentials with `agentId` answered by the call in progress. */
|
|
59
61
|
creds;
|
|
60
62
|
client;
|
|
61
63
|
roster = null;
|
|
62
|
-
constructor(base) {
|
|
64
|
+
constructor(base, enabled = true) {
|
|
65
|
+
this.enabled = enabled;
|
|
63
66
|
this.client = new AssistantConnectionClient(base);
|
|
64
67
|
this.creds = new Proxy(base, {
|
|
65
|
-
get: (target, key, receiver) => key === 'agentId'
|
|
68
|
+
get: (target, key, receiver) => key === 'agentId' && this.enabled
|
|
66
69
|
? (this.active.getStore()?.agentId ?? '')
|
|
67
70
|
: Reflect.get(target, key, receiver),
|
|
68
71
|
});
|
|
69
72
|
}
|
|
73
|
+
activate() { this.enabled = true; }
|
|
70
74
|
acceptsWorkspace(name) {
|
|
71
75
|
return !GLOBAL.has(name) && !CONNECTION.has(name);
|
|
72
76
|
}
|
|
@@ -103,6 +107,10 @@ export class WorkspaceRouter {
|
|
|
103
107
|
return null;
|
|
104
108
|
}
|
|
105
109
|
wrap(name, readOnly, handler, nativeOrgId = false) {
|
|
110
|
+
const routed = this.wrapConnected(name, readOnly, handler, nativeOrgId);
|
|
111
|
+
return (args, ...extra) => this.enabled ? routed(args, ...extra) : handler(args, ...extra);
|
|
112
|
+
}
|
|
113
|
+
wrapConnected(name, readOnly, handler, nativeOrgId = false) {
|
|
106
114
|
if (GLOBAL.has(name))
|
|
107
115
|
return handler;
|
|
108
116
|
// The routing argument stays out of business payloads; a tool with its own
|
|
@@ -204,7 +212,7 @@ export class WorkspaceRouter {
|
|
|
204
212
|
* already knows, as a fan-out does.
|
|
205
213
|
*/
|
|
206
214
|
async run(workspace, args, extra, handler, label = true) {
|
|
207
|
-
return this.active.run(workspace, async () => {
|
|
215
|
+
return withAssistantRequest({ orgId: workspace.orgId }, () => this.active.run(workspace, async () => {
|
|
208
216
|
const response = await handler(args, ...extra);
|
|
209
217
|
if (!label || response.isError)
|
|
210
218
|
return response;
|
|
@@ -225,7 +233,7 @@ export class WorkspaceRouter {
|
|
|
225
233
|
};
|
|
226
234
|
}),
|
|
227
235
|
};
|
|
228
|
-
});
|
|
236
|
+
}));
|
|
229
237
|
}
|
|
230
238
|
}
|
|
231
239
|
function parseJson(text) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/ziggs-mcp",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.1",
|
|
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": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
|
-
"@ziggs-ai/api-client": "0.34.
|
|
42
|
+
"@ziggs-ai/api-client": "0.34.1",
|
|
43
43
|
"dotenv": "^16.6.1",
|
|
44
44
|
"zod": "^3.24.2",
|
|
45
45
|
"zod-to-json-schema": "^3.25.1"
|