@zackbart/connecta 0.18.3 → 0.20.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +131 -4
  2. package/dist/apps-shell.d.ts +10 -12
  3. package/dist/apps-shell.js +29 -220
  4. package/dist/catalog-service.d.ts +16 -13
  5. package/dist/catalog-service.js +106 -115
  6. package/dist/catalog.js +29 -46
  7. package/dist/connector-scope.js +2 -7
  8. package/dist/connectors/api.d.ts +4 -16
  9. package/dist/connectors/api.js +19 -46
  10. package/dist/connectors/guarded-fetch.d.ts +9 -23
  11. package/dist/connectors/guarded-fetch.js +38 -76
  12. package/dist/connectors/remote-mcp.js +36 -79
  13. package/dist/errors.d.ts +6 -27
  14. package/dist/errors.js +8 -5
  15. package/dist/execute.d.ts +23 -28
  16. package/dist/execute.js +101 -257
  17. package/dist/executor-result.d.ts +1 -0
  18. package/dist/executor-result.js +4 -11
  19. package/dist/executors/quickjs-child.js +1 -3
  20. package/dist/executors/quickjs-runtime.js +1 -3
  21. package/dist/executors/quickjs.js +1 -3
  22. package/dist/index.js +134 -123
  23. package/dist/invocation.d.ts +1 -1
  24. package/dist/invocation.js +113 -183
  25. package/dist/meta-tools.d.ts +15 -29
  26. package/dist/meta-tools.js +41 -582
  27. package/dist/operator-ui/generated.d.ts +2 -2
  28. package/dist/providers/cloudflare.d.ts +2 -18
  29. package/dist/providers/cloudflare.js +1460 -2451
  30. package/dist/providers/linear.d.ts +4 -41
  31. package/dist/providers/linear.js +8 -39
  32. package/dist/providers/mixpanel.d.ts +3 -25
  33. package/dist/providers/mixpanel.js +7 -22
  34. package/dist/providers/notion.d.ts +1 -15
  35. package/dist/providers/notion.js +44 -173
  36. package/dist/providers/revenuecat.d.ts +4 -57
  37. package/dist/providers/revenuecat.js +10 -93
  38. package/dist/providers/stripe.d.ts +1 -12
  39. package/dist/providers/stripe.js +7 -45
  40. package/dist/registry.d.ts +9 -34
  41. package/dist/registry.js +9 -103
  42. package/dist/routes/mcp.js +1 -1
  43. package/dist/routes/oauth.js +3 -3
  44. package/dist/routes/shared.d.ts +15 -15
  45. package/dist/routes/shared.js +1 -3
  46. package/dist/skills.d.ts +1 -1
  47. package/dist/skills.js +5 -5
  48. package/dist/timeout.d.ts +8 -7
  49. package/dist/timeout.js +47 -38
  50. package/dist/types.d.ts +3 -3
  51. package/dist/ui.d.ts +1 -25
  52. package/dist/ui.js +18 -45
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/documentation/call-admission.md +1 -1
  56. package/documentation/cloudflare.md +1 -1
  57. package/documentation/code-mode.md +25 -25
  58. package/documentation/connectors.md +24 -1
  59. package/documentation/linear.md +1 -1
  60. package/documentation/meta-tools.md +4 -30
  61. package/documentation/mixpanel.md +1 -1
  62. package/documentation/notion.md +1 -1
  63. package/documentation/operations.md +29 -25
  64. package/documentation/provider-conventions.md +4 -5
  65. package/documentation/revenuecat.md +1 -1
  66. package/documentation/stripe.md +1 -1
  67. package/documentation/upgrading.md +43 -8
  68. package/ethos.md +75 -121
  69. package/package.json +3 -4
  70. package/templates/node/package.json +1 -1
  71. package/documentation/code-first-exploration.md +0 -292
  72. package/documentation/mcp-2026-07-28.md +0 -46
  73. package/documentation/mcp-ui-design.md +0 -382
  74. package/documentation/program-ui-read-calls.md +0 -213
  75. package/documentation/provider-audit.md +0 -198
  76. package/documentation/rich-output-design.md +0 -211
