@zackbart/connecta 0.24.3 → 0.25.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/AGENTS.md +58 -30
- package/CHANGELOG.md +317 -1
- package/README.md +5 -6
- package/dist/advertised-schema.js +53 -0
- package/dist/auth/downstream-oauth.js +490 -106
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +141 -105
- package/dist/catalog.js +258 -33
- package/dist/concurrency.js +24 -23
- package/dist/connector-scope.js +6 -37
- package/dist/connectors/api.d.ts +3 -3
- package/dist/connectors/api.js +10 -2
- package/dist/connectors/guarded-fetch.js +34 -4
- package/dist/connectors/remote-mcp.d.ts +6 -5
- package/dist/connectors/remote-mcp.js +325 -245
- package/dist/credential-contract.d.ts +9 -0
- package/dist/credential-rules.js +2 -2
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +45 -3
- package/dist/errors.d.ts +11 -3
- package/dist/errors.js +2 -1
- package/dist/execute.js +363 -257
- package/dist/executor-admission.js +67 -50
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +251 -210
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +63 -21
- package/dist/invocation.js +324 -285
- package/dist/meta-tools.js +304 -114
- package/dist/oauth-callback-outcome.js +94 -0
- package/dist/oauth-sealing.js +29 -0
- package/dist/operator-ui/fix-prompts.js +87 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +46 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +109 -2
- package/dist/program-source.js +205 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +85 -12
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +70 -5
- package/dist/providers/stripe.d.ts +10 -3
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +12 -2
- package/dist/registry.d.ts +13 -5
- package/dist/registry.js +317 -258
- package/dist/routes/activity.js +120 -137
- package/dist/routes/credentials.js +174 -163
- package/dist/routes/mcp.js +129 -70
- package/dist/routes/oauth-management.js +60 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +82 -0
- package/dist/routes/shared.d.ts +2 -0
- package/dist/routes/ui-data.js +210 -0
- package/dist/routes/ui.js +64 -32
- package/dist/runtime/admission.js +43 -0
- package/dist/runtime/call-admission.js +67 -0
- package/dist/runtime/connector-scope.js +72 -0
- package/dist/runtime/run.js +174 -0
- package/dist/runtime/services.js +130 -0
- package/dist/runtime/storage.js +66 -0
- package/dist/server.js +111 -89
- package/dist/skills.js +1 -1
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +17 -47
- package/dist/types.d.ts +64 -7
- package/dist/ui.d.ts +16 -2
- package/dist/ui.js +68 -176
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +368 -182
- package/documentation/auth.md +277 -176
- package/documentation/code-mode.md +479 -329
- package/documentation/meta-tools.md +494 -417
- package/ethos.md +44 -47
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +62 -14
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +18 -21
- package/examples/worker/wrangler.jsonc +16 -4
- package/package.json +11 -4
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/dist/auth/downstream-oauth.d.ts +0 -200
- package/dist/catalog-fingerprint.d.ts +0 -19
- package/dist/catalog-limits.d.ts +0 -9
- package/dist/catalog-service.d.ts +0 -263
- package/dist/catalog.d.ts +0 -87
- package/dist/concurrency.d.ts +0 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -92
- package/dist/credential-rules.d.ts +0 -85
- package/dist/execute.d.ts +0 -154
- package/dist/executor-result.d.ts +0 -10
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -67
- package/dist/favicon.d.ts +0 -1
- package/dist/identity.d.ts +0 -4
- package/dist/invocation.d.ts +0 -74
- package/dist/mcp-result.d.ts +0 -8
- package/dist/meta-tools.d.ts +0 -129
- package/dist/operator-ui/generated.d.ts +0 -2
- package/dist/operator-ui/view.d.ts +0 -151
- package/dist/result-shapes.d.ts +0 -13
- package/dist/retired-toolkits.d.ts +0 -2
- package/dist/routes/activity.d.ts +0 -2
- package/dist/routes/credentials.d.ts +0 -2
- package/dist/routes/mcp.d.ts +0 -10
- package/dist/routes/oauth-management.d.ts +0 -2
- package/dist/routes/oauth.d.ts +0 -2
- package/dist/routes/ui.d.ts +0 -2
- package/dist/server.d.ts +0 -9
- package/dist/skills.d.ts +0 -44
- package/dist/timeout.d.ts +0 -21
- package/dist/tool-safety.d.ts +0 -9
- package/documentation/call-admission.md +0 -158
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -431
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -729
- package/documentation/request-admission.md +0 -204
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -254
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -768
- package/documentation/vercel.md +0 -241
package/dist/catalog.js
CHANGED
|
@@ -35,11 +35,29 @@ class SchemaWork {
|
|
|
35
35
|
const visit = this.visit.bind(this);
|
|
36
36
|
const text = this.text.bind(this);
|
|
37
37
|
const ancestors = [];
|
|
38
|
+
let serializedBytes = 0;
|
|
39
|
+
const addSerialized = (value) => {
|
|
40
|
+
serializedBytes += schemaEncoder.encode(value).length;
|
|
41
|
+
if (serializedBytes > this.byteLimit)
|
|
42
|
+
throw schemaSizeExceeded;
|
|
43
|
+
};
|
|
38
44
|
return this.text(JSON.stringify(value, function (key, item) {
|
|
39
45
|
visit();
|
|
40
46
|
text(key);
|
|
41
47
|
if (typeof item === "string")
|
|
42
48
|
text(item);
|
|
49
|
+
const omitted = item === undefined || typeof item === "function" || typeof item === "symbol";
|
|
50
|
+
const root = key === "" && ancestors.length === 0;
|
|
51
|
+
if (!omitted) {
|
|
52
|
+
// Array indexes are implicit in JSON. Object keys and primitive values
|
|
53
|
+
// are the useful lower bound; the final text check remains authoritative
|
|
54
|
+
// for braces, commas, and values whose encoding is larger than this bound.
|
|
55
|
+
if (!root && !Array.isArray(this))
|
|
56
|
+
addSerialized(JSON.stringify(key));
|
|
57
|
+
if (typeof item !== "object" || item === null) {
|
|
58
|
+
addSerialized(JSON.stringify(item));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
43
61
|
if (item !== null && typeof item === "object") {
|
|
44
62
|
while (ancestors.length && ancestors[ancestors.length - 1] !== this) {
|
|
45
63
|
ancestors.pop();
|
|
@@ -481,17 +499,114 @@ function renderConstraints(base, schema, byteLimit, onTruncated) {
|
|
|
481
499
|
? base
|
|
482
500
|
: `${grouped(base)} /* ${kept.join("; ")} */`;
|
|
483
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* `grouped` for the TypeScript dialect, whose JSDoc prose and string literals
|
|
504
|
+
* may carry unbalanced brackets from a downstream description: skipping both
|
|
505
|
+
* keeps a stray `)` in prose from hiding a real top-level union.
|
|
506
|
+
*/
|
|
507
|
+
function groupedType(part) {
|
|
508
|
+
let nesting = 0;
|
|
509
|
+
for (let i = 0; i < part.length; i += 1) {
|
|
510
|
+
const char = part[i];
|
|
511
|
+
if (part.startsWith("/*", i)) {
|
|
512
|
+
const end = part.indexOf("*/", i + 2);
|
|
513
|
+
if (end < 0)
|
|
514
|
+
break;
|
|
515
|
+
i = end + 1;
|
|
516
|
+
}
|
|
517
|
+
else if (char === '"') {
|
|
518
|
+
for (i += 1; i < part.length && part[i] !== '"'; i += 1) {
|
|
519
|
+
if (part[i] === "\\")
|
|
520
|
+
i += 1;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
else if (char === "{" || char === "(" || char === "[" || char === "<") {
|
|
524
|
+
nesting += 1;
|
|
525
|
+
}
|
|
526
|
+
else if (char === "}" || char === ")" || char === "]" || char === ">") {
|
|
527
|
+
nesting -= 1;
|
|
528
|
+
}
|
|
529
|
+
else if (nesting === 0 &&
|
|
530
|
+
(part.startsWith(" | ", i) || part.startsWith(" & ", i))) {
|
|
531
|
+
return `(${part})`;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return part;
|
|
535
|
+
}
|
|
536
|
+
const TYPESCRIPT_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
537
|
+
function typescriptKey(key) {
|
|
538
|
+
return TYPESCRIPT_IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
539
|
+
}
|
|
540
|
+
/** One-line JSDoc body; downstream prose can never close the comment early. */
|
|
541
|
+
function typescriptDoc(text) {
|
|
542
|
+
return text.replace(/\s+/g, " ").trim().replaceAll("*/", "*\\/");
|
|
543
|
+
}
|
|
544
|
+
function typescriptPrimitive(type) {
|
|
545
|
+
if (type === "integer")
|
|
546
|
+
return "number";
|
|
547
|
+
if (type === "array")
|
|
548
|
+
return "unknown[]";
|
|
549
|
+
if (type === "object")
|
|
550
|
+
return "Record<string, unknown>";
|
|
551
|
+
return ["string", "number", "boolean", "null"].includes(type)
|
|
552
|
+
? type
|
|
553
|
+
: "unknown";
|
|
554
|
+
}
|
|
555
|
+
function boundedParts(work, separator, prefix, suffix) {
|
|
556
|
+
// Check each addition before joining so a rejected schema never creates a
|
|
557
|
+
// large intermediate string just to discover that the final result is over.
|
|
558
|
+
const parts = [];
|
|
559
|
+
let bytes = schemaEncoder.encode(prefix).length + schemaEncoder.encode(suffix).length;
|
|
560
|
+
const separatorBytes = schemaEncoder.encode(separator).length;
|
|
561
|
+
return {
|
|
562
|
+
get length() {
|
|
563
|
+
return parts.length;
|
|
564
|
+
},
|
|
565
|
+
add(part) {
|
|
566
|
+
const partBytes = schemaEncoder.encode(part).length;
|
|
567
|
+
const added = partBytes + (parts.length > 0 ? separatorBytes : 0);
|
|
568
|
+
if (bytes + added > work.byteLimit)
|
|
569
|
+
throw schemaSizeExceeded;
|
|
570
|
+
parts.push(part);
|
|
571
|
+
bytes += added;
|
|
572
|
+
},
|
|
573
|
+
finish() {
|
|
574
|
+
return `${prefix}${parts.join(separator)}${suffix}`;
|
|
575
|
+
},
|
|
576
|
+
};
|
|
577
|
+
}
|
|
484
578
|
function renderSchema(schema, defs, seen, depth, options) {
|
|
485
579
|
options.work.visit();
|
|
486
580
|
return options.work.text(renderSchemaNode(schema, defs, seen, depth, options));
|
|
487
581
|
}
|
|
488
582
|
function renderSchemaNode(schema, defs, seen, depth, options) {
|
|
489
|
-
|
|
490
|
-
|
|
583
|
+
const ts = options.typescript === true;
|
|
584
|
+
const group = ts ? groupedType : grouped;
|
|
585
|
+
if (depth > 4) {
|
|
586
|
+
if (!ts)
|
|
587
|
+
return "…";
|
|
588
|
+
options.work.truncated = true;
|
|
589
|
+
return `unknown${COMPACT_DISCOVERY_TRUNCATION}`;
|
|
590
|
+
}
|
|
491
591
|
if (schema === null || typeof schema !== "object") {
|
|
592
|
+
if (ts)
|
|
593
|
+
return schema === false ? "never" : "unknown";
|
|
492
594
|
return options.work.json(schema);
|
|
493
595
|
}
|
|
494
596
|
const s = schema;
|
|
597
|
+
// OpenAPI 3.0's `nullable` has no JSON Schema meaning, so compact ignores
|
|
598
|
+
// it; a TypeScript reader writing `null` checks needs it in the type.
|
|
599
|
+
if (ts && s.nullable === true) {
|
|
600
|
+
const base = Object.create(null);
|
|
601
|
+
for (const key of propertyNames(s, options.work)) {
|
|
602
|
+
if (key !== "nullable")
|
|
603
|
+
base[key] = s[key];
|
|
604
|
+
}
|
|
605
|
+
const rendered = declaresShape(base)
|
|
606
|
+
? renderSchema(base, defs, seen, depth, options)
|
|
607
|
+
: "unknown";
|
|
608
|
+
return rendered === "unknown" ? rendered : `${rendered} | null`;
|
|
609
|
+
}
|
|
495
610
|
const constrain = (rendered) => options.renderConstraints
|
|
496
611
|
? renderConstraints(rendered, s, options.constraintByteLimit, options.onConstraintTruncated)
|
|
497
612
|
: rendered;
|
|
@@ -524,29 +639,41 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
|
|
|
524
639
|
if (key !== "allOf")
|
|
525
640
|
own[key] = s[key];
|
|
526
641
|
}
|
|
527
|
-
const parts =
|
|
528
|
-
|
|
529
|
-
|
|
642
|
+
const parts = boundedParts(options.work, " & ", "", "");
|
|
643
|
+
const groupParts = declaresShape(own)
|
|
644
|
+
? s.allOf.length > 0
|
|
645
|
+
: s.allOf.length > 1;
|
|
646
|
+
if (declaresShape(own)) {
|
|
647
|
+
const rendered = renderSchema(own, defs, seen, depth, options);
|
|
648
|
+
parts.add(groupParts ? group(rendered) : rendered);
|
|
649
|
+
}
|
|
530
650
|
for (const member of s.allOf) {
|
|
531
|
-
|
|
651
|
+
const rendered = renderSchema(member, defs, seen, depth + 1, options);
|
|
652
|
+
parts.add(groupParts ? group(rendered) : rendered);
|
|
532
653
|
}
|
|
533
654
|
if (parts.length === 0)
|
|
534
655
|
return "unknown";
|
|
535
|
-
|
|
536
|
-
return parts[0];
|
|
537
|
-
return parts.map(grouped).join(" & ");
|
|
656
|
+
return parts.finish();
|
|
538
657
|
}
|
|
539
658
|
const reference = s.$ref ?? s.$dynamicRef;
|
|
540
659
|
if (typeof reference === "string") {
|
|
541
660
|
const dynamic = s.$ref === undefined;
|
|
542
661
|
const rawName = refName(options.work.text(reference));
|
|
543
662
|
const name = dynamic ? rawName.replace(/^#/, "") : rawName;
|
|
544
|
-
if (seen.has(name))
|
|
545
|
-
|
|
663
|
+
if (seen.has(name)) {
|
|
664
|
+
if (!ts)
|
|
665
|
+
return name;
|
|
666
|
+
// A cycle has no finite TypeScript expansion, and a bare definition
|
|
667
|
+
// name would be an undeclared type: degrade and send callers to JSON.
|
|
668
|
+
options.work.truncated = true;
|
|
669
|
+
return "unknown /* recursive */";
|
|
670
|
+
}
|
|
546
671
|
const target = resolveDefinition(defs, name);
|
|
547
672
|
if (target === undefined) {
|
|
548
|
-
if (dynamic)
|
|
673
|
+
if (dynamic || ts)
|
|
549
674
|
options.work.truncated = true;
|
|
675
|
+
if (ts)
|
|
676
|
+
return "unknown /* unresolved */";
|
|
550
677
|
return dynamic ? "unknown" : name;
|
|
551
678
|
}
|
|
552
679
|
const cacheKey = JSON.stringify([name, depth, [...seen]]);
|
|
@@ -561,10 +688,11 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
|
|
|
561
688
|
}
|
|
562
689
|
const union = (s.oneOf ?? s.anyOf);
|
|
563
690
|
if (Array.isArray(union)) {
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
.
|
|
567
|
-
|
|
691
|
+
const parts = boundedParts(options.work, " | ", "", "");
|
|
692
|
+
for (const member of union) {
|
|
693
|
+
parts.add(renderSchema(member, defs, seen, depth + 1, options));
|
|
694
|
+
}
|
|
695
|
+
const rendered = parts.finish() || "unknown";
|
|
568
696
|
return constrain(rendered);
|
|
569
697
|
}
|
|
570
698
|
if (Array.isArray(s.enum)) {
|
|
@@ -580,21 +708,50 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
|
|
|
580
708
|
return constrain(rendered);
|
|
581
709
|
}
|
|
582
710
|
const type = s.type;
|
|
711
|
+
// A type list renders member by member, so `["object", "null"]` beside
|
|
712
|
+
// `properties` keeps both its shape and its null rather than the object alone.
|
|
713
|
+
if (ts && Array.isArray(type) && type.length > 0) {
|
|
714
|
+
const members = new Set();
|
|
715
|
+
const parts = boundedParts(options.work, " | ", "", "");
|
|
716
|
+
for (const item of type) {
|
|
717
|
+
options.work.visit();
|
|
718
|
+
if (typeof item !== "string")
|
|
719
|
+
continue;
|
|
720
|
+
let rendered = "null";
|
|
721
|
+
if (item !== "null") {
|
|
722
|
+
const member = Object.create(null);
|
|
723
|
+
for (const key of propertyNames(s, options.work)) {
|
|
724
|
+
if (key !== "type")
|
|
725
|
+
member[key] = s[key];
|
|
726
|
+
}
|
|
727
|
+
member.type = item;
|
|
728
|
+
rendered = renderSchema(member, defs, seen, depth, options);
|
|
729
|
+
}
|
|
730
|
+
if (members.has(rendered))
|
|
731
|
+
continue;
|
|
732
|
+
members.add(rendered);
|
|
733
|
+
parts.add(rendered);
|
|
734
|
+
}
|
|
735
|
+
return parts.length === 0 ? "unknown" : parts.finish();
|
|
736
|
+
}
|
|
583
737
|
if (Array.isArray(s.prefixItems)) {
|
|
584
|
-
const parts =
|
|
738
|
+
const parts = boundedParts(options.work, ", ", "[", "]");
|
|
739
|
+
for (const item of s.prefixItems) {
|
|
740
|
+
parts.add(renderSchema(item, defs, seen, depth + 1, options));
|
|
741
|
+
}
|
|
585
742
|
if (s.items !== false) {
|
|
586
743
|
const rest = s.items === undefined || s.items === true
|
|
587
744
|
? "unknown"
|
|
588
745
|
: renderSchema(s.items, defs, seen, depth + 1, options);
|
|
589
|
-
parts.
|
|
746
|
+
parts.add(`...${group(rest)}[]`);
|
|
590
747
|
}
|
|
591
|
-
return
|
|
748
|
+
return parts.finish();
|
|
592
749
|
}
|
|
593
750
|
if (type === "array" || s.items) {
|
|
594
751
|
const items = s.items
|
|
595
752
|
? renderSchema(s.items, defs, seen, depth + 1, options)
|
|
596
753
|
: "unknown";
|
|
597
|
-
return `${items}[]`;
|
|
754
|
+
return ts ? `${groupedType(items)}[]` : `${items}[]`;
|
|
598
755
|
}
|
|
599
756
|
if (type === "object" || s.properties) {
|
|
600
757
|
const props = (s.properties ?? {});
|
|
@@ -606,10 +763,17 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
|
|
|
606
763
|
...declaredKeys.filter((key) => !required.has(key)),
|
|
607
764
|
]
|
|
608
765
|
: declaredKeys;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
766
|
+
const additional = s.additionalProperties;
|
|
767
|
+
const index = ts && additional !== null && typeof additional === "object"
|
|
768
|
+
? renderSchema(additional, defs, seen, depth + 1, options)
|
|
769
|
+
: undefined;
|
|
770
|
+
if (keys.length === 0 && index === undefined) {
|
|
771
|
+
// In TypeScript `{}` reads as "empty"; an object with no declared
|
|
772
|
+
// properties is an open map unless it closes itself explicitly.
|
|
773
|
+
return ts && additional !== false ? "Record<string, unknown>" : "{}";
|
|
774
|
+
}
|
|
775
|
+
const parts = boundedParts(options.work, ts ? "; " : ", ", "{ ", " }");
|
|
776
|
+
for (const key of keys) {
|
|
613
777
|
const optional = required.has(key) ? "" : "?";
|
|
614
778
|
const rendered = renderSchema(props[key], defs, seen, depth + 1, options);
|
|
615
779
|
const description = props[key]?.description;
|
|
@@ -617,27 +781,40 @@ function renderSchemaNode(schema, defs, seen, depth, options) {
|
|
|
617
781
|
options.work.text(description);
|
|
618
782
|
}
|
|
619
783
|
options.work.text(key);
|
|
784
|
+
if (ts) {
|
|
785
|
+
const doc = options.propertyDescriptions && typeof description === "string" &&
|
|
786
|
+
description.trim()
|
|
787
|
+
? `/** ${typescriptDoc(description)} */ `
|
|
788
|
+
: "";
|
|
789
|
+
parts.add(`${doc}${typescriptKey(key)}${optional}: ${rendered}`);
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
620
792
|
const comment = options.propertyDescriptions && typeof description === "string"
|
|
621
793
|
? ` // ${description}`
|
|
622
794
|
: "";
|
|
623
|
-
|
|
624
|
-
}
|
|
625
|
-
|
|
795
|
+
parts.add(`${key}${optional}: ${rendered}${comment}`);
|
|
796
|
+
}
|
|
797
|
+
if (index !== undefined)
|
|
798
|
+
parts.add(`[key: string]: ${index}`);
|
|
799
|
+
return parts.finish();
|
|
626
800
|
}
|
|
627
801
|
if (typeof type === "string") {
|
|
628
|
-
|
|
802
|
+
const text = options.work.text(type);
|
|
803
|
+
return constrain(ts ? typescriptPrimitive(text) : text);
|
|
629
804
|
}
|
|
630
805
|
if (Array.isArray(type)) {
|
|
631
|
-
const
|
|
806
|
+
const parts = boundedParts(options.work, " | ", "", "");
|
|
807
|
+
for (const item of type) {
|
|
632
808
|
options.work.visit();
|
|
633
|
-
|
|
634
|
-
}
|
|
809
|
+
parts.add(options.work.text(String(item)));
|
|
810
|
+
}
|
|
811
|
+
const rendered = parts.finish();
|
|
635
812
|
return constrain(rendered);
|
|
636
813
|
}
|
|
637
814
|
if (options.renderConstraints && constraintEntries(s).length > 0) {
|
|
638
815
|
return renderConstraints("unknown", s, options.constraintByteLimit, options.onConstraintTruncated);
|
|
639
816
|
}
|
|
640
|
-
return options.work.json(schema);
|
|
817
|
+
return ts ? "unknown" : options.work.json(schema);
|
|
641
818
|
}
|
|
642
819
|
const compactSchemas = new WeakMap();
|
|
643
820
|
function resolveDefinition(schema, name) {
|
|
@@ -713,10 +890,58 @@ export function compactDiscoverySchema(schema) {
|
|
|
713
890
|
compactDiscoverySchemas.set(schema, result);
|
|
714
891
|
return result;
|
|
715
892
|
}
|
|
716
|
-
|
|
893
|
+
const typescriptDiscoveryTypes = new WeakMap();
|
|
894
|
+
const typescriptDescriptionTypes = new WeakMap();
|
|
895
|
+
/**
|
|
896
|
+
* One schema as a TypeScript type, under exactly the compact budgets: search
|
|
897
|
+
* drops prose and orders required keys first within 1,024 bytes, describe
|
|
898
|
+
* keeps prose as JSDoc within 8,192, and both share the 2,000-visit walk.
|
|
899
|
+
*/
|
|
900
|
+
function typescriptType(schema, description) {
|
|
901
|
+
const cache = description ? typescriptDescriptionTypes : typescriptDiscoveryTypes;
|
|
902
|
+
const cached = cache.get(schema);
|
|
903
|
+
if (cached)
|
|
904
|
+
return cached;
|
|
905
|
+
const result = boundedCompactSchema(schema, description, true);
|
|
906
|
+
cache.set(schema, result);
|
|
907
|
+
return result;
|
|
908
|
+
}
|
|
909
|
+
/** Marks runtime evidence inside the signature itself, not only beside it. */
|
|
910
|
+
const OBSERVED_OUTPUT_MARKER = "/* observed, not declared */ ";
|
|
911
|
+
/**
|
|
912
|
+
* Render a tool as the function `connecta.call(address, args)` resolves to,
|
|
913
|
+
* for an agent to read — never to execute: code mode still runs JavaScript.
|
|
914
|
+
*
|
|
915
|
+
* The input half comes from the declared input schema. The output half is the
|
|
916
|
+
* declared output schema, else a passively observed shape carrying a leading
|
|
917
|
+
* marker (runtime evidence is never presented as a contract), else `unknown`.
|
|
918
|
+
* A zero-field input renders as the optional `args?: {}` that call accepts:
|
|
919
|
+
* an MCP tool with no parameters rarely closes its schema, and reading its
|
|
920
|
+
* open `Record<string, unknown>` as "pass anything" would invite guessing.
|
|
921
|
+
*/
|
|
922
|
+
export function typescriptSignature(input, output, options) {
|
|
923
|
+
const renderedInput = typescriptType(input, options.description);
|
|
924
|
+
const renderedOutput = output
|
|
925
|
+
? typescriptType(output, options.description)
|
|
926
|
+
: undefined;
|
|
927
|
+
const args = renderedInput.text === "{}" ||
|
|
928
|
+
renderedInput.text === "Record<string, unknown>"
|
|
929
|
+
? "args?: {}"
|
|
930
|
+
: `args: ${renderedInput.text}`;
|
|
931
|
+
const result = renderedOutput
|
|
932
|
+
? `${options.observed ? OBSERVED_OUTPUT_MARKER : ""}${renderedOutput.text}`
|
|
933
|
+
: "unknown";
|
|
934
|
+
return {
|
|
935
|
+
text: `(${args}) => Promise<${result}>`,
|
|
936
|
+
inputTruncated: renderedInput.truncated,
|
|
937
|
+
outputTruncated: renderedOutput?.truncated === true,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function boundedCompactSchema(schema, description, typescript = false) {
|
|
717
941
|
const work = new SchemaWork(description ? MAX_COMPACT_DESCRIPTION_SCHEMA_BYTES : MAX_COMPACT_DISCOVERY_SCHEMA_BYTES);
|
|
718
942
|
const options = {
|
|
719
943
|
work,
|
|
944
|
+
typescript,
|
|
720
945
|
propertyDescriptions: description,
|
|
721
946
|
requiredFirst: !description,
|
|
722
947
|
enumByteLimit: description ? work.byteLimit : MAX_COMPACT_DISCOVERY_ENUM_BYTES,
|
package/dist/concurrency.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { runEdge } from "./runtime/run.js";
|
|
1
3
|
const DEFAULT_DISCOVERY_CONCURRENCY = 4;
|
|
2
4
|
export function resolveDiscoveryConcurrency(value) {
|
|
3
5
|
return value !== undefined && Number.isInteger(value) && value > 0
|
|
4
6
|
? value
|
|
5
7
|
: DEFAULT_DISCOVERY_CONCURRENCY;
|
|
6
8
|
}
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return settled;
|
|
9
|
+
/**
|
|
10
|
+
* Run `fn` over `items` with at most `limit` operations in flight, settling
|
|
11
|
+
* every one: a rejection lands in its slot instead of cancelling the rest.
|
|
12
|
+
* Results keep input order whatever order the work finishes in. A limit below
|
|
13
|
+
* one runs the items one at a time.
|
|
14
|
+
*/
|
|
15
|
+
export function mapSettledWithConcurrency(items, limit, fn) {
|
|
16
|
+
return runEdge(Effect.forEach(items, (item, index) => Effect.tryPromise({
|
|
17
|
+
// Promise.resolve keeps a non-promise return from `fn` working, as
|
|
18
|
+
// `await` did; a synchronous throw is caught either way.
|
|
19
|
+
try: () => Promise.resolve(fn(item, index)),
|
|
20
|
+
catch: (reason) => reason,
|
|
21
|
+
}).pipe(Effect.match({
|
|
22
|
+
onSuccess: (value) => ({
|
|
23
|
+
status: "fulfilled",
|
|
24
|
+
value,
|
|
25
|
+
}),
|
|
26
|
+
onFailure: (reason) => ({
|
|
27
|
+
status: "rejected",
|
|
28
|
+
reason,
|
|
29
|
+
}),
|
|
30
|
+
})), { concurrency: limit >= 1 ? Math.floor(limit) : 1 }));
|
|
30
31
|
}
|
package/dist/connector-scope.js
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const CONNECTOR_SCOPE_CLOSE_BUDGET_MS = 100;
|
|
4
|
-
/**
|
|
5
|
-
* Bound on cleanup continued after the caller-facing window expires.
|
|
6
|
-
*
|
|
7
|
-
* `remoteMcp` spends at most one second asking the downstream to terminate its
|
|
8
|
-
* session, leaving another second for the local close. Custom hooks still get a
|
|
9
|
-
* finite background window: handing a never-settling promise to a Worker's
|
|
10
|
-
* `waitUntil` would otherwise keep the invocation alive until the platform cap.
|
|
11
|
-
*/
|
|
12
|
-
const CONNECTOR_SCOPE_DEFER_BUDGET_MS = 2_000;
|
|
13
|
-
/** Resolve when `work` settles or `budgetMs` expires; never reject. */
|
|
14
|
-
function waitAtMost(work, budgetMs) {
|
|
15
|
-
return Promise.race([work, sleep(budgetMs)]).then(() => { });
|
|
16
|
-
}
|
|
1
|
+
import { closeScope } from "./runtime/connector-scope.js";
|
|
2
|
+
import { runEdge } from "./runtime/run.js";
|
|
17
3
|
/**
|
|
18
4
|
* Tell a connector that a scope owned by the core has ended.
|
|
19
5
|
*
|
|
@@ -25,25 +11,8 @@ function waitAtMost(work, budgetMs) {
|
|
|
25
11
|
* window. Callers own the at-most-once guarantee and must not use the scope
|
|
26
12
|
* again after this returns.
|
|
27
13
|
*/
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
33
|
-
// Attach both handlers before either timer can win, so a late rejection is
|
|
34
|
-
// consumed rather than becoming an unhandled rejection.
|
|
35
|
-
const settled = closing.then(() => { }, () => { });
|
|
36
|
-
if (defer) {
|
|
37
|
-
try {
|
|
38
|
-
defer(waitAtMost(settled, CONNECTOR_SCOPE_DEFER_BUDGET_MS));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// A runtime hook is best-effort too; the caller cap still applies.
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
await waitAtMost(settled, CONNECTOR_SCOPE_CLOSE_BUDGET_MS);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// The scope is over whether or not the connector managed to clean it up.
|
|
48
|
-
}
|
|
14
|
+
export function closeConnectorScope(connector, ctx, defer) {
|
|
15
|
+
// The Promise face of closeScope (src/runtime/connector-scope.ts), which
|
|
16
|
+
// owns the budgets; an Effect caller registers it as a Scope finalizer.
|
|
17
|
+
return runEdge(closeScope(connector, ctx, defer));
|
|
49
18
|
}
|
package/dist/connectors/api.d.ts
CHANGED
|
@@ -52,9 +52,9 @@ export interface ApiOptions {
|
|
|
52
52
|
* discovery metadata. See `Connector.usageGuide`.
|
|
53
53
|
*/
|
|
54
54
|
usageGuide?: string | ConnectorUsageGuide;
|
|
55
|
-
/** Optional operator-managed credential exposed through ctx.credential and
|
|
55
|
+
/** Optional operator-managed credential exposed through ctx.credential and the connection in the operator UI. */
|
|
56
56
|
credential?: ConnectorCredentialConfig;
|
|
57
|
-
/** Optional validation behind
|
|
57
|
+
/** Optional validation behind the connection's Test action in the operator UI. */
|
|
58
58
|
testCredential?: (value: string, ctx: ConnectorContext) => Promise<CredentialTestResult>;
|
|
59
59
|
/** Optional validation for named multi-field credentials. */
|
|
60
60
|
testCredentials?: (values: ConnectorCredentialValues, ctx: ConnectorContext) => Promise<CredentialTestResult>;
|
|
@@ -68,5 +68,5 @@ export interface ApiOptions {
|
|
|
68
68
|
validateArgs?: boolean;
|
|
69
69
|
tools: ApiTool[];
|
|
70
70
|
}
|
|
71
|
-
/** A static connector;
|
|
71
|
+
/** A static connector; every tool passes {@link checkToolContract} first. */
|
|
72
72
|
export declare function api(id: string, opts: ApiOptions): Connector;
|
package/dist/connectors/api.js
CHANGED
|
@@ -3,7 +3,15 @@ import { compileValidator, validateToolInput } from "../validate.js";
|
|
|
3
3
|
export function defined(value) {
|
|
4
4
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Three things a hand-written surface is refused for at construction rather
|
|
8
|
+
* than in production (#340): no description (discovery has nothing to route on
|
|
9
|
+
* and a guess costs a call), no explicit `readOnlyHint` (connecta never infers
|
|
10
|
+
* the safety class, so an unclassified tool is a deployment bug), and an
|
|
11
|
+
* `inputSchema` the validator cannot compile (declaring one is optional;
|
|
12
|
+
* declaring an unenforceable one is not). None of this reaches a proxied
|
|
13
|
+
* catalog — the contract binds the surfaces we write, not the ones we relay.
|
|
14
|
+
*/
|
|
7
15
|
function checkToolContract(id, tool) {
|
|
8
16
|
const address = `${id}.${tool.name}`;
|
|
9
17
|
if (typeof tool.description !== "string" || tool.description.trim() === "") {
|
|
@@ -20,7 +28,7 @@ function checkToolContract(id, tool) {
|
|
|
20
28
|
if (tool.inputSchema)
|
|
21
29
|
compileValidator(tool.inputSchema, { address });
|
|
22
30
|
}
|
|
23
|
-
/** A static connector;
|
|
31
|
+
/** A static connector; every tool passes {@link checkToolContract} first. */
|
|
24
32
|
export function api(id, opts) {
|
|
25
33
|
for (const t of opts.tools)
|
|
26
34
|
checkToolContract(id, t);
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The safety machinery every hand-written HTTP surface needs, extracted once
|
|
3
|
+
* (#341): path confinement, encoded query and framed bodies, per-request
|
|
4
|
+
* credential headers applied last, a refused redirect, a bounded read, and
|
|
5
|
+
* network-failure normalization. It owns no *meaning* — it never reads a status
|
|
6
|
+
* code and never invents an auth scheme, because the same 403 means an ungranted
|
|
7
|
+
* capability at one provider and a token scope at another, and those want
|
|
8
|
+
* opposite next moves. The provider's `authenticate` supplies headers and its
|
|
9
|
+
* mapper turns one `GuardedResponse` into a result or a typed failure.
|
|
10
|
+
*
|
|
11
|
+
* Not exported this release: the migrations proved the shape preserves behavior
|
|
12
|
+
* for connectors that already had this machinery, not that it is the right
|
|
13
|
+
* public surface. Web APIs only.
|
|
14
|
+
*/
|
|
2
15
|
import { ConnectorCallError, unavailableCallError } from "../errors.js";
|
|
3
16
|
/** Parse delta-seconds or an HTTP-date into a non-negative wait window. */
|
|
4
17
|
export function retryAfterMs(headers) {
|
|
@@ -123,7 +136,18 @@ async function drain(provider, stream, limit) {
|
|
|
123
136
|
}
|
|
124
137
|
return body;
|
|
125
138
|
}
|
|
126
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* Every read is capped at `maxResponseBytes`: a streaming body is abandoned at
|
|
141
|
+
* the ceiling rather than buffered past it, and where a runtime gives no body
|
|
142
|
+
* stream the text is measured in UTF-8 bytes before it is accepted, with JSON
|
|
143
|
+
* parsed from that same bounded text. Such a runtime still buffers internally,
|
|
144
|
+
* but cannot return an oversized body as a successful result.
|
|
145
|
+
*
|
|
146
|
+
* A mapper must re-throw `ConnectorCallError` and swallow only what it
|
|
147
|
+
* recognizes: a bare `catch` around `response.json()` eats this refusal along
|
|
148
|
+
* with a parse error and turns a response nobody was allowed to read into an
|
|
149
|
+
* empty success.
|
|
150
|
+
*/
|
|
127
151
|
function boundedResponse(provider, response, limit) {
|
|
128
152
|
if (BODILESS_STATUSES.has(response.status)) {
|
|
129
153
|
const emptyJson = async () => undefined;
|
|
@@ -186,7 +210,11 @@ async function jsonResult(read) {
|
|
|
186
210
|
return { parseError: cause };
|
|
187
211
|
}
|
|
188
212
|
}
|
|
189
|
-
/**
|
|
213
|
+
/**
|
|
214
|
+
* Build the transport a connector sends every request through. `maxResponseBytes`
|
|
215
|
+
* is required rather than defaulted: what counts as an absurd response is a fact
|
|
216
|
+
* about the API, not about HTTP.
|
|
217
|
+
*/
|
|
190
218
|
export function guardedFetch(options) {
|
|
191
219
|
const { provider, maxResponseBytes: limit } = options;
|
|
192
220
|
if (!Number.isInteger(limit) || limit < 1) {
|
|
@@ -229,7 +257,9 @@ export function guardedFetch(options) {
|
|
|
229
257
|
: request.rawBody !== undefined
|
|
230
258
|
? { body: request.rawBody }
|
|
231
259
|
: {}),
|
|
232
|
-
//
|
|
260
|
+
// A redirect is an instruction to re-send the credential to whatever
|
|
261
|
+
// origin `Location` names, and a confinement a redirect can undo was
|
|
262
|
+
// never one. Refused below rather than followed.
|
|
233
263
|
redirect: "manual",
|
|
234
264
|
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
235
265
|
});
|
|
@@ -2,8 +2,8 @@ import type { FetchLike, Transport } from "@modelcontextprotocol/client";
|
|
|
2
2
|
import { ConnectorCallError } from "../errors.js";
|
|
3
3
|
import type { Connector, ConnectorCallAdmissionPolicy, ConnectorContext, ConnectorCredentialConfig, ConnectorUsageGuide, Logger } from "../types.js";
|
|
4
4
|
/**
|
|
5
|
-
* A static downstream credential the
|
|
6
|
-
* rather than
|
|
5
|
+
* A static downstream credential supplied through the connection in the
|
|
6
|
+
* operator UI rather than baked into deployment source.
|
|
7
7
|
*
|
|
8
8
|
* The connector, its endpoint, and the credential *slot* stay declared in
|
|
9
9
|
* code; only the secret arrives through the operator route, exactly as for
|
|
@@ -15,8 +15,8 @@ import type { Connector, ConnectorCallAdmissionPolicy, ConnectorContext, Connect
|
|
|
15
15
|
interface RemoteMcpCredentialAuth {
|
|
16
16
|
type: "credential";
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
* `{ label: "API key" }`; a maintained provider passes the name the provider
|
|
18
|
+
* Slot description rendered in the connection in the operator UI. Defaults
|
|
19
|
+
* to `{ label: "API key" }`; a maintained provider passes the name the provider
|
|
20
20
|
* itself uses. Named `fields` are refused — this shape reads the reserved
|
|
21
21
|
* `value` field only.
|
|
22
22
|
*/
|
|
@@ -122,7 +122,8 @@ export declare class RemoteMcpRedirectError extends ConnectorCallError {
|
|
|
122
122
|
* Wrap fetch with explicit, bounded redirect handling.
|
|
123
123
|
*
|
|
124
124
|
* The starting URL of each fetch call is trusted by its caller (the configured
|
|
125
|
-
* MCP endpoint, or an OAuth URL
|
|
125
|
+
* MCP endpoint, or an OAuth URL the pinned SDK discovered and
|
|
126
|
+
* `learnedUrlSafeFetch` already admitted). Only Location
|
|
126
127
|
* values are policy-controlled here. No rejected target is ever fetched, so
|
|
127
128
|
* arbitrary static header names receive the same protection as Authorization.
|
|
128
129
|
*/
|