@zackbart/connecta 0.12.1 → 0.13.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/CHANGELOG.md +173 -0
- package/README.md +4 -1
- package/dist/apps-shell.d.ts +13 -11
- package/dist/apps-shell.d.ts.map +1 -1
- package/dist/apps-shell.js +221 -30
- package/dist/apps-shell.js.map +1 -1
- package/dist/catalog-service.d.ts +41 -0
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +94 -5
- package/dist/catalog-service.js.map +1 -1
- package/dist/connectors/api.d.ts +5 -4
- package/dist/connectors/api.d.ts.map +1 -1
- package/dist/connectors/api.js.map +1 -1
- package/dist/connectors/remote-mcp.d.ts +5 -4
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/execute.d.ts +12 -4
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +142 -20
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +14 -4
- package/dist/meta-tools.js.map +1 -1
- package/dist/providers/mixpanel.d.ts +21 -0
- package/dist/providers/mixpanel.d.ts.map +1 -0
- package/dist/providers/mixpanel.js +183 -0
- package/dist/providers/mixpanel.js.map +1 -0
- package/dist/skills.d.ts +7 -9
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +60 -25
- package/dist/skills.js.map +1 -1
- package/dist/types.d.ts +26 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/code-mode.md +32 -20
- package/documentation/connectors.md +116 -4
- package/documentation/mcp-ui-design.md +8 -8
- package/documentation/meta-tools.md +80 -8
- package/documentation/mixpanel.md +72 -0
- package/documentation/program-ui-read-calls.md +213 -0
- package/ethos.md +10 -4
- package/package.json +5 -1
- package/src/apps-shell.ts +221 -30
- package/src/catalog-service.ts +139 -4
- package/src/connectors/api.ts +5 -3
- package/src/connectors/remote-mcp.ts +5 -3
- package/src/execute.ts +215 -21
- package/src/index.ts +1 -0
- package/src/meta-tools.ts +19 -4
- package/src/providers/mixpanel.ts +220 -0
- package/src/skills.ts +66 -24
- package/src/types.ts +27 -6
- package/src/version.ts +1 -1
- package/templates/node/package.json +1 -1
package/src/apps-shell.ts
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A build-time string constant, not a file read at startup: the core is
|
|
5
5
|
* Web-API-only so it runs unchanged on Workers, and the same bytes have to
|
|
6
|
-
* serve everywhere.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* serve everywhere. It renders whatever HTML a program handed `connecta.ui`
|
|
7
|
+
* inside a nested `srcdoc` frame. The one-argument form forwards no channel;
|
|
8
|
+
* an explicitly bound view gets only named read calls through the trusted
|
|
9
|
+
* shell, never a raw host channel.
|
|
10
10
|
*
|
|
11
11
|
* The address carries a version segment because hosts are permitted to
|
|
12
|
-
* prefetch and cache templates by URI: change these bytes, bump
|
|
12
|
+
* prefetch and cache templates by URI: change these bytes, bump the version.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/** The only `ui://` URI in the system. No program input reaches it. */
|
|
16
|
-
export const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/
|
|
16
|
+
export const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v2";
|
|
17
17
|
|
|
18
18
|
/** The mimeType the Apps spec requires of an HTML template. */
|
|
19
19
|
export const PROGRAM_UI_MIME_TYPE = "text/html;profile=mcp-app";
|
|
@@ -34,10 +34,12 @@ export const MCP_APPS_EXTENSION = "io.modelcontextprotocol/ui";
|
|
|
34
34
|
* Apps postMessage dialect (`ui/initialize`, `ui/notifications/initialized`,
|
|
35
35
|
* `ui/notifications/tool-result`, `ui/notifications/size-changed`,
|
|
36
36
|
* `ui/resource-teardown`), lifts `_meta["connecta/ui"].html` out of the
|
|
37
|
-
* delivered tool result, and puts it in a frame.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* delivered tool result, and puts it in a frame. An optional read manifest
|
|
38
|
+
* installs one narrow `connecta.read(name, args)` bridge in that inner frame;
|
|
39
|
+
* the outer shell maps declared names to the existing `call_tool` meta-tool.
|
|
40
|
+
* It declares no CSP domains, so the host applies its restrictive default and
|
|
41
|
+
* the `srcdoc` frame inherits `default-src 'none'` — program markup still gets
|
|
42
|
+
* no direct network.
|
|
41
43
|
*/
|
|
42
44
|
export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
43
45
|
<html lang="en">
|
|
@@ -70,17 +72,21 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
|
70
72
|
<script>
|
|
71
73
|
(function () {
|
|
72
74
|
"use strict";
|
|
73
|
-
// The
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// no bridge from program HTML to the host, by construction rather
|
|
78
|
-
// than by validation.
|
|
75
|
+
// The outer shell is the only host peer. The payload frame is
|
|
76
|
+
// sandboxed to scripts alone, with no same-origin escape. Its one
|
|
77
|
+
// optional message dialect is handled below and translated into
|
|
78
|
+
// bounded call_tool requests; raw JSON-RPC is never forwarded.
|
|
79
79
|
var host = window.parent;
|
|
80
80
|
var view = document.getElementById("program-view");
|
|
81
81
|
var initializeId = "connecta-ui-initialize";
|
|
82
82
|
var lastWidth = 0;
|
|
83
83
|
var lastHeight = 0;
|
|
84
|
+
var reads = null;
|
|
85
|
+
var hostCanCallTools = false;
|
|
86
|
+
var nextHostRequestId = 0;
|
|
87
|
+
var pendingHostReads = Object.create(null);
|
|
88
|
+
var activeHostReads = 0;
|
|
89
|
+
var maxActiveHostReads = 8;
|
|
84
90
|
|
|
85
91
|
function send(message) {
|
|
86
92
|
if (!host || host === window) return;
|
|
@@ -92,8 +98,8 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
// Program views are fixed-height by construction. The shell has no
|
|
95
|
-
// bridge to the payload frame
|
|
96
|
-
//
|
|
101
|
+
// content-height bridge to the payload frame, so it can never learn
|
|
102
|
+
// the payload's content height, and
|
|
97
103
|
// what it reports here is its own box: the min-height above, unless
|
|
98
104
|
// the host has given it more. Taller content scrolls inside the inner
|
|
99
105
|
// frame rather than growing the view. Raising the min-height is the
|
|
@@ -110,30 +116,213 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
|
110
116
|
});
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
function
|
|
119
|
+
function payload(result) {
|
|
114
120
|
if (!result || typeof result !== "object") return null;
|
|
115
121
|
var meta = result._meta;
|
|
116
122
|
if (!meta || typeof meta !== "object") return null;
|
|
117
|
-
var
|
|
118
|
-
if (!
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
var value = meta["connecta/ui"];
|
|
124
|
+
if (!value || typeof value !== "object") return null;
|
|
125
|
+
return typeof value.html === "string" && value.html.length > 0
|
|
126
|
+
? value
|
|
127
|
+
: null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// This function is serialized into the opaque-origin payload frame.
|
|
131
|
+
// It knows no addresses and has no host channel of its own: one named
|
|
132
|
+
// read request goes to the trusted outer shell and one correlated
|
|
133
|
+
// result comes back.
|
|
134
|
+
function payloadReadBridge() {
|
|
135
|
+
"use strict";
|
|
136
|
+
var pending = Object.create(null);
|
|
137
|
+
var nextId = 0;
|
|
138
|
+
|
|
139
|
+
function read(name, args) {
|
|
140
|
+
return new Promise(function (resolve, reject) {
|
|
141
|
+
var id = String(++nextId);
|
|
142
|
+
pending[id] = { resolve: resolve, reject: reject };
|
|
143
|
+
try {
|
|
144
|
+
window.parent.postMessage({
|
|
145
|
+
type: "connecta/read",
|
|
146
|
+
id: id,
|
|
147
|
+
name: name,
|
|
148
|
+
args: args === undefined ? {} : args
|
|
149
|
+
}, "*");
|
|
150
|
+
} catch (error) {
|
|
151
|
+
delete pending[id];
|
|
152
|
+
reject(error);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
Object.defineProperty(globalThis, "connecta", {
|
|
158
|
+
value: Object.freeze({ read: read }),
|
|
159
|
+
configurable: false,
|
|
160
|
+
enumerable: true,
|
|
161
|
+
writable: false
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
window.addEventListener("message", function (event) {
|
|
165
|
+
if (event.source !== window.parent) return;
|
|
166
|
+
var message = event.data;
|
|
167
|
+
if (!message || message.type !== "connecta/read-result") return;
|
|
168
|
+
var waiter = pending[message.id];
|
|
169
|
+
if (!waiter) return;
|
|
170
|
+
delete pending[message.id];
|
|
171
|
+
if (message.ok) waiter.resolve(message.value);
|
|
172
|
+
else waiter.reject(new Error(message.error || "Read failed"));
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function htmlWithReadBridge(html) {
|
|
177
|
+
var script =
|
|
178
|
+
"<scr" + "ipt>(" + payloadReadBridge.toString() + ")();</scr" + "ipt>";
|
|
179
|
+
var head = /<head(?:\\s[^>]*)?>/i.exec(html);
|
|
180
|
+
if (head) {
|
|
181
|
+
var at = (head.index || 0) + head[0].length;
|
|
182
|
+
return html.slice(0, at) + script + html.slice(at);
|
|
183
|
+
}
|
|
184
|
+
var document = /<html(?:\\s[^>]*)?>/i.exec(html);
|
|
185
|
+
if (document) {
|
|
186
|
+
var afterHtml = (document.index || 0) + document[0].length;
|
|
187
|
+
return html.slice(0, afterHtml) + "<head>" + script + "</head>" + html.slice(afterHtml);
|
|
188
|
+
}
|
|
189
|
+
return script + html;
|
|
121
190
|
}
|
|
122
191
|
|
|
123
192
|
function render(params) {
|
|
124
|
-
var
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
193
|
+
var value =
|
|
194
|
+
payload(params) ||
|
|
195
|
+
payload(params && params.result) ||
|
|
196
|
+
payload(params && params.toolResult);
|
|
197
|
+
if (value === null) return;
|
|
198
|
+
reads = value.reads && typeof value.reads === "object"
|
|
199
|
+
? value.reads
|
|
200
|
+
: null;
|
|
201
|
+
view.srcdoc = reads
|
|
202
|
+
? htmlWithReadBridge(value.html)
|
|
203
|
+
: value.html;
|
|
130
204
|
reportSize();
|
|
131
205
|
}
|
|
132
206
|
|
|
207
|
+
function readError(message, fallback) {
|
|
208
|
+
if (message && typeof message.message === "string") return message.message;
|
|
209
|
+
if (message && message.data && typeof message.data.message === "string") {
|
|
210
|
+
return message.data.message;
|
|
211
|
+
}
|
|
212
|
+
return fallback;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function finishInnerRead(innerId, ok, value) {
|
|
216
|
+
if (!view.contentWindow) return;
|
|
217
|
+
view.contentWindow.postMessage(ok
|
|
218
|
+
? { type: "connecta/read-result", id: innerId, ok: true, value: value }
|
|
219
|
+
: { type: "connecta/read-result", id: innerId, ok: false, error: value }, "*");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function beginInnerRead(message) {
|
|
223
|
+
if (!hostCanCallTools) {
|
|
224
|
+
finishInnerRead(message && message.id, false, "This host does not support app-initiated server tool calls");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!message || typeof message.id !== "string" || typeof message.name !== "string") return;
|
|
228
|
+
if (!reads || !Object.prototype.hasOwnProperty.call(reads, message.name)) {
|
|
229
|
+
finishInnerRead(message.id, false, "Unknown read binding");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (activeHostReads >= maxActiveHostReads) {
|
|
233
|
+
finishInnerRead(message.id, false, "Too many concurrent reads");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
var binding = reads[message.name];
|
|
237
|
+
if (!binding || typeof binding !== "object" || typeof binding.address !== "string") {
|
|
238
|
+
finishInnerRead(message.id, false, "Invalid read binding");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
var supplied = message.args;
|
|
242
|
+
if (!supplied || typeof supplied !== "object" || Array.isArray(supplied)) {
|
|
243
|
+
finishInnerRead(message.id, false, "Read arguments must be an object");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
var allowed = Array.isArray(binding.viewArgs) ? binding.viewArgs : [];
|
|
247
|
+
var suppliedKeys = Object.keys(supplied);
|
|
248
|
+
for (var i = 0; i < suppliedKeys.length; i++) {
|
|
249
|
+
var key = suppliedKeys[i];
|
|
250
|
+
if (allowed.indexOf(key) === -1) {
|
|
251
|
+
finishInnerRead(message.id, false, "Undeclared read argument " + JSON.stringify(key));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
var args = Object.create(null);
|
|
256
|
+
var fixed = binding.fixedArgs && typeof binding.fixedArgs === "object"
|
|
257
|
+
? binding.fixedArgs
|
|
258
|
+
: {};
|
|
259
|
+
Object.keys(fixed).forEach(function (key) { args[key] = fixed[key]; });
|
|
260
|
+
suppliedKeys.forEach(function (key) { args[key] = supplied[key]; });
|
|
261
|
+
|
|
262
|
+
var hostId = "connecta-ui-read-" + String(++nextHostRequestId);
|
|
263
|
+
pendingHostReads[hostId] = { innerId: message.id };
|
|
264
|
+
activeHostReads++;
|
|
265
|
+
send({
|
|
266
|
+
jsonrpc: "2.0",
|
|
267
|
+
id: hostId,
|
|
268
|
+
method: "tools/call",
|
|
269
|
+
params: {
|
|
270
|
+
name: "call_tool",
|
|
271
|
+
arguments: {
|
|
272
|
+
address: binding.address,
|
|
273
|
+
args: args,
|
|
274
|
+
resultMode: "value"
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function finishHostRead(message) {
|
|
281
|
+
var pending = pendingHostReads[message.id];
|
|
282
|
+
if (!pending) return false;
|
|
283
|
+
delete pendingHostReads[message.id];
|
|
284
|
+
activeHostReads--;
|
|
285
|
+
if (message.error) {
|
|
286
|
+
finishInnerRead(pending.innerId, false, readError(message.error, "Host rejected read"));
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
var toolResult = message.result;
|
|
290
|
+
if (!toolResult || typeof toolResult !== "object") {
|
|
291
|
+
finishInnerRead(pending.innerId, false, "Host returned an invalid tool result");
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
var structured = toolResult.structuredContent;
|
|
295
|
+
if (toolResult.isError || (structured && structured.ok === false)) {
|
|
296
|
+
var detail = structured && structured.error;
|
|
297
|
+
var content = Array.isArray(toolResult.content)
|
|
298
|
+
? toolResult.content.find(function (block) { return block && block.type === "text"; })
|
|
299
|
+
: null;
|
|
300
|
+
finishInnerRead(
|
|
301
|
+
pending.innerId,
|
|
302
|
+
false,
|
|
303
|
+
readError(detail, content && content.text ? content.text : "Read failed")
|
|
304
|
+
);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
var value = structured && structured.ok === true &&
|
|
308
|
+
Object.prototype.hasOwnProperty.call(structured, "data")
|
|
309
|
+
? structured.data
|
|
310
|
+
: structured !== undefined
|
|
311
|
+
? structured
|
|
312
|
+
: toolResult;
|
|
313
|
+
finishInnerRead(pending.innerId, true, value);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
133
317
|
window.addEventListener("message", function (event) {
|
|
134
|
-
if (event.source !== host) return;
|
|
135
318
|
var message = event.data;
|
|
319
|
+
if (event.source === view.contentWindow) {
|
|
320
|
+
if (message && message.type === "connecta/read") beginInnerRead(message);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (event.source !== host) return;
|
|
136
324
|
if (!message || message.jsonrpc !== "2.0") return;
|
|
325
|
+
if (message.id !== undefined && finishHostRead(message)) return;
|
|
137
326
|
if (message.method === "ui/notifications/tool-result") {
|
|
138
327
|
render(message.params);
|
|
139
328
|
return;
|
|
@@ -151,6 +340,8 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
|
|
|
151
340
|
// response carries the same id, and announcing initialization on one
|
|
152
341
|
// would assert a handshake that never happened.
|
|
153
342
|
if (message.id === initializeId && message.result !== undefined) {
|
|
343
|
+
var capabilities = message.result.hostCapabilities;
|
|
344
|
+
hostCanCallTools = Boolean(capabilities && capabilities.serverTools);
|
|
154
345
|
notify("ui/notifications/initialized", {});
|
|
155
346
|
}
|
|
156
347
|
});
|
package/src/catalog-service.ts
CHANGED
|
@@ -25,6 +25,8 @@ import type {
|
|
|
25
25
|
} from "./registry.js";
|
|
26
26
|
import {
|
|
27
27
|
connectorGuide,
|
|
28
|
+
connectorGuideRequired,
|
|
29
|
+
connectorGuideSummary,
|
|
28
30
|
connectorSkillName,
|
|
29
31
|
} from "./skills.js";
|
|
30
32
|
import {
|
|
@@ -196,6 +198,7 @@ export interface CatalogDescribeArgs {
|
|
|
196
198
|
interface CatalogSearchEntry {
|
|
197
199
|
connector: Connector;
|
|
198
200
|
guide?: string;
|
|
201
|
+
guideSummary?: string;
|
|
199
202
|
tool: {
|
|
200
203
|
name: string;
|
|
201
204
|
address: string;
|
|
@@ -208,9 +211,34 @@ interface CatalogSearchEntry {
|
|
|
208
211
|
requiredInputKeys?: string[];
|
|
209
212
|
outputKeys?: string[];
|
|
210
213
|
annotations?: ToolDef["annotations"];
|
|
214
|
+
guideRequired?: true;
|
|
215
|
+
guideRequiredReasons?: GuideRequiredReason[];
|
|
211
216
|
};
|
|
212
217
|
}
|
|
213
218
|
|
|
219
|
+
type GuideRequiredReason =
|
|
220
|
+
| "connector_required"
|
|
221
|
+
| "approval_required"
|
|
222
|
+
| "schema_truncated";
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Reasons discovery can determine without reading arguments or guessing at a
|
|
226
|
+
* task. Summary-only conventions remain an agent decision; hard requirements
|
|
227
|
+
* are explicit and machine-readable.
|
|
228
|
+
*/
|
|
229
|
+
function guideRequiredReasons(
|
|
230
|
+
connector: Connector,
|
|
231
|
+
tool: ToolDef,
|
|
232
|
+
schemaTruncated: boolean,
|
|
233
|
+
): GuideRequiredReason[] | undefined {
|
|
234
|
+
if (!connectorGuide(connector)) return undefined;
|
|
235
|
+
const reasons: GuideRequiredReason[] = [];
|
|
236
|
+
if (connectorGuideRequired(connector)) reasons.push("connector_required");
|
|
237
|
+
if (!isExplicitlyReadOnly(tool)) reasons.push("approval_required");
|
|
238
|
+
if (schemaTruncated) reasons.push("schema_truncated");
|
|
239
|
+
return reasons.length > 0 ? reasons : undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
214
242
|
/**
|
|
215
243
|
* Code-mode key metadata for one match. Each half is omitted when its schema
|
|
216
244
|
* does not resolve to an object shape, so a program reads "no metadata, use the
|
|
@@ -237,6 +265,19 @@ function schemaKeyMetadata(
|
|
|
237
265
|
};
|
|
238
266
|
}
|
|
239
267
|
|
|
268
|
+
/**
|
|
269
|
+
* The classified-failure subset a scoped search may echo: enough to tell a
|
|
270
|
+
* transient outage from one an operator must clear, and nothing more. Kept as
|
|
271
|
+
* its own type rather than `CallErrorDetails` so widening the call-path
|
|
272
|
+
* classifier cannot widen this discovery-surface field by accident.
|
|
273
|
+
*/
|
|
274
|
+
interface CatalogFailureDetail {
|
|
275
|
+
code: string;
|
|
276
|
+
message: string;
|
|
277
|
+
retryable: boolean;
|
|
278
|
+
retryAfterMs?: number;
|
|
279
|
+
}
|
|
280
|
+
|
|
240
281
|
export interface CatalogSearchPage {
|
|
241
282
|
entries: CatalogSearchEntry[];
|
|
242
283
|
total: number;
|
|
@@ -253,6 +294,12 @@ export interface CatalogSearchPage {
|
|
|
253
294
|
connectorScope?: string;
|
|
254
295
|
unknownConnector?: true;
|
|
255
296
|
unavailableConnectorCount?: number;
|
|
297
|
+
/** Bounded typed failure for an explicitly scoped unavailable catalog. */
|
|
298
|
+
catalogError?: CatalogFailureDetail;
|
|
299
|
+
guide?: string;
|
|
300
|
+
guideSummary?: string;
|
|
301
|
+
guideRequired?: true;
|
|
302
|
+
guideRequiredReasons?: GuideRequiredReason[];
|
|
256
303
|
guidance?: string;
|
|
257
304
|
};
|
|
258
305
|
}
|
|
@@ -262,6 +309,9 @@ export interface CatalogDescription {
|
|
|
262
309
|
name?: string;
|
|
263
310
|
description?: string;
|
|
264
311
|
guide?: string;
|
|
312
|
+
guideSummary?: string;
|
|
313
|
+
guideRequired?: true;
|
|
314
|
+
guideRequiredReasons?: GuideRequiredReason[];
|
|
265
315
|
inputSchema?: unknown;
|
|
266
316
|
outputSchema?: unknown;
|
|
267
317
|
annotations?: ToolDef["annotations"];
|
|
@@ -656,10 +706,19 @@ export class CatalogService {
|
|
|
656
706
|
match.tool.description,
|
|
657
707
|
args.fullDescriptions === true,
|
|
658
708
|
);
|
|
709
|
+
const requiredReasons = guideRequiredReasons(
|
|
710
|
+
match.connector,
|
|
711
|
+
match.tool,
|
|
712
|
+
renderedInput?.truncated === true || renderedOutput?.truncated === true,
|
|
713
|
+
);
|
|
714
|
+
const guideSummary = connectorGuideSummary(match.connector);
|
|
659
715
|
return {
|
|
660
716
|
connector: match.connector,
|
|
661
717
|
...(connectorGuide(match.connector)
|
|
662
|
-
? {
|
|
718
|
+
? {
|
|
719
|
+
guide: connectorSkillName(match.connector.id),
|
|
720
|
+
...(guideSummary ? { guideSummary } : {}),
|
|
721
|
+
}
|
|
663
722
|
: {}),
|
|
664
723
|
tool: {
|
|
665
724
|
name: match.tool.name,
|
|
@@ -697,6 +756,12 @@ export class CatalogService {
|
|
|
697
756
|
...(match.tool.annotations
|
|
698
757
|
? { annotations: match.tool.annotations }
|
|
699
758
|
: {}),
|
|
759
|
+
...(requiredReasons
|
|
760
|
+
? {
|
|
761
|
+
guideRequired: true as const,
|
|
762
|
+
guideRequiredReasons: requiredReasons,
|
|
763
|
+
}
|
|
764
|
+
: {}),
|
|
700
765
|
},
|
|
701
766
|
};
|
|
702
767
|
});
|
|
@@ -732,6 +797,27 @@ export class CatalogService {
|
|
|
732
797
|
const unavailableCatalogs = catalogs.filter(
|
|
733
798
|
(catalog) => catalog.status === "rejected",
|
|
734
799
|
).length;
|
|
800
|
+
// Named field by field rather than spread: `CallErrorDetails` also carries
|
|
801
|
+
// connector, operation, recovery, and nextAction, and a discovery read is
|
|
802
|
+
// not a call — widening the classifier must not silently widen what a
|
|
803
|
+
// catalog search hands back.
|
|
804
|
+
const scopedCatalogError = ((): CatalogFailureDetail | undefined => {
|
|
805
|
+
if (!scopedConnector || catalogs[0]?.status !== "rejected") {
|
|
806
|
+
return undefined;
|
|
807
|
+
}
|
|
808
|
+
const error = classifyCallError(
|
|
809
|
+
catalogs[0].reason,
|
|
810
|
+
"catalog_lookup_failed",
|
|
811
|
+
);
|
|
812
|
+
return {
|
|
813
|
+
code: error.code,
|
|
814
|
+
message: boundedEchoText(error.message),
|
|
815
|
+
retryable: error.retryable,
|
|
816
|
+
...(error.retryAfterMs === undefined
|
|
817
|
+
? {}
|
|
818
|
+
: { retryAfterMs: error.retryAfterMs }),
|
|
819
|
+
};
|
|
820
|
+
})();
|
|
735
821
|
const safetyLabel =
|
|
736
822
|
safety === "readOnly"
|
|
737
823
|
? "read-only "
|
|
@@ -740,6 +826,14 @@ export class CatalogService {
|
|
|
740
826
|
: "";
|
|
741
827
|
const filterRecovery =
|
|
742
828
|
safety === "all" ? "" : " Change safety to inspect the other tools.";
|
|
829
|
+
const scopedGuide =
|
|
830
|
+
matches.length === 0 && scopedConnector && connectorGuide(scopedConnector)
|
|
831
|
+
? {
|
|
832
|
+
guide: connectorSkillName(scopedConnector.id),
|
|
833
|
+
guideSummary: connectorGuideSummary(scopedConnector),
|
|
834
|
+
required: connectorGuideRequired(scopedConnector),
|
|
835
|
+
}
|
|
836
|
+
: undefined;
|
|
743
837
|
const guidance =
|
|
744
838
|
queryTerms.length === 0
|
|
745
839
|
? undefined
|
|
@@ -748,8 +842,10 @@ export class CatalogService {
|
|
|
748
842
|
? `Connector "${args.connector}" is not configured in this deployment. Omit connector to search all configured tools.`
|
|
749
843
|
: scopedConnector
|
|
750
844
|
? unavailableCatalogs > 0
|
|
751
|
-
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable.
|
|
752
|
-
:
|
|
845
|
+
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
846
|
+
: scopedGuide?.required
|
|
847
|
+
? `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Fetch queryAnalysis.guide before calling, then refine terms or browse with an empty query.${filterRecovery}`
|
|
848
|
+
: `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`
|
|
753
849
|
: unavailableCatalogs === 0
|
|
754
850
|
? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
|
|
755
851
|
: `No matching ${safetyLabel}capability was found in the catalogs that answered; ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
|
|
@@ -789,6 +885,23 @@ export class CatalogService {
|
|
|
789
885
|
...(unavailableCatalogs > 0
|
|
790
886
|
? { unavailableConnectorCount: unavailableCatalogs }
|
|
791
887
|
: {}),
|
|
888
|
+
...(scopedCatalogError ? { catalogError: scopedCatalogError } : {}),
|
|
889
|
+
...(scopedGuide
|
|
890
|
+
? {
|
|
891
|
+
guide: scopedGuide.guide,
|
|
892
|
+
...(scopedGuide.guideSummary
|
|
893
|
+
? { guideSummary: scopedGuide.guideSummary }
|
|
894
|
+
: {}),
|
|
895
|
+
...(scopedGuide.required
|
|
896
|
+
? {
|
|
897
|
+
guideRequired: true as const,
|
|
898
|
+
guideRequiredReasons: [
|
|
899
|
+
"connector_required" as const,
|
|
900
|
+
],
|
|
901
|
+
}
|
|
902
|
+
: {}),
|
|
903
|
+
}
|
|
904
|
+
: {}),
|
|
792
905
|
...(guidance ? { guidance } : {}),
|
|
793
906
|
},
|
|
794
907
|
}
|
|
@@ -853,12 +966,27 @@ export class CatalogService {
|
|
|
853
966
|
tool.description,
|
|
854
967
|
args.fullDescriptions === true,
|
|
855
968
|
);
|
|
969
|
+
const requiredReasons = guideRequiredReasons(
|
|
970
|
+
addressResolution.connector,
|
|
971
|
+
tool,
|
|
972
|
+
false,
|
|
973
|
+
);
|
|
974
|
+
const guideSummary = connectorGuideSummary(addressResolution.connector);
|
|
856
975
|
return {
|
|
857
976
|
address,
|
|
858
977
|
name: tool.name,
|
|
859
978
|
...(description !== undefined ? { description } : {}),
|
|
860
979
|
...(connectorGuide(addressResolution.connector)
|
|
861
|
-
? {
|
|
980
|
+
? {
|
|
981
|
+
guide: connectorSkillName(addressResolution.connector.id),
|
|
982
|
+
...(guideSummary ? { guideSummary } : {}),
|
|
983
|
+
}
|
|
984
|
+
: {}),
|
|
985
|
+
...(requiredReasons
|
|
986
|
+
? {
|
|
987
|
+
guideRequired: true as const,
|
|
988
|
+
guideRequiredReasons: requiredReasons,
|
|
989
|
+
}
|
|
862
990
|
: {}),
|
|
863
991
|
inputSchema: renderSchema(input, format),
|
|
864
992
|
...(tool.outputSchema
|
|
@@ -877,6 +1005,7 @@ export function groupedSearchResult(page: CatalogSearchPage) {
|
|
|
877
1005
|
id: string;
|
|
878
1006
|
title?: string;
|
|
879
1007
|
guide?: string;
|
|
1008
|
+
guideSummary?: string;
|
|
880
1009
|
tools: CatalogSearchEntry["tool"][];
|
|
881
1010
|
}> = [];
|
|
882
1011
|
const byConnector = new Map<string, (typeof groups)[number]>();
|
|
@@ -889,6 +1018,9 @@ export function groupedSearchResult(page: CatalogSearchPage) {
|
|
|
889
1018
|
id: entry.connector.id,
|
|
890
1019
|
...(entry.connector.title ? { title: entry.connector.title } : {}),
|
|
891
1020
|
...(entry.guide ? { guide: entry.guide } : {}),
|
|
1021
|
+
...(entry.guideSummary
|
|
1022
|
+
? { guideSummary: entry.guideSummary }
|
|
1023
|
+
: {}),
|
|
892
1024
|
tools: [],
|
|
893
1025
|
};
|
|
894
1026
|
byConnector.set(entry.connector.id, group);
|
|
@@ -913,6 +1045,9 @@ export function flatSearchResult(page: CatalogSearchPage) {
|
|
|
913
1045
|
tools: page.entries.map((entry) => ({
|
|
914
1046
|
...entry.tool,
|
|
915
1047
|
...(entry.guide ? { guide: entry.guide } : {}),
|
|
1048
|
+
...(entry.guideSummary
|
|
1049
|
+
? { guideSummary: entry.guideSummary }
|
|
1050
|
+
: {}),
|
|
916
1051
|
})),
|
|
917
1052
|
total: page.total,
|
|
918
1053
|
offset: page.offset,
|
package/src/connectors/api.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ConnectorCredentialConfig,
|
|
6
6
|
ConnectorCredentialValues,
|
|
7
7
|
ConnectorContext,
|
|
8
|
+
ConnectorUsageGuide,
|
|
8
9
|
CredentialTestResult,
|
|
9
10
|
JsonSchema,
|
|
10
11
|
ToolAnnotations,
|
|
@@ -41,10 +42,11 @@ export interface ApiOptions {
|
|
|
41
42
|
/** Optional per-runtime downstream call-admission policy. */
|
|
42
43
|
callAdmission?: ConnectorCallAdmissionPolicy;
|
|
43
44
|
/**
|
|
44
|
-
* Optional agent-facing usage guide
|
|
45
|
-
*
|
|
45
|
+
* Optional agent-facing usage guide served by `skills` as
|
|
46
|
+
* `connector:<id>`. A string is markdown; the structured form adds bounded
|
|
47
|
+
* discovery metadata. See `Connector.usageGuide`.
|
|
46
48
|
*/
|
|
47
|
-
usageGuide?: string;
|
|
49
|
+
usageGuide?: string | ConnectorUsageGuide;
|
|
48
50
|
/** Optional operator-managed credential exposed through ctx.credential and /credentials. */
|
|
49
51
|
credential?: ConnectorCredentialConfig;
|
|
50
52
|
/** Optional validation behind /credentials' Test action. */
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
ConnectorCallAdmissionPolicy,
|
|
22
22
|
ConnectorContext,
|
|
23
23
|
ConnectorStatus,
|
|
24
|
+
ConnectorUsageGuide,
|
|
24
25
|
Logger,
|
|
25
26
|
ToolDef,
|
|
26
27
|
} from "../types.js";
|
|
@@ -47,10 +48,11 @@ export interface RemoteMcpOptions {
|
|
|
47
48
|
/** Optional per-runtime downstream call-admission policy. */
|
|
48
49
|
callAdmission?: ConnectorCallAdmissionPolicy;
|
|
49
50
|
/**
|
|
50
|
-
* Optional agent-facing usage guide
|
|
51
|
-
*
|
|
51
|
+
* Optional agent-facing usage guide served by `skills` as
|
|
52
|
+
* `connector:<id>`. A string is markdown; the structured form adds bounded
|
|
53
|
+
* discovery metadata. See `Connector.usageGuide`.
|
|
52
54
|
*/
|
|
53
|
-
usageGuide?: string;
|
|
55
|
+
usageGuide?: string | ConnectorUsageGuide;
|
|
54
56
|
auth?: RemoteMcpAuth;
|
|
55
57
|
/**
|
|
56
58
|
* Downstream HTTP redirect policy. Defaults to `"none"`: every redirect is
|