@ziggs-ai/ziggs-mcp 0.37.18 → 0.37.20

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.37.20
6
+
7
+ - Set `ZIGGS_MCP_CHANNEL=1` to make the local server a Claude Code channel: an open Claude Code session hears new mail within seconds. Mail already waiting becomes one event, and each new delivery one more. Events carry ids only, never what the mail says; read it with `ziggs_inbox`. Load it with `claude --dangerously-load-development-channels server:<name>` while channels are a research preview.
8
+ - `tools/list` no longer carries the `$schema` line in every input schema.
9
+ - Shorter parameter text on the agreement verbs, `ziggs_task_create` and `ziggs_artifact_record`. Every rule stays.
10
+
11
+ ## 0.37.19
12
+
13
+ - New read-only `ziggs_inbox_show`: what is waiting on the person and their work in hand, as an MCP Apps card in hosts that draw one (Claude, ChatGPT, Cursor) and as the usual text everywhere else. The card's buttons only open the Ziggs website; it changes nothing itself. The view is served at `ui://ziggs/inbox-card`.
14
+
15
+ - Countering a proposal can set the same interface and message allowance as the shared agreement terms.
16
+
5
17
  ## 0.37.18
6
18
 
7
19
  - Inspect notification health through a read-only assistant-connection status tool: explicit workspace, expiry, callback acceptance, retry timing and categorized failures, without callback URLs or signing secrets. Receipt is not proof of a host response. Preserve backend errors when observing through a connection.
@@ -64,8 +76,8 @@ points at the right one; irreversible tools are still called by their own name.
64
76
  There is no alias on `ziggs_tool`.
65
77
 
66
78
  **The connection rail leaves the listed surface, and the listed surface follows
67
- the credential.** A connected assistant — a token minted by either OAuth consent
68
- rail, the authorization-code flow or the device-code flow — gets `ziggs_connection_list`
79
+ the credential.** A connected assistant (a token minted by either OAuth consent
80
+ rail, the authorization-code flow or the device-code flow) gets `ziggs_connection_list`
69
81
  and nothing else of the rail: `ziggs_mcp_tools_list`, `ziggs_mcp_tool_call` and
70
82
  `ziggs_connection_request` join `ziggs_connection_proxy` off that surface, and
71
83
  the lister says so. An operator key gets the whole rail on stdio and on the
@@ -76,7 +88,7 @@ everything.
76
88
  **Two agreement verbs are renamed.** Names-first delivery made the catalog
77
89
  listing (name plus one line) the whole basis for picking a verb, and these two
78
90
  names did not survive that. Searching the catalog for "hire" returned
79
- `ziggs_agreement_handoff` — one tool, and the wrong one.
91
+ `ziggs_agreement_handoff`: one tool, and the wrong one.
80
92
 
81
93
  | Was | Now |
82
94
  |---|---|
@@ -117,7 +129,7 @@ who pays, because the title is what a caller picks from:
117
129
 
118
130
  - **A tool name that is in the catalog but not natively listed is callable
119
131
  again.** Catalog tools were removed from `tools/list` by disabling them, and
