@zackbart/connecta 0.22.3 → 0.23.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 +42 -0
- package/README.md +1 -1
- package/dist/catalog-service.d.ts +1 -6
- package/dist/catalog-service.js +3 -53
- package/dist/errors.d.ts +1 -1
- package/dist/execute.d.ts +5 -52
- package/dist/execute.js +40 -347
- package/dist/executors/quickjs-protocol.d.ts +0 -7
- package/dist/executors/quickjs-protocol.js +2 -10
- package/dist/executors/quickjs.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/invocation.d.ts +0 -33
- package/dist/invocation.js +54 -121
- package/dist/meta-tools.d.ts +3 -6
- package/dist/meta-tools.js +6 -17
- package/dist/registry.js +4 -0
- package/dist/routes/mcp.js +0 -48
- package/dist/server.d.ts +1 -2
- package/dist/server.js +1 -19
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +55 -19
- package/dist/types.d.ts +1 -15
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +7 -11
- package/documentation/auth.md +4 -5
- package/documentation/call-admission.md +10 -11
- package/documentation/code-mode.md +65 -260
- package/documentation/connectors.md +7 -5
- package/documentation/meta-tools.md +31 -14
- package/documentation/operations.md +7 -9
- package/documentation/provider-conventions.md +2 -2
- package/documentation/upgrading.md +58 -8
- package/ethos.md +14 -13
- package/package.json +1 -1
- package/templates/node/package.json +1 -1
- package/dist/apps-shell.d.ts +0 -37
- package/dist/apps-shell.js +0 -174
package/dist/invocation.js
CHANGED
|
@@ -4,7 +4,7 @@ import { classifyCallError, ConnectorCallError, echoedCallArgs, framingError, }
|
|
|
4
4
|
import { unwrapMcpResult } from "./mcp-result.js";
|
|
5
5
|
import { splitAddress } from "./registry.js";
|
|
6
6
|
import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
7
|
-
import {
|
|
7
|
+
import { withDeadline } from "./timeout.js";
|
|
8
8
|
import { validateToolInput } from "./validate.js";
|
|
9
9
|
function defined(values) {
|
|
10
10
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
@@ -18,40 +18,6 @@ async function timed(bucket, fn) {
|
|
|
18
18
|
bucket(Date.now() - started);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* The longest the engine will park a synchronous inbound request in *waiting
|
|
23
|
-
* alone*. The engine already treats ~15 s as the outer bound of one reasonable
|
|
24
|
-
* connector call (EXECUTE_HOST_CALL_TIMEOUT_MS), so sleeping for minutes trades
|
|
25
|
-
* a fast, informative failure for a hung one. A connector-reported window this
|
|
26
|
-
* long isn't truncated — it's declined (see `retryBackoffMs`) and reported
|
|
27
|
-
* verbatim as `error.retryAfterMs`, so the agent, which can afford to wait,
|
|
28
|
-
* decides when to re-issue.
|
|
29
|
-
*/
|
|
30
|
-
export const MAX_RETRY_BACKOFF_MS = 10_000;
|
|
31
|
-
/**
|
|
32
|
-
* How long to wait before the next attempt, or `undefined` for "don't retry".
|
|
33
|
-
*
|
|
34
|
-
* A connector that read a `Retry-After` header knows the window exactly, so it
|
|
35
|
-
* is honoured **exactly or not at all**: truncating an exponential *guess* is
|
|
36
|
-
* harmless, but truncating a *known* window means deliberately retrying inside
|
|
37
|
-
* a rate limit — the harm this channel exists to prevent. A window longer than
|
|
38
|
-
* `MAX_RETRY_BACKOFF_MS` therefore declines the retry rather than shortening
|
|
39
|
-
* it. (`retryAfterMs` is normalized non-negative, so `0` means "retry now".)
|
|
40
|
-
* Connectors that report no window keep the historical exponential guess.
|
|
41
|
-
*
|
|
42
|
-
* Waits are per attempt, matching the per-attempt `timeoutMs` race in
|
|
43
|
-
* `InvocationService.invoke`. Exported for direct testing.
|
|
44
|
-
*/
|
|
45
|
-
export function retryBackoffMs(attempt, retryAfterMs) {
|
|
46
|
-
if (retryAfterMs === undefined) {
|
|
47
|
-
return Math.min(250 * 2 ** (attempt - 1), 1_000);
|
|
48
|
-
}
|
|
49
|
-
return retryAfterMs <= MAX_RETRY_BACKOFF_MS ? retryAfterMs : undefined;
|
|
50
|
-
}
|
|
51
|
-
function retrySafe(definition) {
|
|
52
|
-
return (definition.annotations?.readOnlyHint === true ||
|
|
53
|
-
definition.annotations?.idempotentHint === true);
|
|
54
|
-
}
|
|
55
21
|
function callerCancelledDetails() {
|
|
56
22
|
return {
|
|
57
23
|
code: "cancelled",
|
|
@@ -122,23 +88,10 @@ export class InvocationService {
|
|
|
122
88
|
this.activity = activity;
|
|
123
89
|
}
|
|
124
90
|
async invoke(address, args, context) {
|
|
125
|
-
const options = defined({ signal: context.requestSignal });
|
|
126
|
-
return this.invokeWithResolution(address, args, context, () => this.catalog.resolveTool(address, options));
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Code-mode namespace dispatch preserves JavaScript-safe tool aliases while
|
|
130
|
-
* still feeding the resolved catalog entry through the one invocation path.
|
|
131
|
-
*/
|
|
132
|
-
async invokeToolAlias(connectorId, toolAlias, aliasFor, args, context) {
|
|
133
|
-
const options = defined({ signal: context.requestSignal });
|
|
134
|
-
return this.invokeWithResolution(`${connectorId}.${toolAlias}`, args, context, () => this.catalog.resolveToolAlias(connectorId, toolAlias, aliasFor, options));
|
|
135
|
-
}
|
|
136
|
-
async invokeWithResolution(address, args, context, resolve) {
|
|
137
91
|
const started = Date.now();
|
|
138
92
|
let catalogMs = 0;
|
|
139
93
|
let admissionMs = 0;
|
|
140
94
|
let connectorMs = 0;
|
|
141
|
-
let backoffMs = 0;
|
|
142
95
|
let resultProcessingMs = 0;
|
|
143
96
|
let attempts = 0;
|
|
144
97
|
let resolved;
|
|
@@ -151,7 +104,6 @@ export class InvocationService {
|
|
|
151
104
|
catalogMs,
|
|
152
105
|
admissionMs,
|
|
153
106
|
connectorMs,
|
|
154
|
-
backoffMs,
|
|
155
107
|
resultProcessingMs,
|
|
156
108
|
totalMs: Date.now() - started,
|
|
157
109
|
});
|
|
@@ -248,7 +200,7 @@ export class InvocationService {
|
|
|
248
200
|
error: details,
|
|
249
201
|
};
|
|
250
202
|
};
|
|
251
|
-
const resolution = await
|
|
203
|
+
const resolution = await this.catalog.resolveTool(address, defined({ signal: context.requestSignal }));
|
|
252
204
|
catalogMs += resolution.catalogMs;
|
|
253
205
|
if (!resolution.ok) {
|
|
254
206
|
if (resolution.connector && resolution.toolName) {
|
|
@@ -290,80 +242,61 @@ export class InvocationService {
|
|
|
290
242
|
? error.details
|
|
291
243
|
: classifyCallError(error));
|
|
292
244
|
}
|
|
293
|
-
const maxRetries = Math.min(2, Math.max(0, Math.trunc(context.maxRetries ?? 0)));
|
|
294
245
|
let result;
|
|
295
246
|
let observedResult;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
`authorize_connector({ connector: "${resolved.connector.id}" }).`);
|
|
314
|
-
}
|
|
315
|
-
// Cancellation can arrive during admission or context construction.
|
|
316
|
-
if (callSignal?.aborted)
|
|
317
|
-
throw callSignal.reason;
|
|
318
|
-
return resolved.connector.callTool(resolved.toolName, args ?? {}, connectorContext);
|
|
319
|
-
};
|
|
320
|
-
if (!context.timeoutMs && !context.requestSignal)
|
|
321
|
-
return call();
|
|
322
|
-
return withDeadline(call, {
|
|
323
|
-
...defined({
|
|
324
|
-
timeoutMs: context.timeoutMs,
|
|
325
|
-
signal: context.requestSignal,
|
|
326
|
-
}),
|
|
327
|
-
timeoutError: new ConnectorCallError("timeout", `Tool call timed out after ${context.timeoutMs}ms`),
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
// isError is checked here for BOTH result shapes so every adapter
|
|
331
|
-
// reports the same downstream-failure wording, and the throw lands
|
|
332
|
-
// inside the attempt where it stays retry-eligible and feeds health.
|
|
333
|
-
assertRawMcpSuccess(resolved.connector.kind, raw);
|
|
334
|
-
observedResult = unwrapMcpResult(resolved.connector.kind, raw);
|
|
335
|
-
result = context.unwrapResult ? observedResult : raw;
|
|
336
|
-
}
|
|
337
|
-
catch (error) {
|
|
338
|
-
attemptFailed = true;
|
|
339
|
-
attemptError = error;
|
|
340
|
-
}
|
|
341
|
-
finally {
|
|
342
|
-
permit?.release();
|
|
343
|
-
}
|
|
344
|
-
if (attemptFailed) {
|
|
345
|
-
const callerCancelled = isCallerCancellation(attemptError, context.requestSignal);
|
|
346
|
-
const details = callerCancelled
|
|
347
|
-
? callerCancelledDetails()
|
|
348
|
-
: classifyCallError(attemptError);
|
|
349
|
-
if (!callerCancelled &&
|
|
350
|
-
attempts <= maxRetries &&
|
|
351
|
-
retrySafe(resolved.definition) &&
|
|
352
|
-
details.retryable) {
|
|
353
|
-
const wait = retryBackoffMs(attempts, details.retryAfterMs);
|
|
354
|
-
if (wait !== undefined) {
|
|
355
|
-
const completed = await timed((elapsed) => { backoffMs += elapsed; }, () => sleep(wait, context.requestSignal));
|
|
356
|
-
if (!completed)
|
|
357
|
-
return failed(callerCancelledDetails());
|
|
358
|
-
continue;
|
|
247
|
+
attempts = 1;
|
|
248
|
+
let permit;
|
|
249
|
+
let attemptError;
|
|
250
|
+
let attemptFailed = false;
|
|
251
|
+
try {
|
|
252
|
+
permit = await timed((elapsed) => { admissionMs += elapsed; }, () => this.registry.admitCall(resolved.connector.id, {
|
|
253
|
+
toolName: resolved.toolName,
|
|
254
|
+
args: args ?? {},
|
|
255
|
+
...defined({ signal: context.requestSignal }),
|
|
256
|
+
}));
|
|
257
|
+
const raw = await timed((elapsed) => { connectorMs += elapsed; }, () => {
|
|
258
|
+
const call = (callSignal) => {
|
|
259
|
+
const connectorContext = this.registry.contextFor(resolved.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({ signal: callSignal, timeoutMs: context.timeoutMs }));
|
|
260
|
+
if (resolved.connector.credential &&
|
|
261
|
+
!connectorContext.credential) {
|
|
262
|
+
throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured. Call " +
|
|
263
|
+
`authorize_connector({ connector: "${resolved.connector.id}" }).`);
|
|
359
264
|
}
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
265
|
+
// Cancellation can arrive during admission or context construction.
|
|
266
|
+
if (callSignal?.aborted)
|
|
267
|
+
throw callSignal.reason;
|
|
268
|
+
return resolved.connector.callTool(resolved.toolName, args ?? {}, connectorContext);
|
|
269
|
+
};
|
|
270
|
+
if (!context.timeoutMs && !context.requestSignal)
|
|
271
|
+
return call();
|
|
272
|
+
return withDeadline(call, {
|
|
273
|
+
...defined({
|
|
274
|
+
timeoutMs: context.timeoutMs,
|
|
275
|
+
signal: context.requestSignal,
|
|
276
|
+
}),
|
|
277
|
+
timeoutError: new ConnectorCallError("timeout", `Tool call timed out after ${context.timeoutMs}ms`),
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
// isError is checked here for BOTH result shapes so every adapter
|
|
281
|
+
// reports the same downstream-failure wording, and the throw lands
|
|
282
|
+
// inside the attempt where it feeds health.
|
|
283
|
+
assertRawMcpSuccess(resolved.connector.kind, raw);
|
|
284
|
+
observedResult = unwrapMcpResult(resolved.connector.kind, raw);
|
|
285
|
+
result = context.unwrapResult ? observedResult : raw;
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
attemptFailed = true;
|
|
289
|
+
attemptError = error;
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
permit?.release();
|
|
293
|
+
}
|
|
294
|
+
if (attemptFailed) {
|
|
295
|
+
const callerCancelled = isCallerCancellation(attemptError, context.requestSignal);
|
|
296
|
+
const details = callerCancelled
|
|
297
|
+
? callerCancelledDetails()
|
|
298
|
+
: classifyCallError(attemptError);
|
|
299
|
+
return failed(details);
|
|
367
300
|
}
|
|
368
301
|
try {
|
|
369
302
|
const value = await timed((elapsed) => { resultProcessingMs += elapsed; }, async () => {
|
package/dist/meta-tools.d.ts
CHANGED
|
@@ -2,9 +2,8 @@ import type { McpServer } from "@modelcontextprotocol/server";
|
|
|
2
2
|
import type { ActivityRequestContext } from "./activity.js";
|
|
3
3
|
import { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT } from "./catalog-service.js";
|
|
4
4
|
import type { DeferredWork } from "./connector-scope.js";
|
|
5
|
-
import { MAX_RETRY_BACKOFF_MS, retryBackoffMs } from "./invocation.js";
|
|
6
5
|
import { type RegistryView } from "./registry.js";
|
|
7
|
-
export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES,
|
|
6
|
+
export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, };
|
|
8
7
|
interface TextContent {
|
|
9
8
|
type: "text";
|
|
10
9
|
text: string;
|
|
@@ -48,8 +47,6 @@ export interface CallArgs {
|
|
|
48
47
|
args?: Record<string, unknown>;
|
|
49
48
|
resultMode?: ResultMode;
|
|
50
49
|
timeoutMs?: number;
|
|
51
|
-
/** Retries after the first attempt; honored only for safely annotated tools. */
|
|
52
|
-
maxRetries?: number;
|
|
53
50
|
/** Include connector/catalog/result-processing timing segments. */
|
|
54
51
|
diagnostics?: boolean;
|
|
55
52
|
}
|
|
@@ -112,8 +109,8 @@ export declare function createMetaTools(registry: RegistryView, baseUrl: string,
|
|
|
112
109
|
/**
|
|
113
110
|
* Register the six explicit meta-tools onto an McpServer instance.
|
|
114
111
|
* `registerExecuteTool` adds the seventh, `execute_code`. Broad discovery and
|
|
115
|
-
* multi-call work
|
|
116
|
-
*
|
|
112
|
+
* multi-call work uses discovery and ordinary JavaScript promises inside a
|
|
113
|
+
* program, which `execute_code` builds over the same
|
|
117
114
|
* `CatalogService` and `InvocationService` these handlers use — one shared
|
|
118
115
|
* services layer, two adapters above it.
|
|
119
116
|
*/
|
package/dist/meta-tools.js
CHANGED
|
@@ -3,11 +3,11 @@ import { boundedDiscoveryText, CatalogService, DEFAULT_SEARCH_LIMIT, DiscoveryPo
|
|
|
3
3
|
import { resolveDiscoveryConcurrency } from "./concurrency.js";
|
|
4
4
|
import { msg } from "./errors.js";
|
|
5
5
|
import { serializeResultText } from "./executor-result.js";
|
|
6
|
-
import { InvocationService,
|
|
6
|
+
import { InvocationService, } from "./invocation.js";
|
|
7
7
|
import { isValidMaxResultBytes, MIN_MAX_RESULT_BYTES, resolveMaxResultBytes, } from "./registry.js";
|
|
8
8
|
import { hasConnectorGuides, listSkills, resolveSkill, } from "./skills.js";
|
|
9
9
|
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, } from "./timeout.js";
|
|
10
|
-
export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES,
|
|
10
|
+
export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, };
|
|
11
11
|
const RESULT_TTL_SECONDS = 900;
|
|
12
12
|
const enc = new TextEncoder();
|
|
13
13
|
const dec = new TextDecoder();
|
|
@@ -267,9 +267,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
267
267
|
? { allowDestructive: options.allowDestructive }
|
|
268
268
|
: {}),
|
|
269
269
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
270
|
-
...(call.maxRetries !== undefined
|
|
271
|
-
? { maxRetries: call.maxRetries }
|
|
272
|
-
: {}),
|
|
273
270
|
...(opts.requestSignal !== undefined
|
|
274
271
|
? { requestSignal: opts.requestSignal }
|
|
275
272
|
: {}),
|
|
@@ -568,14 +565,13 @@ const CALL_INPUT_SCHEMA = {
|
|
|
568
565
|
args: z.record(z.string(), z.unknown()).optional(),
|
|
569
566
|
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
570
567
|
timeoutMs: z.number().int().positive().optional(),
|
|
571
|
-
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
572
568
|
diagnostics: z.boolean().optional(),
|
|
573
569
|
};
|
|
574
570
|
/**
|
|
575
571
|
* Register the six explicit meta-tools onto an McpServer instance.
|
|
576
572
|
* `registerExecuteTool` adds the seventh, `execute_code`. Broad discovery and
|
|
577
|
-
* multi-call work
|
|
578
|
-
*
|
|
573
|
+
* multi-call work uses discovery and ordinary JavaScript promises inside a
|
|
574
|
+
* program, which `execute_code` builds over the same
|
|
579
575
|
* `CatalogService` and `InvocationService` these handlers use — one shared
|
|
580
576
|
* services layer, two adapters above it.
|
|
581
577
|
*/
|
|
@@ -592,7 +588,6 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
592
588
|
description: describedFor(registry, SKILLS_DESC, "skills"),
|
|
593
589
|
inputSchema: z.object({ name: z.string().optional() }),
|
|
594
590
|
annotations: READ_ONLY_LOCAL,
|
|
595
|
-
_meta: { ui: { visibility: ["model"] } },
|
|
596
591
|
}, async (args) => mt.skills(args));
|
|
597
592
|
server.registerTool("search_tools", {
|
|
598
593
|
description: describedFor(registry, SEARCH_DESC, "search"),
|
|
@@ -608,20 +603,17 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
608
603
|
includeSchemas: z.enum(["compact", "json"]).optional(),
|
|
609
604
|
}),
|
|
610
605
|
annotations: READ_ONLY_REMOTE,
|
|
611
|
-
_meta: { ui: { visibility: ["model"] } },
|
|
612
606
|
}, async (args) => mt.searchTools(args));
|
|
613
607
|
server.registerTool("call_tool", {
|
|
614
608
|
description: CALL_DESC,
|
|
615
|
-
inputSchema: z.
|
|
609
|
+
inputSchema: z.strictObject(CALL_INPUT_SCHEMA),
|
|
616
610
|
// call_tool admits only tools that are themselves explicitly read-only;
|
|
617
611
|
// anything else is refused and routed to call_destructive_tool.
|
|
618
612
|
annotations: READ_ONLY_REMOTE,
|
|
619
|
-
// Omission defaults to model + app. Display-only views may call no tool.
|
|
620
|
-
_meta: { ui: { visibility: ["model"] } },
|
|
621
613
|
}, async (args) => mt.callTool(args));
|
|
622
614
|
server.registerTool("call_destructive_tool", {
|
|
623
615
|
description: describedFor(registry, CALL_DESTRUCTIVE_DESC, "destructive"),
|
|
624
|
-
inputSchema: z.
|
|
616
|
+
inputSchema: z.strictObject({
|
|
625
617
|
...CALL_INPUT_SCHEMA,
|
|
626
618
|
// Bounded above, but with no lower bound: a model that sends `""` or
|
|
627
619
|
// whitespace has written no reason, and failing an entire consequential
|
|
@@ -634,7 +626,6 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
634
626
|
readOnlyHint: false,
|
|
635
627
|
openWorldHint: true,
|
|
636
628
|
},
|
|
637
|
-
_meta: { ui: { visibility: ["model"] } },
|
|
638
629
|
}, async (args) => {
|
|
639
630
|
// `reason` is the host's to display and connecta's to keep out of the
|
|
640
631
|
// downstream call, so this destructuring is the whole of its handling:
|
|
@@ -657,7 +648,6 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
657
648
|
destructiveHint: false,
|
|
658
649
|
openWorldHint: true,
|
|
659
650
|
},
|
|
660
|
-
_meta: { ui: { visibility: ["model"] } },
|
|
661
651
|
}, async (args) => mt.authorizeConnector(args));
|
|
662
652
|
server.registerTool("get_result", {
|
|
663
653
|
description: GET_RESULT_DESC,
|
|
@@ -671,6 +661,5 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
671
661
|
maxBytes: z.number().int().min(MIN_MAX_RESULT_BYTES).optional(),
|
|
672
662
|
}),
|
|
673
663
|
annotations: READ_ONLY_LOCAL,
|
|
674
|
-
_meta: { ui: { visibility: ["model"] } },
|
|
675
664
|
}, async (args) => mt.getResult(args));
|
|
676
665
|
}
|
package/dist/registry.js
CHANGED
|
@@ -131,6 +131,10 @@ export class Registry {
|
|
|
131
131
|
this.persistToolCatalog = opts.persistToolCatalog ?? true;
|
|
132
132
|
this.maxResultBytes = resolveMaxResultBytes(opts.maxResultBytes, DEFAULT_MAX_RESULT_BYTES);
|
|
133
133
|
for (const c of connectors) {
|
|
134
|
+
if ("handleRequest" in c) {
|
|
135
|
+
throw new Error(`Connector "${c.id}" declares removed handleRequest. ` +
|
|
136
|
+
"Move custom HTTP routes into the deployment's fetch handler.");
|
|
137
|
+
}
|
|
134
138
|
if (!ID_RE.test(c.id)) {
|
|
135
139
|
throw new Error(`Invalid connector id "${c.id}": must match ${ID_RE.source}`);
|
|
136
140
|
}
|
package/dist/routes/mcp.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createMcpHandler, isLegacyRequest, McpServer, WebStandardStreamableHTTPServerTransport, } from "@modelcontextprotocol/server";
|
|
2
|
-
import { MCP_APPS_EXTENSION, PROGRAM_UI_MIME_TYPE, PROGRAM_UI_RESOURCE_URI, PROGRAM_UI_SHELL_HTML, } from "../apps-shell.js";
|
|
3
2
|
import { registerExecuteTool } from "../execute.js";
|
|
4
3
|
import { ExecutorAdmissionError, } from "../executor-admission.js";
|
|
5
4
|
import { registerMetaTools } from "../meta-tools.js";
|
|
@@ -149,56 +148,10 @@ function toolkitRetired(logger) {
|
|
|
149
148
|
},
|
|
150
149
|
});
|
|
151
150
|
}
|
|
152
|
-
/**
|
|
153
|
-
* U5: one static template, served by a handler that answers exactly one URI
|
|
154
|
-
* and fails on every other. Registering it is also what declares the
|
|
155
|
-
* `resources` capability — which is why `resources/list` has to answer, and
|
|
156
|
-
* why it answers with nothing. That is the Apps spec's permitted omission of
|
|
157
|
-
* UI-only resources from listing, taken exactly: the capability stays honest
|
|
158
|
-
* because the method answers, and nothing downstream is ever listed or
|
|
159
|
-
* aggregated. Widening this handler to proxy downstream templates is a
|
|
160
|
-
* decision (see the design record), not a diff.
|
|
161
|
-
*/
|
|
162
|
-
function registerProgramUiResource(server) {
|
|
163
|
-
server.registerResource("connecta-program-ui", PROGRAM_UI_RESOURCE_URI, {
|
|
164
|
-
title: "connecta program view",
|
|
165
|
-
description: "The MCP Apps shell that renders HTML an execute_code program handed connecta.ui.",
|
|
166
|
-
mimeType: PROGRAM_UI_MIME_TYPE,
|
|
167
|
-
}, (uri) => ({
|
|
168
|
-
contents: [
|
|
169
|
-
{
|
|
170
|
-
uri: uri.href,
|
|
171
|
-
mimeType: PROGRAM_UI_MIME_TYPE,
|
|
172
|
-
text: PROGRAM_UI_SHELL_HTML,
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
}));
|
|
176
|
-
// The SDK's generated listing would advertise the template it just
|
|
177
|
-
// registered. Replace it rather than accept that: the URI reaches the host
|
|
178
|
-
// through tool metadata, so the listing has nothing to carry.
|
|
179
|
-
server.server.setRequestHandler("resources/list", () => ({ resources: [] }));
|
|
180
|
-
}
|
|
181
151
|
async function serveMcp(request, opts, baseUrl, actor, registry, runtimeContext) {
|
|
182
152
|
const createServer = () => {
|
|
183
153
|
const server = new McpServer(opts.serverInfo, {
|
|
184
154
|
instructions: instructionsFor(),
|
|
185
|
-
// U11: the Apps extension must be explicitly negotiated, and a
|
|
186
|
-
// conforming client acts on an extension only when both sides declare
|
|
187
|
-
// it — without this line no host reads execute_code's _meta.ui, no host
|
|
188
|
-
// fetches the shell, and the whole design is inert. This is the one
|
|
189
|
-
// extension connecta advertises; the versioned extensions framework
|
|
190
|
-
// stays declined as a general surface (https://github.com/zackbart/connecta/blob/main/records/mcp-2026-07-28.md).
|
|
191
|
-
capabilities: {
|
|
192
|
-
extensions: {
|
|
193
|
-
[MCP_APPS_EXTENSION]: { mimeTypes: [PROGRAM_UI_MIME_TYPE] },
|
|
194
|
-
},
|
|
195
|
-
// Registering the shell below declares `resources` on its own, but it
|
|
196
|
-
// would default `listChanged` to true. Connecta serves one build-time
|
|
197
|
-
// template and never sends a list_changed notification, so say so:
|
|
198
|
-
// a client that subscribes on the strength of that flag would wait
|
|
199
|
-
// forever for an event this server has no way to produce.
|
|
200
|
-
resources: { listChanged: false },
|
|
201
|
-
},
|
|
202
155
|
cacheHints: {
|
|
203
156
|
"tools/list": {
|
|
204
157
|
ttlMs: 3_600_000,
|
|
@@ -206,7 +159,6 @@ async function serveMcp(request, opts, baseUrl, actor, registry, runtimeContext)
|
|
|
206
159
|
},
|
|
207
160
|
},
|
|
208
161
|
});
|
|
209
|
-
registerProgramUiResource(server);
|
|
210
162
|
const activity = opts.activity
|
|
211
163
|
? {
|
|
212
164
|
sink: opts.activity,
|
package/dist/server.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export type { ServerOptions } from "./routes/shared.js";
|
|
|
4
4
|
* Build the Web-standard fetch handler.
|
|
5
5
|
*
|
|
6
6
|
* Route ordering is the contract: private mutation routes precede wildcard
|
|
7
|
-
* OPTIONS,
|
|
8
|
-
* wrapper is applied to every response.
|
|
7
|
+
* OPTIONS, and the security wrapper is applied to every response, including 404s.
|
|
9
8
|
*/
|
|
10
9
|
export declare function createFetchHandler(opts: ServerOptions): (request: Request, runtimeContext?: RuntimeExecutionContext) => Promise<Response>;
|
package/dist/server.js
CHANGED
|
@@ -10,8 +10,7 @@ import { routeUi } from "./routes/ui.js";
|
|
|
10
10
|
* Build the Web-standard fetch handler.
|
|
11
11
|
*
|
|
12
12
|
* Route ordering is the contract: private mutation routes precede wildcard
|
|
13
|
-
* OPTIONS,
|
|
14
|
-
* wrapper is applied to every response.
|
|
13
|
+
* OPTIONS, and the security wrapper is applied to every response, including 404s.
|
|
15
14
|
*/
|
|
16
15
|
export function createFetchHandler(opts) {
|
|
17
16
|
const { auth, publicUrl, registry } = opts;
|
|
@@ -147,23 +146,6 @@ export function createFetchHandler(opts) {
|
|
|
147
146
|
const mcp = await routeMcp(context);
|
|
148
147
|
if (mcp)
|
|
149
148
|
return mcp;
|
|
150
|
-
// Connector-owned public routes, dispatched last: a connector can add a
|
|
151
|
-
// route but never shadow one of connecta's own. A throw here is the
|
|
152
|
-
// connector's bug, not a missing route, so it surfaces as 500 rather
|
|
153
|
-
// than falling through to 404.
|
|
154
|
-
for (const connector of registry.listConnectors()) {
|
|
155
|
-
if (!connector.handleRequest)
|
|
156
|
-
continue;
|
|
157
|
-
try {
|
|
158
|
-
const response = await connector.handleRequest(request, registry.contextFor(connector.id, baseUrl));
|
|
159
|
-
if (response)
|
|
160
|
-
return response;
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
opts.logger.error(`[connecta] connector "${connector.id}" handleRequest failed`, error);
|
|
164
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
149
|
return new Response("Not Found", { status: 404 });
|
|
168
150
|
};
|
|
169
151
|
return withSecurityHeaders(await route(), url, path);
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Connector } from "./types.js";
|
|
2
|
-
export declare const CONNECTA_INSTRUCTIONS = "Choose a route before discovery. A known-address read needs only call_tool. Unknown-address read-only work starts with
|
|
2
|
+
export declare const CONNECTA_INSTRUCTIONS = "Choose a route before discovery. A known-address read needs only call_tool. Unknown-address read-only work starts with execute_code to discover, call, and return the answer; use the same route for reduction, multiple or dependent calls, loops, joins, or branches. Keep discovery and calls together when schemas suffice; do not return catalog matches alone. Inspect unfamiliar result shapes with a small sample before proceeding. Only readOnlyHint: true tools run there. Keep catalog inspection and unannotated, write-capable, or destructive work top level: search_tools then call_destructive_tool when a call is needed. After auth_required use authorize_connector. After a truncated direct result use get_result. Guidance is on demand: fetch skills({ name: \"usage\" }) only when these instructions and the tool description are insufficient or a run needs repair.";
|
|
3
3
|
/** Shared Connecta routing guidance, byte-identical across deployments. */
|
|
4
4
|
export declare const USAGE_SKILL: string;
|
|
5
5
|
/** The always-loaded MCP `instructions` string. */
|