@@ -2,7 +2,7 @@ import { compactDiscoverySchema, compactSchema, lexicalCorpusStatistics, lexical
2
2
  import { mapSettledWithConcurrency, resolveDiscoveryConcurrency, } from "./concurrency.js";
3
3
  import { boundedEchoText, classifyCallError, framingError, } from "./errors.js";
4
4
  import { connectorGuide, connectorGuideRequired, connectorGuideSummary, connectorSkillName, } from "./skills.js";
5
- import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, withAbortableTimeout, } from "./timeout.js";
5
+ import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, withDeadline, } from "./timeout.js";
6
6
  import { isExplicitlyReadOnly } from "./tool-safety.js";
7
7
  export const DEFAULT_SEARCH_LIMIT = 8;
8
8
  export const MAX_SEARCH_LIMIT = 100;
@@ -261,10 +261,13 @@ export class CatalogService {
261
261
  return loading;
262
262
  }
263
263
  loadForDiscovery(id, label) {
264
- return withAbortableTimeout((signal) => this.loadConnector(id, {
264
+ return withDeadline((signal) => this.loadConnector(id, {
265
265
  signal,
266
266
  timeoutMs: this.probeTimeoutMs,
267
- }), this.probeTimeoutMs, label);
267
+ }), {
268
+ timeoutMs: this.probeTimeoutMs,
269
+ timeoutError: new Error(`${label} timed out after ${this.probeTimeoutMs}ms`),
270
+ });
268
271
  }
