@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.
@@ -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 { sleep, withDeadline } from "./timeout.js";
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 resolve();
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
- while (true) {
297
- attempts++;
298
- let permit;
299
- let attemptError;
300
- let attemptFailed = false;
301
- try {
302
- permit = await timed((elapsed) => { admissionMs += elapsed; }, () => this.registry.admitCall(resolved.connector.id, {
303
- toolName: resolved.toolName,
304
- args: args ?? {},
305
- ...defined({ signal: context.requestSignal }),
306
- }));
307
- const raw = await timed((elapsed) => { connectorMs += elapsed; }, () => {
308
- const call = (callSignal) => {
309
- const connectorContext = this.registry.contextFor(resolved.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({ signal: callSignal, timeoutMs: context.timeoutMs }));
310
- if (resolved.connector.credential &&
311
- !connectorContext.credential) {
312
- throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured. Call " +
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
- // The reported window is longer than the engine will park a
361
- // synchronous request for. Fall through to failure with
362
- // retryAfterMs reported verbatim so the agent can re-issue.
363
- }
364
- return failed(details);
365
- }
366
- break;
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 () => {
@@ -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, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, retryBackoffMs, };
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 is reached through `connecta.search` / `connecta.describe` /
116
- * `connecta.batch` inside a program, which `execute_code` builds over the same
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
  */
@@ -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, MAX_RETRY_BACKOFF_MS, retryBackoffMs, } from "./invocation.js";
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, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, retryBackoffMs, };
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 is reached through `connecta.search` / `connecta.describe` /
578
- * `connecta.batch` inside a program, which `execute_code` builds over the same
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.object(CALL_INPUT_SCHEMA),
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.object({
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
  }
@@ -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, every built-in precedes connector-owned routes, and the security
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, every built-in precedes connector-owned routes, and the security
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 one execute_code program that discovers, calls, and returns the answer; use the same route for reduction, multiple or dependent calls, loops, joins, or branches. 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. connecta.ui(html) exists only inside execute_code, not in connector search; return the same summary data the HTML renders. Guidance is on demand: fetch skills({ name: \"usage\" }) only when these instructions and the tool description are insufficient or a run needs repair.";
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. */