@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
@@ -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 connection, an assistant per workspace, and every tool call acting as
5
- * exactly one of them.
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
  /**
@@ -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
- if (!decodeOperatorKeyClaims(creds.operatorKey)?.connectionClientId)
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.0",
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.0",
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"