269
272
  outputSchema(connectorId, tool) {
270
273
  if (tool.outputSchema)
@@ -274,17 +277,42 @@ export class CatalogService {
274
277
  ? { schema: observed, source: "observed" }
275
278
  : {};
276
279
  }
280
+ unknownAddressFailure(address, query) {
281
+ return {
282
+ ok: false,
283
+ error: {
284
+ ...framingError("unknown_address", `Unknown address "${boundedEchoText(address)}"`),
285
+ nextAction: this.searchRecovery({ query: recoveryQuery(query) }, "Find the configured canonical address before retrying."),
286
+ },
287
+ catalogMs: 0,
288
+ };
289
+ }
290
+ catalogLoadFailure(cause, started, connector, toolName) {
291
+ return {
292
+ ok: false,
293
+ error: classifyCallError(cause, "catalog_lookup_failed"),
294
+ catalogMs: Date.now() - started,
295
+ connector,
296
+ toolName,
297
+ cause,
298
+ };
299
+ }
300
+ unknownToolFailure(toolName, connector, started) {
301
+ return {
302
+ ok: false,
303
+ error: {
304
+ ...framingError("unknown_tool", `Unknown tool "${boundedEchoText(toolName)}" on connector "${connector.id}"`),
305
+ nextAction: this.searchRecovery({ query: recoveryQuery(toolName), connector: connector.id }, "Find the connector's current canonical tool address."),
306
+ },
307
+ catalogMs: Date.now() - started,
308
+ connector,
309
+ toolName,
310
+ };
311
+ }
277
312
  async resolveTool(address, callOptions = {}) {
278
313
  const resolved = this.registry.resolveAddress(address);
279
314
  if (!resolved) {
280
- return {
281
- ok: false,
282
- error: {
283
- ...framingError("unknown_address", `Unknown address "${boundedEchoText(address)}"`),
284
- nextAction: this.searchRecovery({ query: recoveryQuery(address) }, "Find the configured canonical address before retrying."),
285
- },
286
- catalogMs: 0,
287
- };
315
+ return this.unknownAddressFailure(address, address);
288
316
  }
289
317
  const started = Date.now();
290
318
  let tools;
@@ -292,30 +320,11 @@ export class CatalogService {
292
320
  tools = await this.loadConnector(resolved.connector.id, callOptions);
293
321
  }
294
322
  catch (cause) {
295
- return {
296
- ok: false,
297
- error: classifyCallError(cause, "catalog_lookup_failed"),
298
- catalogMs: Date.now() - started,
299
- connector: resolved.connector,
300
- toolName: resolved.toolName,
301
- cause,
302
- };
323
+ return this.catalogLoadFailure(cause, started, resolved.connector, resolved.toolName);
303
324
  }
304
325
  const definition = tools.find((tool) => tool.name === resolved.toolName);
305
326
  if (!definition) {
306
- return {
307
- ok: false,
308
- error: {
309
- ...framingError("unknown_tool", `Unknown tool "${boundedEchoText(resolved.toolName)}" on connector "${resolved.connector.id}"`),
310
- nextAction: this.searchRecovery({
311
- query: recoveryQuery(resolved.toolName),
312
- connector: resolved.connector.id,
313
- }, "Find the connector's current canonical tool address."),
314
- },
315
- catalogMs: Date.now() - started,
316
- connector: resolved.connector,
317
- toolName: resolved.toolName,
318
- };
327
+ return this.unknownToolFailure(resolved.toolName, resolved.connector, started);
319
328
  }
320
329
  return {
321
330
  ok: true,
@@ -335,14 +344,7 @@ export class CatalogService {
335
344
  async resolveToolAlias(connectorId, alias, aliasFor, callOptions = {}) {
336
345
  const connector = this.registry.getConnector(connectorId);
337
346
  if (!connector) {
338
- return {
339
- ok: false,
340
- error: {
341
- ...framingError("unknown_address", `Unknown address "${boundedEchoText(`${connectorId}.${alias}`)}"`),
342
- nextAction: this.searchRecovery({ query: recoveryQuery(alias) }, "Find the configured canonical address before retrying."),
343
- },
344
- catalogMs: 0,
345
- };
347
+ return this.unknownAddressFailure(`${connectorId}.${alias}`, alias);
346
348
  }
347
349
  const started = Date.now();
348
350
  let tools;
@@ -350,27 +352,11 @@ export class CatalogService {
350
352
  tools = await this.loadConnector(connector.id, callOptions);
351
353
  }
352
354
  catch (cause) {
353
- return {
354
- ok: false,
355
- error: classifyCallError(cause, "catalog_lookup_failed"),
356
- catalogMs: Date.now() - started,
357
- connector,
358
- toolName: alias,
359
- cause,
360
- };
355
+ return this.catalogLoadFailure(cause, started, connector, alias);
361
356
  }
362
357
  const [definition, ...collisions] = tools.filter((tool) => aliasFor(tool.name) === alias);
363
358
  if (!definition) {
364
- return {
365
- ok: false,
366
- error: {
367
- ...framingError("unknown_tool", `Unknown tool "${boundedEchoText(alias)}" on connector "${connector.id}"`),
368
- nextAction: this.searchRecovery({ query: recoveryQuery(alias), connector: connector.id }, "Find the connector's current canonical tool address."),
369
- },
370
- catalogMs: Date.now() - started,
371
- connector,
372
- toolName: alias,
373
- };
359
+ return this.unknownToolFailure(alias, connector, started);
374
360
  }
375
361
  if (collisions.length > 0) {
376
362
  const names = [definition, ...collisions]
@@ -494,18 +480,19 @@ export class CatalogService {
494
480
  a.order - b.order);
495
481
  });
496
482
  const pageMatches = matches.slice(offset, offset + limit);
483
+ const withSchemas = args.includeSchemas;
497
484
  const entries = pageMatches.map((match) => {
498
- const output = args.includeSchemas
485
+ const output = withSchemas
499
486
  ? this.outputSchema(match.connector.id, match.tool)
500
487
  : {};
501
488
  const input = match.tool.inputSchema ?? { type: "object" };
502
- const renderedInput = args.includeSchemas
503
- ? renderSearchSchema(input, args.includeSchemas)
489
+ const renderedInput = withSchemas
490
+ ? renderSearchSchema(input, withSchemas)
504
491
  : undefined;
505
- const renderedOutput = args.includeSchemas && output.schema
506
- ? renderSearchSchema(output.schema, args.includeSchemas)
492
+ const renderedOutput = withSchemas && output.schema
493
+ ? renderSearchSchema(output.schema, withSchemas)
507
494
  : undefined;
508
- const schemaKeys = args.includeSchemas && args.includeSchemaKeys
495
+ const schemaKeys = withSchemas && args.includeSchemaKeys
509
496
  ? schemaKeyMetadata(input, output.schema)
510
497
  : undefined;
511
498
  const description = summarizeDiscoveryDescription(match.tool.description, args.fullDescriptions === true);
@@ -523,7 +510,7 @@ export class CatalogService {
523
510
  name: match.tool.name,
524
511
  address: `${match.connector.id}.${match.tool.name}`,
525
512
  ...(description !== undefined ? { description } : {}),
526
- ...(args.includeSchemas
513
+ ...(withSchemas
527
514
  ? {
528
515
  inputSchema: renderedInput?.schema,
529
516
  }
@@ -531,12 +518,12 @@ export class CatalogService {
531
518
  ...(renderedInput?.truncated
532
519
  ? { inputSchemaTruncated: true }
533
520
  : {}),
534
- ...(args.includeSchemas && output.schema
521
+ ...(withSchemas && output.schema
535
522
  ? {
536
523
  outputSchema: renderedOutput?.schema,
537
524
  }
538
525
  : {}),
539
- ...(args.includeSchemas && output.source
526
+ ...(withSchemas && output.source
540
527
  ? { outputSchemaSource: output.source }
541
528
  : {}),
542
529
  ...(renderedOutput?.truncated
@@ -671,41 +658,46 @@ export class CatalogService {
671
658
  (queryTerms.length > 0
672
659
  ? matchMode === "partial"
673
660
  : unknownConnectorGuidance !== undefined || unavailableCatalogs > 0);
674
- const guidance = unsearchableQuery
675
- ? (unknownConnectorGuidance ??
676
- (scopedConnector && unavailableCatalogs > 0
661
+ const searchGuidance = () => {
662
+ if (unknownConnectorGuidance)
663
+ return unknownConnectorGuidance;
664
+ if (unsearchableQuery) {
665
+ return scopedConnector && unavailableCatalogs > 0
677
666
  ? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
678
- : "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query."))
679
- : queryTerms.length === 0
680
- ? // A browse has no terms to advise about, so it stays silent unless
681
- // the scope itself failed: the guidance on a scoped miss recommends
682
- // browsing with an empty query, and that advice must not lead into a
683
- // dead end that looks like a connector with no tools.
684
- (unknownConnectorGuidance ??
685
- (unavailableCatalogs === 0
686
- ? undefined
687
- : scopedConnector
688
- ? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
689
- : `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Scope by connector to see the typed reason.`))
690
- : matches.length === 0
691
- ? (unknownConnectorGuidance ??
692
- (scopedConnector
693
- ? unavailableCatalogs > 0
694
- ? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
695
- : scopedGuide?.required
696
- ? `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}`
697
- : `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`
698
- : (identityGuidance ??
699
- (unavailableCatalogs === 0
700
- ? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
701
- : `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}`))))
702
- : matchMode === "partial"
703
- ? scopedConnector
704
- ? `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`
705
- : unavailableCatalogs === 0
706
- ? "No single tool matched every term. Split distinct intents into separate searches."
707
- : "No single tool matched every term in the catalogs that answered. Split distinct intents into separate searches."
708
- : undefined;
667
+ : "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query.";
668
+ }
669
+ if (queryTerms.length === 0) {
670
+ if (unavailableCatalogs === 0)
671
+ return undefined;
672
+ return scopedConnector
673
+ ? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
674
+ : `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Scope by connector to see the typed reason.`;
675
+ }
676
+ if (matches.length === 0) {
677
+ if (scopedConnector) {
678
+ if (unavailableCatalogs > 0) {
679
+ return `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`;
680
+ }
681
+ return scopedGuide?.required
682
+ ? `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}`
683
+ : `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`;
684
+ }
685
+ if (identityGuidance)
686
+ return identityGuidance;
687
+ return unavailableCatalogs === 0
688
+ ? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
689
+ : `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}`;
690
+ }
691
+ if (matchMode !== "partial")
692
+ return undefined;
693
+ if (scopedConnector) {
694
+ return `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`;
695
+ }
696
+ return unavailableCatalogs === 0
697
+ ? "No single tool matched every term. Split distinct intents into separate searches."
698
+ : "No single tool matched every term in the catalogs that answered. Split distinct intents into separate searches.";
699
+ };
700
+ const guidance = searchGuidance();
709
701
  return {
710
702
  entries,
711
703
  total: matches.length,
@@ -859,6 +851,17 @@ export class CatalogService {
859
851
  });
860
852
  }
861
853
  }
854
+ function pageTail(page) {
855
+ return {
856
+ total: page.total,
857
+ offset: page.offset,
858
+ limit: page.limit,
859
+ hasMore: page.hasMore,
860
+ ...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
861
+ ...(page.matchMode ? { matchMode: page.matchMode } : {}),
862
+ ...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
863
+ };
864
+ }
862
865
  export function groupedSearchResult(page) {
863
866
  const groups = [];
864
867
  const byConnector = new Map();
@@ -884,13 +887,7 @@ export function groupedSearchResult(page) {
884
887
  }
885
888
  return {
886
889
  connectors: groups,
887
- total: page.total,
888
- offset: page.offset,
889
- limit: page.limit,
890
- hasMore: page.hasMore,
891
- ...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
892
- ...(page.matchMode ? { matchMode: page.matchMode } : {}),
893
- ...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
890
+ ...pageTail(page),
894
891
  };
895
892
  }
896
893
  export function flatSearchResult(page) {
@@ -902,12 +899,6 @@ export function flatSearchResult(page) {
902
899
  ? { guideSummary: entry.guideSummary }
903
900
  : {}),
904
901
  })),
905
- total: page.total,
906
- offset: page.offset,
907
- limit: page.limit,
908
- hasMore: page.hasMore,
909
- ...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
910
- ...(page.matchMode ? { matchMode: page.matchMode } : {}),
911
- ...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
902
+ ...pageTail(page),
912
903
  };
913
904
  }
package/dist/catalog.js CHANGED
@@ -123,9 +123,6 @@ function indexFor(tools) {
123
123
  }
124
124
  return index;
125
125
  }
126
- function documentsFor(tools) {
127
- return indexFor(tools).documents;
128
- }
129
126
  /**
130
127
  * Whole-token equality is the ordinary lexical match. A deliberately narrow
131
128
  * inflection check retains useful singular/plural and past-tense recall
@@ -293,7 +290,7 @@ export function rankTools(tools, query, mode = "all", statistics = lexicalCorpus
293
290
  const exactNamePhrase = normalized(exactNameQuery);
294
291
  const terms = [...new Set(phrase.split(/\s+/).filter(Boolean))];
295
292
  const ranked = [];
296
- documentsFor(tools).forEach((doc, order) => {
293
+ indexFor(tools).documents.forEach((doc, order) => {
297
294
  const scored = scoreDocument(doc, phrase, terms, mode, statistics);
298
295
  if (scored !== null) {
299
296
  ranked.push({
@@ -425,6 +422,9 @@ function renderSchema(schema, defs, seen, depth, options) {
425
422
  return JSON.stringify(schema);
426
423
  }
427
424
  const s = schema;
425
+ const constrain = (rendered) => options.renderConstraints
426
+ ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
427
+ : rendered;
428
428
  // allOf composes rather than replaces: it is checked before every other
429
429
  // keyword, and renders the schema's own shape alongside its members instead
430
430
  // of returning early. A schema carrying both allOf and properties (the usual
@@ -457,9 +457,7 @@ function renderSchema(schema, defs, seen, depth, options) {
457
457
  seen.add(name);
458
458
  const rendered = renderSchema(target, defs, seen, depth, options);
459
459
  seen.delete(name);
460
- return options.renderConstraints
461
- ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
462
- : rendered;
460
+ return constrain(rendered);
463
461
  }
464
462
  const union = (s.oneOf ?? s.anyOf);
465
463
  if (Array.isArray(union)) {
@@ -467,15 +465,11 @@ function renderSchema(schema, defs, seen, depth, options) {
467
465
  .map((u) => renderSchema(u, defs, seen, depth + 1, options))
468
466
  .join(" | ") ||
469
467
  "unknown";
470
- return options.renderConstraints
471
- ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
472
- : rendered;
468
+ return constrain(rendered);
473
469
  }
474
470
  if (Array.isArray(s.enum)) {
475
471
  const rendered = renderEnum(s.enum, options.enumByteLimit, options.onEnumTruncated);
476
- return options.renderConstraints
477
- ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
478
- : rendered;
472
+ return constrain(rendered);
479
473
  }
480
474
  // Checked before type/properties so a discriminator like
481
475
  // { type: "string", const: "emoji" } renders as "emoji" rather than string.
@@ -483,9 +477,7 @@ function renderSchema(schema, defs, seen, depth, options) {
483
477
  // `const: undefined` must fall through to the regular type rendering.
484
478
  if (s.const !== undefined) {
485
479
  const rendered = JSON.stringify(s.const);
486
- return options.renderConstraints
487
- ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
488
- : rendered;
480
+ return constrain(rendered);
489
481
  }
490
482
  const type = s.type;
491
483
  if (type === "array" || s.items) {
@@ -519,15 +511,11 @@ function renderSchema(schema, defs, seen, depth, options) {
519
511
  .join(", ")} }`;
520
512
  }
521
513
  if (typeof type === "string") {
522
- return options.renderConstraints
523
- ? renderConstraints(type, s, options.constraintByteLimit, options.onConstraintTruncated)
524
- : type;
514
+ return constrain(type);
525
515
  }
526
516
  if (Array.isArray(type)) {
527
517
  const rendered = type.join(" | ");
528
- return options.renderConstraints
529
- ? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
530
- : rendered;
518
+ return constrain(rendered);
531
519
  }
532
520
  if (options.renderConstraints && constraintEntries(s).length > 0) {
533
521
  return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
@@ -535,15 +523,18 @@ function renderSchema(schema, defs, seen, depth, options) {
535
523
  return JSON.stringify(schema);
536
524
  }
537
525
  const compactSchemas = new WeakMap();
526
+ function defsOf(schema) {
527
+ return {
528
+ ...schema.$defs,
529
+ ...schema.definitions,
530
+ };
531
+ }
538
532
  /** Render and cache a compact TypeScript-like representation of JSON Schema. */
539
533
  export function compactSchema(schema) {
540
534
  const cached = compactSchemas.get(schema);
541
535
  if (cached)
542
536
  return cached;
543
- const defs = {
544
- ...schema.$defs,
545
- ...schema.definitions,
546
- };
537
+ const defs = defsOf(schema);
547
538
  let rendered;
548
539
  try {
549
540
  rendered = renderSchema(schema, defs, new Set(), 0, {
@@ -603,24 +594,24 @@ export function compactDiscoverySchema(schema) {
603
594
  const cached = compactDiscoverySchemas.get(schema);
604
595
  if (cached)
605
596
  return cached;
606
- const defs = {
607
- ...schema.$defs,
608
- ...schema.definitions,
609
- };
597
+ const defs = defsOf(schema);
610
598
  let rendered;
611
599
  let enumTruncated = false;
612
600
  let constraintTruncated = false;
601
+ const base = {
602
+ propertyDescriptions: false,
603
+ requiredFirst: true,
604
+ enumByteLimit: MAX_COMPACT_DISCOVERY_ENUM_BYTES,
605
+ onEnumTruncated: () => {
606
+ enumTruncated = true;
607
+ },
608
+ };
613
609
  try {
614
610
  rendered = renderSchema(schema, defs, new Set(), 0, {
615
- propertyDescriptions: false,
616
- requiredFirst: true,
611
+ ...base,
617
612
  // Three near-cap enums spend about three quarters of the complete shape
618
613
  // budget, leaving the final quarter for surrounding syntax before the
619
614
  // unchanged global fallback applies. Whole values keep this UTF-8 safe.
620
- enumByteLimit: MAX_COMPACT_DISCOVERY_ENUM_BYTES,
621
- onEnumTruncated: () => {
622
- enumTruncated = true;
623
- },
624
615
  renderConstraints: true,
625
616
  constraintByteLimit: MAX_COMPACT_DISCOVERY_CONSTRAINT_BYTES,
626
617
  onConstraintTruncated: () => {
@@ -635,12 +626,7 @@ export function compactDiscoverySchema(schema) {
635
626
  MAX_COMPACT_DISCOVERY_SCHEMA_BYTES) {
636
627
  try {
637
628
  rendered = renderSchema(schema, defs, new Set(), 0, {
638
- propertyDescriptions: false,
639
- requiredFirst: true,
640
- enumByteLimit: MAX_COMPACT_DISCOVERY_ENUM_BYTES,
641
- onEnumTruncated: () => {
642
- enumTruncated = true;
643
- },
629
+ ...base,
644
630
  renderConstraints: false,
645
631
  });
646
632
  constraintTruncated = true;
@@ -683,10 +669,7 @@ export function compactDiscoverySchema(schema) {
683
669
  export function schemaObjectKeys(schema) {
684
670
  if (!schema)
685
671
  return undefined;
686
- const defs = {
687
- ...schema.$defs,
688
- ...schema.definitions,
689
- };
672
+ const defs = defsOf(schema);
690
673
  try {
691
674
  return objectKeys(schema, defs, new Set(), 0);
692
675
  }
@@ -1,3 +1,4 @@
1
+ import { sleep } from "./timeout.js";
1
2
  /** Enough for local transport abort/close without letting cleanup own latency. */
2
3
  const CONNECTOR_SCOPE_CLOSE_BUDGET_MS = 100;
3
4
  /**
@@ -11,13 +12,7 @@ const CONNECTOR_SCOPE_CLOSE_BUDGET_MS = 100;
11
12
  const CONNECTOR_SCOPE_DEFER_BUDGET_MS = 2_000;
12
13
  /** Resolve when `work` settles or `budgetMs` expires; never reject. */
13
14
  function waitAtMost(work, budgetMs) {
14
- return new Promise((resolve) => {
15
- const timer = setTimeout(resolve, budgetMs);
16
- work.then(() => {
17
- clearTimeout(timer);
18
- resolve();
19
- });
20
- });
15
+ return Promise.race([work, sleep(budgetMs)]).then(() => { });
21
16
  }
22
17
  /**
23
18
  * Tell a connector that a scope owned by the core has ended.
@@ -1,4 +1,7 @@
1
1
  import type { Connector, ConnectorCallAdmissionPolicy, ConnectorCredentialConfig, ConnectorCredentialValues, ConnectorContext, ConnectorUsageGuide, CredentialTestResult, JsonSchema, ToolAnnotations } from "../types.js";
2
+ export declare function defined<T extends object>(value: T): {
3
+ [K in keyof T]?: Exclude<T[K], undefined>;
4
+ };
2
5
  export interface ApiTool {
3
6
  name: string;
4
7
  /**
@@ -63,20 +66,5 @@ export interface ApiOptions {
63
66
  validateArgs?: boolean;
64
67
  tools: ApiTool[];
65
68
  }
66
- /**
67
- * A connector defined entirely in code: static tool defs + fetch handlers.
68
- * Tool inputs are plain JSON Schema objects (bring your own zod-to-json-schema
69
- * conversion if you prefer zod). call_tool JSON-wraps the handler's return.
70
- *
71
- * Every tool declares a description and an explicit `annotations.readOnlyHint`,
72
- * and any `inputSchema` it carries must compile — a tool that fails the
73
- * contract throws here rather than reaching a catalog. Arguments are then
74
- * validated against `inputSchema` before the handler runs (disable with
75
- * `validateArgs: false`, which opts out of enforcement, not out of the schema
76
- * being real), and a schema that only reveals itself as unenforceable on first
77
- * use — an unresolvable `$ref`, say — fails the call rather than passing raw
78
- * arguments through. Remote MCP inputs are also validated, but in the shared
79
- * invocation path against the request-local downstream catalog, where a
80
- * downstream's schema is its own affair and stays fail-open.
81
- */
69
+ /** A static connector; see provider conventions' two construction-time checks. */
82
70
  export declare function api(id: string, opts: ApiOptions): Connector;
@@ -1,14 +1,8 @@
1
1
  import { compileValidator, validateToolInput } from "../validate.js";
2
- /**
3
- * Enforce the construction contract for one hand-written tool.
4
- *
5
- * Everything here is something only the author can supply and no runtime can
6
- * guess: what the tool does, and whether calling it needs a human's blessing.
7
- * Guessing either one is how a deployment boots into the wrong shape, so this
8
- * throws instead. Note what it does *not* do — it never reads a name, verb, or
9
- * HTTP method to infer a safety class. An unclassified tool is a bug in the
10
- * deployment, not a puzzle for connecta to solve.
11
- */
2
+ export function defined(value) {
3
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
4
+ }
5
+ /** Enforce provider conventions' two construction-time checks. */
12
6
  function checkToolContract(id, tool) {
13
7
  const address = `${id}.${tool.name}`;
14
8
  if (typeof tool.description !== "string" || tool.description.trim() === "") {
@@ -25,55 +19,34 @@ function checkToolContract(id, tool) {
25
19
  if (tool.inputSchema)
26
20
  compileValidator(tool.inputSchema, { address });
27
21
  }
28
- /**
29
- * A connector defined entirely in code: static tool defs + fetch handlers.
30
- * Tool inputs are plain JSON Schema objects (bring your own zod-to-json-schema
31
- * conversion if you prefer zod). call_tool JSON-wraps the handler's return.
32
- *
33
- * Every tool declares a description and an explicit `annotations.readOnlyHint`,
34
- * and any `inputSchema` it carries must compile — a tool that fails the
35
- * contract throws here rather than reaching a catalog. Arguments are then
36
- * validated against `inputSchema` before the handler runs (disable with
37
- * `validateArgs: false`, which opts out of enforcement, not out of the schema
38
- * being real), and a schema that only reveals itself as unenforceable on first
39
- * use — an unresolvable `$ref`, say — fails the call rather than passing raw
40
- * arguments through. Remote MCP inputs are also validated, but in the shared
41
- * invocation path against the request-local downstream catalog, where a
42
- * downstream's schema is its own affair and stays fail-open.
43
- */
22
+ /** A static connector; see provider conventions' two construction-time checks. */
44
23
  export function api(id, opts) {
45
24
  for (const t of opts.tools)
46
25
  checkToolContract(id, t);
47
26
  const defs = opts.tools.map((t) => ({
48
27
  name: t.name,
49
28
  description: t.description,
50
- ...(t.inputSchema !== undefined ? { inputSchema: t.inputSchema } : {}),
51
- ...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
29
+ ...defined({
30
+ inputSchema: t.inputSchema,
31
+ outputSchema: t.outputSchema,
32
+ }),
52
33
  annotations: t.annotations,
53
34
  }));
54
35
  const byName = new Map(opts.tools.map((t) => [t.name, t]));
55
36
  const validateArgs = opts.validateArgs ?? true;
56
37
  return {
57
38
  id,
58
- ...(opts.title !== undefined ? { title: opts.title } : {}),
39
+ ...defined({ title: opts.title }),
59
40
  kind: "api",
60
- ...(opts.description !== undefined
61
- ? { description: opts.description }
62
- : {}),
63
- ...(opts.maxResultBytes !== undefined
64
- ? { maxResultBytes: opts.maxResultBytes }
65
- : {}),
66
- ...(opts.callAdmission !== undefined
67
- ? { callAdmission: opts.callAdmission }
68
- : {}),
69
- ...(opts.usageGuide !== undefined ? { usageGuide: opts.usageGuide } : {}),
70
- ...(opts.credential !== undefined ? { credential: opts.credential } : {}),
71
- ...(opts.testCredential !== undefined
72
- ? { testCredential: opts.testCredential }
73
- : {}),
74
- ...(opts.testCredentials !== undefined
75
- ? { testCredentials: opts.testCredentials }
76
- : {}),
41
+ ...defined({
42
+ description: opts.description,
43
+ maxResultBytes: opts.maxResultBytes,
44
+ callAdmission: opts.callAdmission,
45
+ usageGuide: opts.usageGuide,
46
+ credential: opts.credential,
47
+ testCredential: opts.testCredential,
48
+ testCredentials: opts.testCredentials,
49
+ }),
77
50
  staticTools: defs,
78
51
  async listTools() {
79
52
  return defs;