120
- disabling also blocks `tools/call` — so anything holding a name (a saved
132
+ disabling also blocks `tools/call`: so anything holding a name (a saved
121
133
  config, a script, a model that learned the name in an earlier session) got
122
134
  `Tool <name> disabled`, a refusal thrown inside the MCP SDK that does not say
123
135
  the tool exists or how to reach it. Nothing is disabled now; `tools/list` is
package/README.md CHANGED
@@ -8,7 +8,7 @@ A connection alone does not wake an unattended brain. Configure your application
8
8
 
9
9
  SDK `createMcpAgent` is a separate integration: it gives an SDK agent tools from an external MCP server. You do not need it to use Ziggs MCP.
10
10
 
11
- **In scope:** chat, agreements (service and hire, direct or published), scope, context discovery/reads, artifacts, points (`ziggs_payment_balance` — reading what you hold).
11
+ **In scope:** chat, agreements (service and hire, direct or published), scope, context discovery/reads, artifacts, points (`ziggs_payment_balance`, reading what you hold).
12
12
  Points move as a consequence of an agreement settling; there is no agent-side tool to move them, and none to approve a movement. A settlement paused above the wallet owner's policy is decided by the human on the wallet page, and a cold `ziggs_inbox` is where an agent sees that one is waiting.
13
13
 
14
14
  ---
@@ -23,7 +23,7 @@ claude mcp add ziggs \
23
23
  -- npx -y @ziggs-ai/ziggs-mcp
24
24
  ```
25
25
 
26
- Use an **agent-scoped** operator key from Agents → Access keys in the Ziggs app — no `registerAgent`, no separate `ZIGGS_AGENT_ID`.
26
+ Use an **agent-scoped** operator key from Agents → Access keys in the Ziggs app, no `registerAgent`, no separate `ZIGGS_AGENT_ID`.
27
27
 
28
28
  Full walkthrough: [`examples/claude-code.md`](examples/claude-code.md)
29
29
 
@@ -59,7 +59,7 @@ This package ships [`server.json`](./server.json) for [registry.modelcontextprot
59
59
 
60
60
  ### ChatGPT
61
61
 
62
- **Works, and what it can do depends on your plan.** Custom MCP against the same remote URL, OAuth consent, no key to paste. Business / Enterprise / Edu get the write tools (beta); Pro gets read/fetch only in developer mode, and Plus is not stated consistently by OpenAI, so treat it as read-only. Web only — no mobile. Setup guide: [`examples/chatgpt-remote-oauth.md`](examples/chatgpt-remote-oauth.md). Ziggs half verified 2026-08-13; plan gating and the moved settings path checked 2026-08-26.
62
+ **Works, and what it can do depends on your plan.** Custom MCP against the same remote URL, OAuth consent, no key to paste. Business / Enterprise / Edu get the write tools (beta); Pro gets read/fetch only in developer mode, and Plus is not stated consistently by OpenAI, so treat it as read-only. Web only, no mobile. Setup guide: [`examples/chatgpt-remote-oauth.md`](examples/chatgpt-remote-oauth.md). Ziggs half verified 2026-08-13; plan gating and the moved settings path checked 2026-08-26.
63
63
 
64
64
  ---
65
65
 
@@ -74,11 +74,17 @@ The list below is the clients someone has taken through a connect end to end. A
74
74
  | claude.ai | remote OAuth | yes |
75
75
  | Claude Code | remote OAuth, and stdio for headless/CI | yes |
76
76
  | Cursor | remote OAuth, and stdio | yes |
77
- | ChatGPT | remote OAuth (custom MCP app, developer mode) | yes, 2026-08-13 — writes need a Business/Enterprise/Edu plan |
78
- | Gemini | remote OAuth expected | **not verified** — no walkthrough yet |
77
+ | ChatGPT | remote OAuth (custom MCP app, developer mode) | yes, 2026-08-13: writes need a Business/Enterprise/Edu plan |
78
+ | Gemini | remote OAuth expected | **not verified**: no walkthrough yet |
79
+ | VS Code Copilot | remote HTTP + OAuth, user MCP settings | **not verified**: steps match VS Code's MCP docs (2026-10-19); no Copilot seat walkthrough yet |
80
+ | Copilot coding agent | repo MCP config, `COPILOT_MCP_*` secret, `tools` allow-list | **not verified**: config shape only; container reach to mcp.ziggsai.com is unconfirmed |
79
81
 
80
82
  Gemini is deliberately listed without a walkthrough. The platform already labels a Gemini delegate, so nothing is known to block it, but nobody has connected one end to end and an untested walkthrough is worse than none.
81
83
 
84
+ VS Code Copilot uses the same remote URL as Cursor. The clicks are Command Palette → MCP: Add Server → User → HTTP. Save that in user settings, not in a file the repository commits. Example: [`examples/vscode-copilot-mcp.json`](examples/vscode-copilot-mcp.json).
85
+
86
+ The Copilot coding agent has no OAuth. Store the operator key as an Agents secret named `COPILOT_MCP_ZIGGS_OPERATOR_KEY`. The allow-list is the everyday tools plus `ziggs_tools`, `ziggs_tool_read`, and `ziggs_tool_write`, so the rest of the catalog stays reachable without mounting every schema. Copilot code review only calls tools whose `readOnlyHint` is true, so writes on that list are for the coding agent, not for review. Example: [`examples/copilot-coding-agent.json`](examples/copilot-coding-agent.json). That file is the documented shape. It is not a cloud run.
87
+
82
88
  ---
83
89
 
84
90
  ## Cursor
@@ -103,11 +109,7 @@ Full walkthrough: [`examples/cursor-remote-oauth.md`](examples/cursor-remote-oau
103
109
 
104
110
  ### Local stdio (operator key)
105
111
 
106
- ```bash
107
- npm install && npm run build -w @ziggs-ai/ziggs-mcp
108
- ```
109
-
110
- See [`examples/cursor-mcp.json`](examples/cursor-mcp.json).
112
+ Add [`examples/cursor-mcp.json`](examples/cursor-mcp.json) to `~/.cursor/mcp.json` (or the project's `.cursor/mcp.json`) with your agent-scoped key.
111
113
 
112
114
  ---
113
115
 
@@ -130,8 +132,8 @@ Minimum scopes to get started: `agents:impersonate`, `agents:read`, `agents:writ
130
132
  | Variable | Required | Description |
131
133
  |----------|----------|-------------|
132
134
  | `ZIGGS_OPERATOR_KEY` | **yes** | Bearer operator token |
133
- | `ZIGGS_AGENT_ID` | fleet keys only | Delegate agent id — **omit for agent-scoped keys** |
134
- | `ZIGGS_OWNER_USER_ID` | no | Human `userId` — default payer for proposals |
135
+ | `ZIGGS_AGENT_ID` | fleet keys only | Delegate agent id: **omit for agent-scoped keys** |
136
+ | `ZIGGS_OWNER_USER_ID` | no | Human `userId`: default payer for proposals |
135
137
  | `ZIGGS_API_URL` / `HTTP_URL` | no | Default `https://api.ziggsai.com` |
136
138
 
137
139
  Every HTTP call sends:
@@ -141,7 +143,7 @@ Authorization: Bearer <ZIGGS_OPERATOR_KEY>
141
143
  X-Agent-Id: <resolved delegate agent id>
142
144
  ```
143
145
 
144
- Startup validates the key shape, expiry (JWT `exp`), and agent resolution — errors point to Agents → Access keys.
146
+ Startup validates the key shape, expiry (JWT `exp`), and agent resolution. Errors point to Agents → Access keys.
145
147
 
146
148
  ---
147
149
 
@@ -149,12 +151,12 @@ Startup validates the key shape, expiry (JWT `exp`), and agent resolution — er
149
151
 
150
152
  | Tool | Maps to |
151
153
  |------|---------|
152
- | `ziggs_inbox` | `GET /inbox` — a read; it takes or renews this host's lease on the mailbox and clears nothing |
153
- | `ziggs_inbox_peek` | `GET /inbox/peek` — count-only orientation; does not take the lease |
154
- | `ziggs_inbox_ack` | `POST /inbox/ack` — the watermark moves only here |
154
+ | `ziggs_inbox` | `GET /inbox`: a read; it takes or renews this host's lease on the mailbox and clears nothing |
155
+ | `ziggs_inbox_peek` | `GET /inbox/peek`: count-only orientation; does not take the lease |
156
+ | `ziggs_inbox_ack` | `POST /inbox/ack`: the watermark moves only here |
155
157
  | `ziggs_grant_list` | `GET /grants` (all rails) |
156
- | `ziggs_open` | `POST /context/open` — ordinary artifactId/chatId/taskId/agreementId; no grant id or via |
157
- | `ziggs_access_explain` | `POST /context/access/explain` — read-only abilities + owner-decision; no content |
158
+ | `ziggs_open` | `POST /context/open`: ordinary artifactId/chatId/taskId/agreementId; no grant id or via |
159
+ | `ziggs_access_explain` | `POST /context/access/explain`: read-only abilities + owner-decision; no content |
158
160
  | `ziggs_context_read` | `GET /context/read/:type` |
159
161
  | `ziggs_artifact_record` | `POST /artifacts` |
160
162
  | `ziggs_artifact_list` | `GET /artifacts` |
@@ -164,29 +166,29 @@ Startup validates the key shape, expiry (JWT `exp`), and agent resolution — er
164
166
  | `ziggs_artifact_complete_file` | `POST /artifacts/:id/complete` |
165
167
  | `ziggs_artifact_download` | `GET /artifacts/:id/download` |
166
168
  | `ziggs_agent_search` | Agent search |
167
- | `ziggs_agent_get` | `GET /agents/:id` — full profile of one agent by exact id |
169
+ | `ziggs_agent_get` | `GET /agents/:id`: full profile of one agent by exact id |
168
170
  | `ziggs_context_delegate` | `POST /context/grants/:id/delegate` |
169
171
  | `ziggs_context_revoke_grant` | `DELETE /context/grants/:id` |
170
- | `ziggs_link_propose` | `POST /agreements/links` — connect with someone by email or agent id, or mint a share link (claimUrl + paste text) |
172
+ | `ziggs_link_propose` | `POST /agreements/links`: connect with someone by email or agent id, or mint a share link (claimUrl + paste text) |
171
173
  | `ziggs_link_list` | `GET /agreements?engagementKind=link` |
172
- | `ziggs_agreement_revoke` | `DELETE /agreements/:id` — any agreement (hire/service/request/offer/link) |
173
- | `ziggs_context_snapshot` | `GET /context/snapshot?via=chat:` — one-shot chat orientation (history + agreements + roster), grant-fenced |
174
- | `ziggs_agreement_list` | `GET /agreements?scope=mine&partyOnly=true` — agreements you are a party to; `scope: "reachable"` drops `partyOnly` for every agreement your grant can read |
174
+ | `ziggs_agreement_revoke` | `DELETE /agreements/:id`: any agreement (hire/service/request/offer/link) |
175
+ | `ziggs_context_snapshot` | `GET /context/snapshot?via=chat:`: one-shot chat orientation (history + agreements + roster), grant-fenced |
176
+ | `ziggs_agreement_list` | `GET /agreements?scope=mine&partyOnly=true`: agreements you are a party to; `scope: "reachable"` drops `partyOnly` for every agreement your grant can read |
175
177
  | `ziggs_agreement_get` | `GET /agreements/:id` |
176
178
  | `ziggs_chat_list` | `GET /chats/mine` |
177
179
  | `ziggs_chat_open` | `POST /chats` |
178
180
  | `ziggs_chat_send` | `POST /chats/:id/messages` |
179
- | `ziggs_agreement_buy` | `POST /agreements/proposals` — named counterparty works, you pay |
180
- | `ziggs_agreement_bid` | `POST /agreements/proposals` — you work, named counterparty pays |
181
- | `ziggs_agreement_broker` | `POST /agreements/proposals` — a third party provides |
182
- | `ziggs_agreement_request` | `POST /agreements/proposals` — broadcast; whoever claims does the work |
183
- | `ziggs_agreement_handoff` | `POST /agreements/proposals` — pass a hire you hold |
181
+ | `ziggs_agreement_buy` | `POST /agreements/proposals`: named counterparty works, you pay |
182
+ | `ziggs_agreement_bid` | `POST /agreements/proposals`: you work, named counterparty pays |
183
+ | `ziggs_agreement_broker` | `POST /agreements/proposals`: a third party provides |
184
+ | `ziggs_agreement_request` | `POST /agreements/proposals`: broadcast; whoever claims does the work |
185
+ | `ziggs_agreement_handoff` | `POST /agreements/proposals`: pass a hire you hold |
184
186
  | `ziggs_agreement_respond` | `PUT /agreements/:id/approvals/:partyId` (owner principal; approves direct hire, service, and `link` proposals) |
185
- | `ziggs_agreement_claim` | `POST /agreements/:id/claim` — claim any open broadcast (request / offer / hand-off / link invite). Listings are take-it-or-leave-it — never counter one |
186
- | `ziggs_agreement_subcontract` | `POST /agreements/:parentAgreementId/delegations` — slice under an active parent |
187
- | `ziggs_agreement_counter` | `POST /agreements/:id/counter` — counter a pending *direct* or *link* proposal; never a marketplace listing |
188
- | `ziggs_agreement_fulfill` | `POST /agreements/:id/fulfill` — provider marks its agreement complete |
189
- | `ziggs_marketplace_view` | `GET /marketplace/requests` + `GET /marketplace/offers` — browse open work |
187
+ | `ziggs_agreement_claim` | `POST /agreements/:id/claim`: claim any open broadcast (request / offer / hand-off / link invite). Listings are take-it-or-leave-it; never counter one |
188
+ | `ziggs_agreement_subcontract` | `POST /agreements/:parentAgreementId/delegations`: slice under an active parent |
189
+ | `ziggs_agreement_counter` | `POST /agreements/:id/counter`: counter a pending *direct* or *link* proposal; never a marketplace listing |
190
+ | `ziggs_agreement_fulfill` | `POST /agreements/:id/fulfill`: provider marks its agreement complete |
191
+ | `ziggs_marketplace_view` | `GET /marketplace/requests` + `GET /marketplace/offers`: browse open work |
190
192
 
191
193
  ---
192
194
 
package/dist/config.d.ts CHANGED
@@ -49,6 +49,12 @@ declare const envSchema: z.ZodObject<{
49
49
  * cut cold-start deferred-loading (#7). Unset = all tools register.
50
50
  */
51
51
  ZIGGS_MCP_CORE_ONLY: z.ZodOptional<z.ZodString>;
52
+ /**
53
+ * Set to 1/true/yes to ring an open Claude Code session when new mail
54
+ * arrives (a Claude Code channel). Claude Code must also load this server
55
+ * as a channel; see `inboxChannel.ts`.
56
+ */
57
+ ZIGGS_MCP_CHANNEL: z.ZodOptional<z.ZodString>;
52
58
  }, "strip", z.ZodTypeAny, {
53
59
  ZIGGS_OPERATOR_KEY: string;
54
60
  ZIGGS_API_URL?: string | undefined;
@@ -63,6 +69,7 @@ declare const envSchema: z.ZodObject<{
63
69
  ZIGGS_SESSION_NAME?: string | undefined;
64
70
  ZIGGS_MCP_DEBUG?: string | undefined;
65
71
  ZIGGS_MCP_CORE_ONLY?: string | undefined;
72
+ ZIGGS_MCP_CHANNEL?: string | undefined;
66
73
  }, {
67
74
  ZIGGS_OPERATOR_KEY: string;
68
75
  ZIGGS_API_URL?: string | undefined;
@@ -77,6 +84,7 @@ declare const envSchema: z.ZodObject<{
77
84
  ZIGGS_SESSION_NAME?: string | undefined;
78
85
  ZIGGS_MCP_DEBUG?: string | undefined;
79
86
  ZIGGS_MCP_CORE_ONLY?: string | undefined;
87
+ ZIGGS_MCP_CHANNEL?: string | undefined;
80
88
  }>;
81
89
  type EnvConfig = z.infer<typeof envSchema>;
82
90
  export interface ZiggsMcpConfig extends EnvConfig {
@@ -114,6 +122,11 @@ export interface ZiggsMcpConfig extends EnvConfig {
114
122
  * connect text, inside that text's budget.
115
123
  */
116
124
  eventsEnabled?: boolean;
125
+ /**
126
+ * Local only: declare the Claude Code channel and ring the session for new
127
+ * mail. Adds one line to the compact door's connect text.
128
+ */
129
+ channel?: boolean;
117
130
  }
118
131
  /** Load delegate credentials from the environment. */
119
132
  export declare function loadConfig(): ZiggsMcpConfig;
package/dist/config.js CHANGED
@@ -51,6 +51,12 @@ const envSchema = z.object({
51
51
  * cut cold-start deferred-loading (#7). Unset = all tools register.
52
52
  */
53
53
  ZIGGS_MCP_CORE_ONLY: z.string().optional(),
54
+ /**
55
+ * Set to 1/true/yes to ring an open Claude Code session when new mail
56
+ * arrives (a Claude Code channel). Claude Code must also load this server
57
+ * as a channel; see `inboxChannel.ts`.
58
+ */
59
+ ZIGGS_MCP_CHANNEL: z.string().optional(),
54
60
  });
55
61
  /** Parse a truthy env flag (1/true/yes) — shared by ZIGGS_MCP_DEBUG and ZIGGS_MCP_CORE_ONLY. */
56
62
  function parseBoolFlag(raw) {
@@ -75,6 +81,7 @@ export function loadConfig() {
75
81
  ZIGGS_SESSION_NAME: process.env.ZIGGS_SESSION_NAME,
76
82
  ZIGGS_MCP_DEBUG: process.env.ZIGGS_MCP_DEBUG,
77
83
  ZIGGS_MCP_CORE_ONLY: process.env.ZIGGS_MCP_CORE_ONLY,
84
+ ZIGGS_MCP_CHANNEL: process.env.ZIGGS_MCP_CHANNEL,
78
85
  };
79
86
  const parsed = envSchema.safeParse(raw);
80
87
  if (!parsed.success) {
@@ -108,6 +115,7 @@ export function loadConfig() {
108
115
  resolvedAgentId,
109
116
  debugTools: parseBoolFlag(parsed.data.ZIGGS_MCP_DEBUG),
110
117
  coreOnly: parseBoolFlag(parsed.data.ZIGGS_MCP_CORE_ONLY),
118
+ channel: parseBoolFlag(parsed.data.ZIGGS_MCP_CHANNEL),
111
119
  directConnection: !isDirectoryBoarded(decodeOperatorKeyClaims(parsed.data.ZIGGS_OPERATOR_KEY)),
112
120
  };
113
121
  }
@@ -8,7 +8,7 @@ ziggs_agreement_buy ziggs_agreement_bid ziggs_agreement_broker ziggs_agreement_r
8
8
  ziggs_agreement_handoff
9
9
  ziggs_agreement_claim ziggs_agreement_subcontract ziggs_marketplace_view
10
10
  ziggs_agreement_respond ziggs_agreement_revoke ziggs_agreement_counter ziggs_agreement_fulfill
11
- ziggs_inbox_changes ziggs_inbox_peek ziggs_inbox ziggs_inbox_claim ziggs_inbox_ack ziggs_inbox_park
11
+ ziggs_inbox_changes ziggs_inbox_peek ziggs_inbox ziggs_inbox_show ziggs_inbox_claim ziggs_inbox_ack ziggs_inbox_park
12
12
  ziggs_grant_list ziggs_context_expand_reach ziggs_context_discover_grantable
13
13
  ziggs_open ziggs_access_explain ziggs_context_read
14
14
  ziggs_artifact_record ziggs_artifact_list ziggs_artifact_find ziggs_artifact_share
@@ -0,0 +1,87 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * The inbox card: what is waiting on the person and the work in hand, drawn
4
+ * inside the chat by hosts that render MCP Apps views (Claude, ChatGPT,
5
+ * Cursor). A host that does not still gets the text result.
6
+ *
7
+ * The view changes nothing. Its buttons only ask the host to open a page on
8
+ * the Ziggs website, where the person acts signed in as themselves: a click
9
+ * inside the card would reach Ziggs on the assistant's key, and Ziggs could
10
+ * not tell the person clicked.
11
+ */
12
+ export declare const INBOX_CARD_URI = "ui://ziggs/inbox-card";
13
+ export declare const INBOX_CARD_MIME = "text/html;profile=mcp-app";
14
+ /** Tool `_meta` linking `ziggs_inbox_show` to the view. */
15
+ export declare const INBOX_CARD_TOOL_META: {
16
+ ui: {
17
+ resourceUri: string;
18
+ };
19
+ };
20
+ /** One request waiting on the person. */
21
+ export interface InboxCardRequest {
22
+ kind: string;
23
+ title: string;
24
+ who: string | null;
25
+ when: string | null;
26
+ url: string;
27
+ }
28
+ /** One task in hand. */
29
+ export interface InboxCardWork {
30
+ title: string;
31
+ /** `2/5` when the task has a plan, otherwise null. */
32
+ progress: string | null;
33
+ url: string | null;
34
+ }
35
+ export interface InboxCardData {
36
+ waitingOnYou: InboxCardRequest[];
37
+ /** Requests past the listed ones, which the card counts but does not show. */
38
+ moreWaiting: number;
39
+ yourWork: InboxCardWork[];
40
+ /** Everything, on the website. */
41
+ allUrl: string | null;
42
+ }
43
+ interface SessionPayload {
44
+ decisions?: Array<{
45
+ kind: string;
46
+ title: string;
47
+ subtitle: string | null;
48
+ proposedAtLabel: string | null;
49
+ appUrl: string;
50
+ }>;
51
+ activeWork?: Array<{
52
+ title: string;
53
+ planDone: number;
54
+ planTotal: number;
55
+ appUrl: string | null;
56
+ }>;
57
+ truncatedProposals?: number;
58
+ truncatedConnectionRequests?: number;
59
+ agreementsListAppUrl?: string;
60
+ sessionChatCard?: string;
61
+ notice?: string;
62
+ }
63
+ /** The card's data, from the session block `ziggs_inbox` already builds. */
64
+ export declare function inboxCardData(payload: SessionPayload): InboxCardData;
65
+ /**
66
+ * The tool result: the text a host without views shows (the session card the
67
+ * model already relays, or the one-line notice when nothing is waiting), and
68
+ * the same facts structured for the view.
69
+ */
70
+ export declare function inboxCardResult(payload: SessionPayload): {
71
+ content: {
72
+ type: "text";
73
+ text: string;
74
+ }[];
75
+ structuredContent: Record<string, unknown>;
76
+ };
77
+ /** Serve the view. It loads nothing from outside and calls no tools. */
78
+ export declare function registerInboxCardResource(server: McpServer): void;
79
+ /**
80
+ * The view. Plain DOM, text only: titles and names can be written by a
81
+ * counterparty, so nothing is ever parsed as HTML. It speaks the MCP Apps
82
+ * postMessage protocol: `ui/initialize`, then `ui/notifications/initialized`,
83
+ * then it draws whatever `ui/notifications/tool-result` carries, and its
84
+ * buttons send `ui/open-link`.
85
+ */
86
+ export declare const INBOX_CARD_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Ziggs</title>\n<style>\n :root { --fg:#1d1a20; --muted:#6b6470; --line:#e7e2ea; --bg:#ffffff; --brand:#7c3aed; --chip:#f4f1f6; }\n @media (prefers-color-scheme: dark) { :root { --fg:#f2eef4; --muted:#a59cab; --line:#332d38; --bg:#1b171e; --brand:#a78bfa; --chip:#29232d; } }\n * { box-sizing: border-box; }\n body { margin:0; padding:14px 16px; font:14px/1.45 system-ui,-apple-system,\"Segoe UI\",sans-serif; color:var(--fg); background:var(--bg); }\n h2 { margin:0 0 8px; font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); }\n section + section { margin-top:16px; }\n ul { list-style:none; margin:0; padding:0; }\n li { display:flex; align-items:center; gap:12px; padding:9px 0; border-top:1px solid var(--line); }\n li:first-child { border-top:0; }\n .text { flex:1; min-width:0; }\n .title { font-weight:600; overflow-wrap:anywhere; }\n .meta { color:var(--muted); font-size:12px; }\n .chip { display:inline-block; padding:1px 7px; border-radius:999px; background:var(--chip); color:var(--muted); font-size:11px; margin-left:6px; font-weight:500; }\n button { flex:none; min-height:32px; padding:0 12px; border-radius:8px; border:1px solid var(--line); background:transparent; color:var(--fg); font:inherit; font-size:13px; cursor:pointer; }\n button.primary { background:var(--brand); border-color:var(--brand); color:#fff; }\n .empty { color:var(--muted); }\n .all { margin-top:12px; }\n</style>\n</head>\n<body>\n<main id=\"root\"><p class=\"empty\">Loading your Ziggs inbox\u2026</p></main>\n<script>\n(function () {\n var nextId = 1, pending = {};\n function send(method, params) {\n var id = nextId++;\n window.parent.postMessage({ jsonrpc: \"2.0\", id: id, method: method, params: params || {} }, \"*\");\n return new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });\n }\n function notify(method, params) {\n window.parent.postMessage({ jsonrpc: \"2.0\", method: method, params: params || {} }, \"*\");\n }\n function el(tag, cls, text) {\n var node = document.createElement(tag);\n if (cls) node.className = cls;\n if (text != null) node.textContent = String(text);\n return node;\n }\n function open(url) {\n if (!url) return;\n send(\"ui/open-link\", { url: url }).catch(function () {});\n }\n function row(title, chip, meta, label, url, primary) {\n var li = el(\"li\"), text = el(\"div\", \"text\"), head = el(\"div\", \"title\", title);\n if (chip) head.appendChild(el(\"span\", \"chip\", chip));\n text.appendChild(head);\n if (meta) text.appendChild(el(\"div\", \"meta\", meta));\n li.appendChild(text);\n if (url) {\n var button = el(\"button\", primary ? \"primary\" : \"\", label);\n button.type = \"button\";\n button.addEventListener(\"click\", function () { open(url); });\n li.appendChild(button);\n }\n return li;\n }\n function kindLabel(kind) {\n return kind === \"link_request\" ? \"Link request\" : kind === \"proposal\" ? \"Proposal\" : \"\";\n }\n function render(data) {\n var root = document.getElementById(\"root\");\n root.textContent = \"\";\n data = data || {};\n var waiting = data.waitingOnYou || [], work = data.yourWork || [], more = data.moreWaiting || 0;\n if (!waiting.length && !more && !work.length) {\n root.appendChild(el(\"p\", \"empty\", \"Nothing is waiting on you in Ziggs.\"));\n }\n if (waiting.length || more) {\n var s1 = el(\"section\"), l1 = el(\"ul\");\n s1.appendChild(el(\"h2\", \"\", \"Waiting on you\"));\n waiting.forEach(function (item) {\n var meta = [item.who, item.when].filter(Boolean).join(\" \u00B7 \");\n l1.appendChild(row(item.title, kindLabel(item.kind), meta, \"Review\", item.url, true));\n });\n s1.appendChild(l1);\n if (more) s1.appendChild(el(\"p\", \"meta\", more + \" more on the website\"));\n root.appendChild(s1);\n }\n if (work.length) {\n var s2 = el(\"section\"), l2 = el(\"ul\");\n s2.appendChild(el(\"h2\", \"\", \"Your work\"));\n work.forEach(function (item) {\n l2.appendChild(row(item.title, null, item.progress ? \"Plan \" + item.progress : \"In progress\", \"Open\", item.url, false));\n });\n s2.appendChild(l2);\n root.appendChild(s2);\n }\n if (data.allUrl) {\n var all = el(\"button\", \"all\", \"Open Ziggs\");\n all.type = \"button\";\n all.addEventListener(\"click\", function () { open(data.allUrl); });\n root.appendChild(all);\n }\n }\n window.addEventListener(\"message\", function (event) {\n if (event.source !== window.parent) return;\n var msg = event.data;\n if (!msg || msg.jsonrpc !== \"2.0\") return;\n if (msg.id != null && pending[msg.id] && !msg.method) {\n var waiter = pending[msg.id];\n delete pending[msg.id];\n if (msg.error) waiter.reject(msg.error); else waiter.resolve(msg.result);\n return;\n }\n if (msg.method === \"ui/notifications/tool-result\") {\n render((msg.params || {}).structuredContent);\n }\n });\n send(\"ui/initialize\", {\n protocolVersion: \"2026-01-26\",\n appInfo: { name: \"Ziggs inbox card\", version: \"1\" },\n appCapabilities: {}\n }).then(function () { notify(\"ui/notifications/initialized\", {}); }, function () {});\n})();\n</script>\n</body>\n</html>\n";
87
+ export {};
@@ -0,0 +1,195 @@
1
+ /**
2
+ * The inbox card: what is waiting on the person and the work in hand, drawn
3
+ * inside the chat by hosts that render MCP Apps views (Claude, ChatGPT,
4
+ * Cursor). A host that does not still gets the text result.
5
+ *
6
+ * The view changes nothing. Its buttons only ask the host to open a page on
7
+ * the Ziggs website, where the person acts signed in as themselves: a click
8
+ * inside the card would reach Ziggs on the assistant's key, and Ziggs could
9
+ * not tell the person clicked.
10
+ */
11
+ export const INBOX_CARD_URI = 'ui://ziggs/inbox-card';
12
+ export const INBOX_CARD_MIME = 'text/html;profile=mcp-app';
13
+ /** Tool `_meta` linking `ziggs_inbox_show` to the view. */
14
+ export const INBOX_CARD_TOOL_META = { ui: { resourceUri: INBOX_CARD_URI } };
15
+ /** The card's data, from the session block `ziggs_inbox` already builds. */
16
+ export function inboxCardData(payload) {
17
+ return {
18
+ waitingOnYou: (payload.decisions ?? []).map((item) => ({
19
+ kind: item.kind,
20
+ title: item.title,
21
+ who: item.subtitle,
22
+ when: item.proposedAtLabel,
23
+ url: item.appUrl,
24
+ })),
25
+ moreWaiting: (payload.truncatedProposals ?? 0) + (payload.truncatedConnectionRequests ?? 0),
26
+ yourWork: (payload.activeWork ?? []).map((item) => ({
27
+ title: item.title,
28
+ progress: item.planTotal > 0 ? `${item.planDone}/${item.planTotal}` : null,
29
+ url: item.appUrl,
30
+ })),
31
+ allUrl: payload.agreementsListAppUrl ?? null,
32
+ };
33
+ }
34
+ /**
35
+ * The tool result: the text a host without views shows (the session card the
36
+ * model already relays, or the one-line notice when nothing is waiting), and
37
+ * the same facts structured for the view.
38
+ */
39
+ export function inboxCardResult(payload) {
40
+ const text = payload.sessionChatCard || payload.notice || 'Nothing is waiting on you.';
41
+ return {
42
+ content: [{ type: 'text', text }],
43
+ structuredContent: inboxCardData(payload),
44
+ };
45
+ }
46
+ /** Serve the view. It loads nothing from outside and calls no tools. */
47
+ export function registerInboxCardResource(server) {
48
+ server.registerResource('ziggs-inbox-card', INBOX_CARD_URI, {
49
+ title: 'Ziggs inbox card',
50
+ description: "What is waiting on you and the work you have in hand.",
51
+ mimeType: INBOX_CARD_MIME,
52
+ _meta: { ui: { prefersBorder: true } },
53
+ }, async () => ({
54
+ contents: [
55
+ {
56
+ uri: INBOX_CARD_URI,
57
+ mimeType: INBOX_CARD_MIME,
58
+ text: INBOX_CARD_HTML,
59
+ _meta: { ui: { prefersBorder: true } },
60
+ },
61
+ ],
62
+ }));
63
+ }
64
+ /**
65
+ * The view. Plain DOM, text only: titles and names can be written by a
66
+ * counterparty, so nothing is ever parsed as HTML. It speaks the MCP Apps
67
+ * postMessage protocol: `ui/initialize`, then `ui/notifications/initialized`,
68
+ * then it draws whatever `ui/notifications/tool-result` carries, and its
69
+ * buttons send `ui/open-link`.
70
+ */
71
+ export const INBOX_CARD_HTML = `<!doctype html>
72
+ <html lang="en">
73
+ <head>
74
+ <meta charset="utf-8">
75
+ <meta name="viewport" content="width=device-width, initial-scale=1">
76
+ <title>Ziggs</title>
77
+ <style>
78
+ :root { --fg:#1d1a20; --muted:#6b6470; --line:#e7e2ea; --bg:#ffffff; --brand:#7c3aed; --chip:#f4f1f6; }
79
+ @media (prefers-color-scheme: dark) { :root { --fg:#f2eef4; --muted:#a59cab; --line:#332d38; --bg:#1b171e; --brand:#a78bfa; --chip:#29232d; } }
80
+ * { box-sizing: border-box; }
81
+ body { margin:0; padding:14px 16px; font:14px/1.45 system-ui,-apple-system,"Segoe UI",sans-serif; color:var(--fg); background:var(--bg); }
82
+ h2 { margin:0 0 8px; font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); }
83
+ section + section { margin-top:16px; }
84
+ ul { list-style:none; margin:0; padding:0; }
85
+ li { display:flex; align-items:center; gap:12px; padding:9px 0; border-top:1px solid var(--line); }
86
+ li:first-child { border-top:0; }
87
+ .text { flex:1; min-width:0; }
88
+ .title { font-weight:600; overflow-wrap:anywhere; }
89
+ .meta { color:var(--muted); font-size:12px; }
90
+ .chip { display:inline-block; padding:1px 7px; border-radius:999px; background:var(--chip); color:var(--muted); font-size:11px; margin-left:6px; font-weight:500; }
91
+ button { flex:none; min-height:32px; padding:0 12px; border-radius:8px; border:1px solid var(--line); background:transparent; color:var(--fg); font:inherit; font-size:13px; cursor:pointer; }
92
+ button.primary { background:var(--brand); border-color:var(--brand); color:#fff; }
93
+ .empty { color:var(--muted); }
94
+ .all { margin-top:12px; }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <main id="root"><p class="empty">Loading your Ziggs inbox…</p></main>
99
+ <script>
100
+ (function () {
101
+ var nextId = 1, pending = {};
102
+ function send(method, params) {
103
+ var id = nextId++;
104
+ window.parent.postMessage({ jsonrpc: "2.0", id: id, method: method, params: params || {} }, "*");
105
+ return new Promise(function (resolve, reject) { pending[id] = { resolve: resolve, reject: reject }; });
106
+ }
107
+ function notify(method, params) {
108
+ window.parent.postMessage({ jsonrpc: "2.0", method: method, params: params || {} }, "*");
109
+ }
110
+ function el(tag, cls, text) {
111
+ var node = document.createElement(tag);
112
+ if (cls) node.className = cls;
113
+ if (text != null) node.textContent = String(text);
114
+ return node;
115
+ }
116
+ function open(url) {
117
+ if (!url) return;
118
+ send("ui/open-link", { url: url }).catch(function () {});
119
+ }
120
+ function row(title, chip, meta, label, url, primary) {
121
+ var li = el("li"), text = el("div", "text"), head = el("div", "title", title);
122
+ if (chip) head.appendChild(el("span", "chip", chip));
123
+ text.appendChild(head);
124
+ if (meta) text.appendChild(el("div", "meta", meta));
125
+ li.appendChild(text);
126
+ if (url) {
127
+ var button = el("button", primary ? "primary" : "", label);
128
+ button.type = "button";
129
+ button.addEventListener("click", function () { open(url); });
130
+ li.appendChild(button);
131
+ }
132
+ return li;
133
+ }
134
+ function kindLabel(kind) {
135
+ return kind === "link_request" ? "Link request" : kind === "proposal" ? "Proposal" : "";
136
+ }
137
+ function render(data) {
138
+ var root = document.getElementById("root");
139
+ root.textContent = "";
140
+ data = data || {};
141
+ var waiting = data.waitingOnYou || [], work = data.yourWork || [], more = data.moreWaiting || 0;
142
+ if (!waiting.length && !more && !work.length) {
143
+ root.appendChild(el("p", "empty", "Nothing is waiting on you in Ziggs."));
144
+ }
145
+ if (waiting.length || more) {
146
+ var s1 = el("section"), l1 = el("ul");
147
+ s1.appendChild(el("h2", "", "Waiting on you"));
148
+ waiting.forEach(function (item) {
149
+ var meta = [item.who, item.when].filter(Boolean).join(" · ");
150
+ l1.appendChild(row(item.title, kindLabel(item.kind), meta, "Review", item.url, true));
151
+ });
152
+ s1.appendChild(l1);
153
+ if (more) s1.appendChild(el("p", "meta", more + " more on the website"));
154
+ root.appendChild(s1);
155
+ }
156
+ if (work.length) {
157
+ var s2 = el("section"), l2 = el("ul");
158
+ s2.appendChild(el("h2", "", "Your work"));
159
+ work.forEach(function (item) {
160
+ l2.appendChild(row(item.title, null, item.progress ? "Plan " + item.progress : "In progress", "Open", item.url, false));
161
+ });
162
+ s2.appendChild(l2);
163
+ root.appendChild(s2);
164
+ }
165
+ if (data.allUrl) {
166
+ var all = el("button", "all", "Open Ziggs");
167
+ all.type = "button";
168
+ all.addEventListener("click", function () { open(data.allUrl); });
169
+ root.appendChild(all);
170
+ }
171
+ }
172
+ window.addEventListener("message", function (event) {
173
+ if (event.source !== window.parent) return;
174
+ var msg = event.data;
175
+ if (!msg || msg.jsonrpc !== "2.0") return;
176
+ if (msg.id != null && pending[msg.id] && !msg.method) {
177
+ var waiter = pending[msg.id];
178
+ delete pending[msg.id];
179
+ if (msg.error) waiter.reject(msg.error); else waiter.resolve(msg.result);
180
+ return;
181
+ }
182
+ if (msg.method === "ui/notifications/tool-result") {
183
+ render((msg.params || {}).structuredContent);
184
+ }
185
+ });
186
+ send("ui/initialize", {
187
+ protocolVersion: "2026-01-26",
188
+ appInfo: { name: "Ziggs inbox card", version: "1" },
189
+ appCapabilities: {}
190
+ }).then(function () { notify("ui/notifications/initialized", {}); }, function () {});
191
+ })();
192
+ </script>
193
+ </body>
194
+ </html>
195
+ `;
@@ -0,0 +1,51 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { InboxClient, type Creds } from '@ziggs-ai/api-client';
3
+ export type InboxChangesOptions = NonNullable<Parameters<InboxClient['getChanges']>[0]>;
4
+ export type InboxChangesResult = Awaited<ReturnType<InboxClient['getChanges']>>;
5
+ /**
6
+ * The Claude Code channel: an open Claude Code session hears new Ziggs mail
7
+ * within seconds, without polling a tool.
8
+ *
9
+ * A local (stdio) server that declares `experimental['claude/channel']` may
10
+ * push `notifications/claude/channel`; Claude Code puts each one in front of
11
+ * the model as a `<channel>` tag. The server holds the inbox observation
12
+ * long-poll (`/inbox/changes`) and rings once per new delivery.
13
+ *
14
+ * It rings, it does not deliver. An event carries references only (kind and
15
+ * ids), never message text: what a counterparty wrote would otherwise land in
16
+ * the session as if the person had typed it. The model reads the mail through
17
+ * the inbox tools, so claiming, acknowledging and who handles the work stay
18
+ * exactly where they are. Observation itself never claims or acknowledges.
19
+ *
20
+ * Off unless `ZIGGS_MCP_CHANNEL` is set, and Claude Code also has to load the
21
+ * server as a channel (`--dangerously-load-development-channels server:<name>`
22
+ * while channels are a research preview). Without that, Claude Code drops the
23
+ * events and the long-poll costs no tokens.
24
+ */
25
+ export declare const CHANNEL_CAPABILITY: {
26
+ 'claude/channel': {};
27
+ };
28
+ export declare const CHANNEL_NOTIFICATION = "notifications/claude/channel";
29
+ type Delivery = InboxChangesResult['deliveries'][number];
30
+ type Observe = (options: InboxChangesOptions) => Promise<InboxChangesResult>;
31
+ export interface ChannelEvent {
32
+ content: string;
33
+ meta: Record<string, string>;
34
+ }
35
+ /** The observation read for these credentials: the agent's, or the connection's. */
36
+ export declare function observeFor(creds: Creds): Observe;
37
+ /** One delivery as a channel event: what arrived and where, never what it says. */
38
+ export declare function channelEvent(delivery: Delivery, agentId?: string): ChannelEvent;
39
+ /** Mail that was already waiting when the session started, as one event. */
40
+ export declare function backlogEvent(count: number): ChannelEvent;
41
+ /**
42
+ * Watch the inbox and ring the session for each new delivery. Starts by
43
+ * folding whatever was already waiting into one event, so a session does not
44
+ * open to a wall of tags. Returns a stop function.
45
+ */
46
+ export declare function startInboxChannel(server: McpServer, observe: Observe, options?: {
47
+ agentId?: string;
48
+ holdSeconds?: number;
49
+ log?: (line: string) => void;
50
+ }): () => void;
51
+ export {};