@ziggs-ai/ziggs-mcp 0.37.20 → 0.37.22
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 +9 -0
- package/dist/inboxCard.d.ts +1 -1
- package/dist/inboxCard.js +34 -2
- package/dist/protocol/connectText.d.ts +1 -2
- package/dist/protocol/connectText.js +1 -4
- package/dist/surface.d.ts +6 -18
- package/dist/surface.js +15 -59
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.37.22
|
|
6
|
+
|
|
7
|
+
- The MCP registry entry reads like the other listings: title ZiggsAI, a description for someone who has never heard of Ziggs, and the app icon. The tools and their behavior are unchanged.
|
|
8
|
+
|
|
9
|
+
## 0.37.21
|
|
10
|
+
|
|
11
|
+
- The inbox card reports its size to the host. Without it a host left the frame at zero height, so the card was drawn but never seen.
|
|
12
|
+
- A key-based (operator key) connection now lists every tool by name, like the OAuth connection, instead of the everyday set behind a catalog. `ziggs_tools`, `ziggs_tool_read` and `ziggs_tool_write` stay listed and work as before, so a client that mounts only an allow-list still reaches the rest.
|
|
13
|
+
|
|
5
14
|
## 0.37.20
|
|
6
15
|
|
|
7
16
|
- 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.
|
package/dist/inboxCard.d.ts
CHANGED
|
@@ -83,5 +83,5 @@ export declare function registerInboxCardResource(server: McpServer): void;
|
|
|
83
83
|
* then it draws whatever `ui/notifications/tool-result` carries, and its
|
|
84
84
|
* buttons send `ui/open-link`.
|
|
85
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 () {
|
|
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 = {}, remeasure = null;\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 if (remeasure) remeasure();\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 // A host sizes the frame from what the view reports. Without a report the\n // frame stays at zero height and the card is drawn but never seen.\n function reportSize() {\n var lastWidth = 0, lastHeight = 0, queued = false;\n function measure() {\n if (queued) return;\n queued = true;\n var next = typeof requestAnimationFrame === \"function\" ? requestAnimationFrame : function (fn) { setTimeout(fn, 16); };\n next(function () {\n queued = false;\n var doc = document.documentElement, previous = doc.style.height;\n doc.style.height = \"max-content\";\n var height = Math.ceil(doc.getBoundingClientRect().height);\n doc.style.height = previous;\n var width = Math.ceil(window.innerWidth);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width; lastHeight = height;\n notify(\"ui/notifications/size-changed\", { width: width, height: height });\n });\n }\n remeasure = measure;\n measure();\n if (typeof ResizeObserver === \"function\") {\n var observer = new ResizeObserver(measure);\n observer.observe(document.documentElement);\n observer.observe(document.body);\n }\n }\n send(\"ui/initialize\", {\n protocolVersion: \"2026-01-26\",\n appInfo: { name: \"Ziggs inbox card\", version: \"1\" },\n appCapabilities: {}\n }).then(function () {\n notify(\"ui/notifications/initialized\", {});\n reportSize();\n }, function () {});\n})();\n</script>\n</body>\n</html>\n";
|
|
87
87
|
export {};
|
package/dist/inboxCard.js
CHANGED
|
@@ -98,7 +98,7 @@ export const INBOX_CARD_HTML = `<!doctype html>
|
|
|
98
98
|
<main id="root"><p class="empty">Loading your Ziggs inbox…</p></main>
|
|
99
99
|
<script>
|
|
100
100
|
(function () {
|
|
101
|
-
var nextId = 1, pending = {};
|
|
101
|
+
var nextId = 1, pending = {}, remeasure = null;
|
|
102
102
|
function send(method, params) {
|
|
103
103
|
var id = nextId++;
|
|
104
104
|
window.parent.postMessage({ jsonrpc: "2.0", id: id, method: method, params: params || {} }, "*");
|
|
@@ -168,6 +168,7 @@ export const INBOX_CARD_HTML = `<!doctype html>
|
|
|
168
168
|
all.addEventListener("click", function () { open(data.allUrl); });
|
|
169
169
|
root.appendChild(all);
|
|
170
170
|
}
|
|
171
|
+
if (remeasure) remeasure();
|
|
171
172
|
}
|
|
172
173
|
window.addEventListener("message", function (event) {
|
|
173
174
|
if (event.source !== window.parent) return;
|
|
@@ -183,11 +184,42 @@ export const INBOX_CARD_HTML = `<!doctype html>
|
|
|
183
184
|
render((msg.params || {}).structuredContent);
|
|
184
185
|
}
|
|
185
186
|
});
|
|
187
|
+
// A host sizes the frame from what the view reports. Without a report the
|
|
188
|
+
// frame stays at zero height and the card is drawn but never seen.
|
|
189
|
+
function reportSize() {
|
|
190
|
+
var lastWidth = 0, lastHeight = 0, queued = false;
|
|
191
|
+
function measure() {
|
|
192
|
+
if (queued) return;
|
|
193
|
+
queued = true;
|
|
194
|
+
var next = typeof requestAnimationFrame === "function" ? requestAnimationFrame : function (fn) { setTimeout(fn, 16); };
|
|
195
|
+
next(function () {
|
|
196
|
+
queued = false;
|
|
197
|
+
var doc = document.documentElement, previous = doc.style.height;
|
|
198
|
+
doc.style.height = "max-content";
|
|
199
|
+
var height = Math.ceil(doc.getBoundingClientRect().height);
|
|
200
|
+
doc.style.height = previous;
|
|
201
|
+
var width = Math.ceil(window.innerWidth);
|
|
202
|
+
if (width === lastWidth && height === lastHeight) return;
|
|
203
|
+
lastWidth = width; lastHeight = height;
|
|
204
|
+
notify("ui/notifications/size-changed", { width: width, height: height });
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
remeasure = measure;
|
|
208
|
+
measure();
|
|
209
|
+
if (typeof ResizeObserver === "function") {
|
|
210
|
+
var observer = new ResizeObserver(measure);
|
|
211
|
+
observer.observe(document.documentElement);
|
|
212
|
+
observer.observe(document.body);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
186
215
|
send("ui/initialize", {
|
|
187
216
|
protocolVersion: "2026-01-26",
|
|
188
217
|
appInfo: { name: "Ziggs inbox card", version: "1" },
|
|
189
218
|
appCapabilities: {}
|
|
190
|
-
}).then(function () {
|
|
219
|
+
}).then(function () {
|
|
220
|
+
notify("ui/notifications/initialized", {});
|
|
221
|
+
reportSize();
|
|
222
|
+
}, function () {});
|
|
191
223
|
})();
|
|
192
224
|
</script>
|
|
193
225
|
</body>
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The same text for every client. The two doors differ only where their tools
|
|
12
12
|
* do: the directory door's inbox read observes and a claim takes the lease,
|
|
13
|
-
* while the compact door's full read takes it
|
|
14
|
-
* reached through the catalog. Everything else an agent needs is said where it
|
|
13
|
+
* while the compact door's full read takes it. Everything else an agent needs is said where it
|
|
15
14
|
* applies — in the description of the tool it is about, as a fact about that
|
|
16
15
|
* tool, or in the refusal that teaches it at the moment it matters. The long
|
|
17
16
|
* protocol (`delegateProtocol.ts`) stays the source for SKILL.md and
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The same text for every client. The two doors differ only where their tools
|
|
12
12
|
* do: the directory door's inbox read observes and a claim takes the lease,
|
|
13
|
-
* while the compact door's full read takes it
|
|
14
|
-
* reached through the catalog. Everything else an agent needs is said where it
|
|
13
|
+
* while the compact door's full read takes it. Everything else an agent needs is said where it
|
|
15
14
|
* applies — in the description of the tool it is about, as a fact about that
|
|
16
15
|
* tool, or in the refusal that teaches it at the moment it matters. The long
|
|
17
16
|
* protocol (`delegateProtocol.ts`) stays the source for SKILL.md and
|
|
@@ -46,7 +45,6 @@ const RULES = [
|
|
|
46
45
|
export function sessionRules(door) {
|
|
47
46
|
return [DATA, INBOX[door], WAIT, ACCESS, REPLY, GROUNDING];
|
|
48
47
|
}
|
|
49
|
-
const CATALOG = 'Only the everyday tools are loaded. ziggs_tools lists the rest; call them through ziggs_tool_read or ziggs_tool_write.';
|
|
50
48
|
/**
|
|
51
49
|
* Directory door only: inbox events ride the connection key that door is
|
|
52
50
|
* minted for. Said only when the serving backend has events on, so a host is
|
|
@@ -66,7 +64,6 @@ export function renderConnectText(door, options = {}) {
|
|
|
66
64
|
`- ${SESSION}`,
|
|
67
65
|
`- ${INBOX[door]}`,
|
|
68
66
|
...RULES.map((r) => `- ${r}`),
|
|
69
|
-
...(door === 'compact' ? [`- ${CATALOG}`] : []),
|
|
70
67
|
...(door === 'directory' && options.events ? [`- ${EVENTS}`] : []),
|
|
71
68
|
...(door === 'compact' && options.channel ? [`- ${CHANNEL}`] : []),
|
|
72
69
|
].join('\n');
|
package/dist/surface.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export declare const NATIVE_TOOLS: readonly string[];
|
|
|
41
41
|
* when it refuses.
|
|
42
42
|
*/
|
|
43
43
|
export declare const CATALOG_TOOLS: readonly string[];
|
|
44
|
-
/** Per-tool how-to-call,
|
|
44
|
+
/** Per-tool how-to-call: every tool is listed, so by name, or through its lane's dispatcher. */
|
|
45
45
|
export declare function callLine(entry: CatalogEntry): string;
|
|
46
46
|
/**
|
|
47
47
|
* Drop the `$schema` line the SDK writes into every tool's input and output
|
|
@@ -52,24 +52,12 @@ export declare function callLine(entry: CatalogEntry): string;
|
|
|
52
52
|
*/
|
|
53
53
|
export declare function listWithoutSchemaDialect(server: McpServer): void;
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
55
|
+
* Register the catalog and one dispatcher per annotation lane, next to every
|
|
56
|
+
* tool listed by name.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* `Tool <name> disabled`, thrown from inside its own handler where we cannot
|
|
62
|
-
* reword it — so a caller holding a name (a saved config, a script, a model
|
|
63
|
-
* that learned the name in an earlier session) hits a wall that does not say
|
|
64
|
-
* the tool exists, where it went, or how to reach it.
|
|
65
|
-
*
|
|
66
|
-
* So every tool stays ENABLED, and the listing is narrowed instead. A name is
|
|
67
|
-
* always callable; the list is still the native set plus the catalog tools.
|
|
68
|
-
*
|
|
69
|
-
* The listing delegates to the handler the SDK installed rather than rebuilding
|
|
70
|
-
* one: the SDK owns how a registered tool becomes a `tools/list` entry, and
|
|
71
|
-
* hand-building that response would risk a schema-shape difference on exactly
|
|
72
|
-
* the tools every caller loads. We filter its output and change nothing else.
|
|
58
|
+
* The catalog answers by search and by plain-sentence need, and the
|
|
59
|
+
* dispatchers reach any tool by name, so a client that mounts only part of
|
|
60
|
+
* the list (an allow-list) still reaches the rest.
|
|
73
61
|
*/
|
|
74
62
|
export declare function applySurfacePolicy(server: McpServer, opts?: {
|
|
75
63
|
/** Answers `ziggs_tools need=`. Absent: `need` falls back to the full list. */
|
package/dist/surface.js
CHANGED
|
@@ -63,7 +63,6 @@ export const CATALOG_TOOLS = [
|
|
|
63
63
|
TOOL_READ_NAME,
|
|
64
64
|
TOOL_WRITE_NAME,
|
|
65
65
|
];
|
|
66
|
-
const CATALOG_TOOL_NAMES = new Set(CATALOG_TOOLS);
|
|
67
66
|
/**
|
|
68
67
|
* A tool that takes a freeform target and payload has to say which API it
|
|
69
68
|
* reaches; a store reviewer cannot otherwise tell what it does.
|
|
@@ -77,21 +76,15 @@ const TOOL_WRITE_DESCRIPTION = `Call a Ziggs tool that creates or changes someth
|
|
|
77
76
|
'This reaches every writing tool ziggs_tools lists, native or not. Read-only tools are refused here and pointed at ziggs_tool_read. Most writes cannot be taken back (a sent message, a taken decision, an overwritten plan), so a call through here may ask the human first. ' +
|
|
78
77
|
'Look the tool up with ziggs_tools describe=[<name>] first when you do not already know its parameters: `args` is validated against the real schema and a wrong key is refused, not silently dropped. ' +
|
|
79
78
|
'The result is exactly what the tool itself returns.';
|
|
80
|
-
|
|
81
|
-
return NATIVE_TOOLS.includes(name);
|
|
82
|
-
}
|
|
83
|
-
/** Per-tool how-to-call, derived from NATIVE_TOOLS and the annotations. */
|
|
79
|
+
/** Per-tool how-to-call: every tool is listed, so by name, or through its lane's dispatcher. */
|
|
84
80
|
export function callLine(entry) {
|
|
85
|
-
|
|
86
|
-
if (isNative(entry.name)) {
|
|
87
|
-
return `call it by name: ${entry.name}; ${dispatcher} { tool: "${entry.name}", args } also works`;
|
|
88
|
-
}
|
|
89
|
-
return `call via ${dispatcher} { tool, args }`;
|
|
81
|
+
return `call it by name: ${entry.name}; ${laneOf(entry)} { tool: "${entry.name}", args } also works`;
|
|
90
82
|
}
|
|
91
83
|
function withCall(entry, base) {
|
|
92
84
|
return {
|
|
93
85
|
...base,
|
|
94
|
-
|
|
86
|
+
// Every tool is listed by name now, so every row is native.
|
|
87
|
+
native: true,
|
|
95
88
|
call: callLine(entry),
|
|
96
89
|
};
|
|
97
90
|
}
|
|
@@ -102,35 +95,6 @@ function sortRows(entries) {
|
|
|
102
95
|
function laneOf(entry) {
|
|
103
96
|
return entry.annotations.readOnlyHint ? TOOL_READ_NAME : TOOL_WRITE_NAME;
|
|
104
97
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Keep `tools/list` to `listed` (plus the catalog tools, registered after this
|
|
107
|
-
* runs) by filtering the response the SDK's own handler builds.
|
|
108
|
-
*
|
|
109
|
-
* The handler is read back off the underlying `Server` so the SDK stays the
|
|
110
|
-
* author of every entry. That map is internal, so this asserts rather than
|
|
111
|
-
* degrades: if a future SDK stops keeping handlers there, the server refuses to
|
|
112
|
-
* start and says why. A silent fallback would ship the whole 58-tool surface to
|
|
113
|
-
* every caller — the regression this policy exists to prevent — and it would
|
|
114
|
-
* look like it worked.
|
|
115
|
-
*/
|
|
116
|
-
function listOnly(server, listed) {
|
|
117
|
-
const inner = server.server;
|
|
118
|
-
const built = inner._requestHandlers?.get('tools/list');
|
|
119
|
-
if (!built) {
|
|
120
|
-
throw new Error('ziggs-mcp: the MCP SDK no longer exposes its tools/list handler at ' +
|
|
121
|
-
"server._requestHandlers.get('tools/list'), so the surface cannot be " +
|
|
122
|
-
'narrowed to the native set. Fix surface.ts before shipping: without ' +
|
|
123
|
-
'this filter every tool is listed and a cold connection pays for the ' +
|
|
124
|
-
'whole catalog.');
|
|
125
|
-
}
|
|
126
|
-
server.server.setRequestHandler(ListToolsRequestSchema, async (req, extra) => {
|
|
127
|
-
const all = await built(req, extra);
|
|
128
|
-
return {
|
|
129
|
-
...all,
|
|
130
|
-
tools: all.tools.filter((t) => listed.has(t.name) || CATALOG_TOOL_NAMES.has(t.name)),
|
|
131
|
-
};
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
98
|
/**
|
|
135
99
|
* Drop the `$schema` line the SDK writes into every tool's input and output
|
|
136
100
|
* schema. It names a JSON Schema dialect no MCP client reads, and on the
|
|
@@ -210,28 +174,20 @@ function registerDispatcher(server, lane) {
|
|
|
210
174
|
});
|
|
211
175
|
}
|
|
212
176
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* Narrowing by `.disable()` would be the shorter way to do this, and it is the
|
|
217
|
-
* wrong one: disabling takes a tool out of `tools/list` AND out of
|
|
218
|
-
* `tools/call`. The SDK answers a direct call on a disabled tool with
|
|
219
|
-
* `Tool <name> disabled`, thrown from inside its own handler where we cannot
|
|
220
|
-
* reword it — so a caller holding a name (a saved config, a script, a model
|
|
221
|
-
* that learned the name in an earlier session) hits a wall that does not say
|
|
222
|
-
* the tool exists, where it went, or how to reach it.
|
|
223
|
-
*
|
|
224
|
-
* So every tool stays ENABLED, and the listing is narrowed instead. A name is
|
|
225
|
-
* always callable; the list is still the native set plus the catalog tools.
|
|
177
|
+
* Register the catalog and one dispatcher per annotation lane, next to every
|
|
178
|
+
* tool listed by name.
|
|
226
179
|
*
|
|
227
|
-
* The
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* the tools every caller loads. We filter its output and change nothing else.
|
|
180
|
+
* The catalog answers by search and by plain-sentence need, and the
|
|
181
|
+
* dispatchers reach any tool by name, so a client that mounts only part of
|
|
182
|
+
* the list (an allow-list) still reaches the rest.
|
|
231
183
|
*/
|
|
232
184
|
export function applySurfacePolicy(server, opts = {}) {
|
|
233
|
-
|
|
234
|
-
|
|
185
|
+
// Every tool is listed, as on the directory door. Listing everything costs
|
|
186
|
+
// the same or less per task, because a tool outside the everyday set no
|
|
187
|
+
// longer needs a catalog call first, and a host's permission prompt names
|
|
188
|
+
// the real tool.
|
|
189
|
+
// The catalog stays listed for clients that mount only an allow-list (the
|
|
190
|
+
// Copilot coding agent) and reach the rest through it.
|
|
235
191
|
registerStrictTool(server, TOOLS_CATALOG_NAME, {
|
|
236
192
|
describe: z
|
|
237
193
|
.array(z.string())
|
package/package.json
CHANGED