@alpacakit/channels 0.1.0-beta.36 → 0.1.0-beta.39
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/README.md +204 -723
- package/dist/binding-compiler.d.ts +12 -2
- package/dist/binding-compiler.d.ts.map +1 -1
- package/dist/binding-compiler.js +25 -0
- package/dist/cli-coercion.d.ts +1 -1
- package/dist/cli-coercion.d.ts.map +1 -1
- package/dist/cli-coercion.js +14 -29
- package/dist/cli-compiler.d.ts +4 -17
- package/dist/cli-compiler.d.ts.map +1 -1
- package/dist/cli-compiler.js +78 -39
- package/dist/cli-help-projection.d.ts.map +1 -1
- package/dist/cli-help-projection.js +41 -36
- package/dist/cli-model.d.ts +50 -65
- package/dist/cli-model.d.ts.map +1 -1
- package/dist/cli-model.js +26 -34
- package/dist/cli-tree.d.ts +6 -3
- package/dist/cli-tree.d.ts.map +1 -1
- package/dist/cli-tree.js +161 -387
- package/dist/cli.d.ts +1 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -5
- package/dist/codecs.d.ts +20 -18
- package/dist/codecs.d.ts.map +1 -1
- package/dist/codecs.js +21 -31
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +8 -3
- package/dist/definition-error.d.ts +5 -0
- package/dist/definition-error.d.ts.map +1 -0
- package/dist/definition-error.js +8 -0
- package/dist/factory.d.ts +25 -11
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +28 -18
- package/dist/http-compiler.d.ts +15 -0
- package/dist/http-compiler.d.ts.map +1 -0
- package/dist/http-compiler.js +37 -0
- package/dist/http-model.d.ts +42 -0
- package/dist/http-model.d.ts.map +1 -0
- package/dist/http-model.js +20 -0
- package/dist/http-tree.d.ts +7 -0
- package/dist/http-tree.d.ts.map +1 -0
- package/dist/http-tree.js +181 -0
- package/dist/http.d.ts +3 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/input-compiler.d.ts +13 -0
- package/dist/input-compiler.d.ts.map +1 -0
- package/dist/input-compiler.js +122 -0
- package/dist/introspection.d.ts +1 -1
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +30 -16
- package/dist/mcp.d.ts +25 -24
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +87 -65
- package/dist/model.d.ts +44 -42
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +44 -9
- package/dist/owned.d.ts +12 -0
- package/dist/owned.d.ts.map +1 -0
- package/dist/owned.js +61 -0
- package/dist/registration.d.ts +41 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +14 -0
- package/dist/runtime.d.ts +2 -35
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -127
- package/dist/schema.d.ts +11 -8
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +110 -47
- package/dist/string-coercion.d.ts +4 -0
- package/dist/string-coercion.d.ts.map +1 -0
- package/dist/string-coercion.js +24 -0
- package/dist/tree-internal.d.ts +4 -2
- package/dist/tree-internal.d.ts.map +1 -1
- package/dist/tree-internal.js +5 -2
- package/dist/values.d.ts +1 -6
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +1 -6
- package/examples/README.md +28 -0
- package/examples/book-search.ts +151 -0
- package/examples/greeting.ts +193 -0
- package/package.json +9 -4
- package/dist/cli-application.d.ts +0 -40
- package/dist/cli-application.d.ts.map +0 -1
- package/dist/cli-application.js +0 -90
- package/dist/cli-executor.d.ts +0 -25
- package/dist/cli-executor.d.ts.map +0 -1
- package/dist/cli-executor.js +0 -44
- package/dist/cli-presentation.d.ts +0 -32
- package/dist/cli-presentation.d.ts.map +0 -1
- package/dist/cli-presentation.js +0 -293
- package/dist/cli-result-envelope.d.ts +0 -104
- package/dist/cli-result-envelope.d.ts.map +0 -1
- package/dist/cli-result-envelope.js +0 -137
- package/dist/cli-schema.d.ts +0 -4
- package/dist/cli-schema.d.ts.map +0 -1
- package/dist/cli-schema.js +0 -7
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./codecs.js";
|
|
2
|
-
export
|
|
2
|
+
export { createOutputContract, EntryOutputContractError, requireEntryOutput, verifyEntryOutput, } from "./contract.js";
|
|
3
3
|
export * from "./factory.js";
|
|
4
4
|
export * from "./introspection.js";
|
|
5
5
|
export * from "./model.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CompiledParameterBinding } from "./binding-compiler.js";
|
|
2
|
+
import type { ErasedEntryDefinition, ErasedParameterDefinition, ParameterValues, RawValueMap } from "./model.js";
|
|
3
|
+
import type { InputPlan } from "./registration.js";
|
|
4
|
+
export type InputCoercer = (parameter: ErasedParameterDefinition, raw: unknown) => unknown;
|
|
5
|
+
export type CompiledInputPlan<Values extends RawValueMap = RawValueMap, Input = unknown> = InputPlan<Values, Input> & {
|
|
6
|
+
readonly parameterNames: readonly string[];
|
|
7
|
+
readonly parseCoerced: (raw: RawValueMap, coerce: InputCoercer) => Input;
|
|
8
|
+
};
|
|
9
|
+
export declare function compileInputPlan<const Entry extends ErasedEntryDefinition, Values extends RawValueMap = ParameterValues<Entry["parameters"]>>(input: {
|
|
10
|
+
readonly entry: Entry;
|
|
11
|
+
readonly bindings: readonly CompiledParameterBinding[];
|
|
12
|
+
}): CompiledInputPlan<Values, ReturnType<Entry["project"]>>;
|
|
13
|
+
//# sourceMappingURL=input-compiler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-compiler.d.ts","sourceRoot":"","sources":["../src/input-compiler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,KAAK,EACV,qBAAqB,EACrB,yBAAyB,EACzB,eAAe,EACf,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAOnD,MAAM,MAAM,YAAY,GAAG,CACzB,SAAS,EAAE,yBAAyB,EACpC,GAAG,EAAE,OAAO,KACT,OAAO,CAAC;AAEb,MAAM,MAAM,iBAAiB,CAC3B,MAAM,SAAS,WAAW,GAAG,WAAW,EACxC,KAAK,GAAG,OAAO,IACb,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG;IAC7B,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,KAAK,KAAK,CAAC;CAC1E,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,KAAK,CAAC,KAAK,SAAS,qBAAqB,EACzC,MAAM,SAAS,WAAW,GAAG,eAAe,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EACjE,KAAK,EAAE;IACP,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACxD,GAAG,iBAAiB,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CA4B1D"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { EntryTreeDefinitionError } from "./definition-error.js";
|
|
3
|
+
import { copyJsonValue, ownedRecord } from "./owned.js";
|
|
4
|
+
import { EntryInputError } from "./runtime.js";
|
|
5
|
+
import { ENTRY_INPUT_TARGET_KIND, PARAMETER_REQUIREMENT_KIND, } from "./values.js";
|
|
6
|
+
export function compileInputPlan(input) {
|
|
7
|
+
validateDefaults(input.entry);
|
|
8
|
+
const exposedNames = input.bindings.map((binding) => binding.parameter.name);
|
|
9
|
+
const exposed = new Set(exposedNames);
|
|
10
|
+
const schema = buildInputSchema(input.bindings);
|
|
11
|
+
const project = (values) => projectEntry(input.entry, exposed, values);
|
|
12
|
+
const parseValues = (raw, coerce) => {
|
|
13
|
+
rejectUnknownKeys(raw, exposed);
|
|
14
|
+
const candidate = coerce
|
|
15
|
+
? coerceRawValues(raw, input.bindings, coerce)
|
|
16
|
+
: raw;
|
|
17
|
+
const parsed = schema.safeParse(candidate);
|
|
18
|
+
if (!parsed.success) {
|
|
19
|
+
throw entryInputError(parsed.error);
|
|
20
|
+
}
|
|
21
|
+
return project(parsed.data);
|
|
22
|
+
};
|
|
23
|
+
return ownedRecord({
|
|
24
|
+
schema: schema,
|
|
25
|
+
parse: (raw) => parseValues(raw),
|
|
26
|
+
parseCoerced: (raw, coerce) => parseValues(raw, coerce),
|
|
27
|
+
project,
|
|
28
|
+
parameterNames: Object.freeze(exposedNames),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function buildInputSchema(bindings) {
|
|
32
|
+
const shape = Object.create(null);
|
|
33
|
+
for (const { parameter } of bindings) {
|
|
34
|
+
let schema = parameter.codec.schema;
|
|
35
|
+
switch (parameter.requirement.kind) {
|
|
36
|
+
case PARAMETER_REQUIREMENT_KIND.required:
|
|
37
|
+
break;
|
|
38
|
+
case PARAMETER_REQUIREMENT_KIND.optional:
|
|
39
|
+
schema = schema.optional();
|
|
40
|
+
break;
|
|
41
|
+
case PARAMETER_REQUIREMENT_KIND.optionalWithDefault: {
|
|
42
|
+
const authoredDefault = parameter.requirement.defaultValue;
|
|
43
|
+
schema = schema.prefault(() => copyJsonValue(authoredDefault));
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
schema = schema.describe(parameter.description);
|
|
48
|
+
Reflect.set(shape, parameter.name, schema);
|
|
49
|
+
}
|
|
50
|
+
return z.object(shape).strict();
|
|
51
|
+
}
|
|
52
|
+
function validateDefaults(entry) {
|
|
53
|
+
const issues = [];
|
|
54
|
+
for (const parameter of entry.parameters) {
|
|
55
|
+
if (parameter.requirement.kind !==
|
|
56
|
+
PARAMETER_REQUIREMENT_KIND.optionalWithDefault) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const parsed = parameter.codec.schema.safeParse(copyJsonValue(parameter.requirement.defaultValue));
|
|
60
|
+
if (!parsed.success) {
|
|
61
|
+
issues.push(`${entry.name}#${parameter.name}: invalid authored default: ${formatZodIssues(parsed.error)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (issues.length > 0) {
|
|
65
|
+
throw new EntryTreeDefinitionError(issues);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function projectEntry(entry, exposed, publicValues) {
|
|
69
|
+
const values = Object.create(null);
|
|
70
|
+
for (const parameter of entry.parameters) {
|
|
71
|
+
if (exposed.has(parameter.name)) {
|
|
72
|
+
if (Object.hasOwn(publicValues, parameter.name)) {
|
|
73
|
+
Reflect.set(values, parameter.name, Reflect.get(publicValues, parameter.name));
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (parameter.requirement.kind ===
|
|
78
|
+
PARAMETER_REQUIREMENT_KIND.optionalWithDefault) {
|
|
79
|
+
const parsed = parameter.codec.schema.safeParse(copyJsonValue(parameter.requirement.defaultValue));
|
|
80
|
+
if (!parsed.success) {
|
|
81
|
+
throw new Error(`Compiled default became invalid for ${entry.name}#${parameter.name}`);
|
|
82
|
+
}
|
|
83
|
+
Reflect.set(values, parameter.name, parsed.data);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const project = entry.project;
|
|
87
|
+
return project({ values: values });
|
|
88
|
+
}
|
|
89
|
+
function rejectUnknownKeys(raw, exposed) {
|
|
90
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
91
|
+
throw new EntryInputError(["Entry input must be an object"]);
|
|
92
|
+
}
|
|
93
|
+
const unknown = Object.keys(raw).filter((key) => !exposed.has(key));
|
|
94
|
+
if (unknown.length > 0) {
|
|
95
|
+
throw new EntryInputError(unknown.map((key) => `Unknown parameter ${key}`));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function coerceRawValues(raw, bindings, coerce) {
|
|
99
|
+
const output = Object.create(null);
|
|
100
|
+
for (const binding of bindings) {
|
|
101
|
+
const name = binding.parameter.name;
|
|
102
|
+
if (!Object.hasOwn(raw, name)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
Reflect.set(output, name, coerce(binding.parameter, Reflect.get(raw, name)));
|
|
106
|
+
}
|
|
107
|
+
return output;
|
|
108
|
+
}
|
|
109
|
+
function entryInputError(error) {
|
|
110
|
+
const first = error.issues[0];
|
|
111
|
+
const firstPath = first?.path[0];
|
|
112
|
+
const target = typeof firstPath === "string"
|
|
113
|
+
? {
|
|
114
|
+
kind: ENTRY_INPUT_TARGET_KIND.parameter,
|
|
115
|
+
parameterName: firstPath,
|
|
116
|
+
}
|
|
117
|
+
: { kind: ENTRY_INPUT_TARGET_KIND.entry };
|
|
118
|
+
return new EntryInputError(error.issues.map((issue) => issue.message), target);
|
|
119
|
+
}
|
|
120
|
+
function formatZodIssues(error) {
|
|
121
|
+
return error.issues.map((issue) => issue.message).join("; ");
|
|
122
|
+
}
|
package/dist/introspection.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JsonValue } from "@alpacakit/core";
|
|
2
2
|
import type { CommandDocumentation, EntryTreeDefinition, ErasedEntryDefinition, ValueShape } from "./model.js";
|
|
3
|
-
import { ENTRY_OUTPUT_DESCRIPTION_KIND,
|
|
3
|
+
import { ENTRY_OUTPUT_DESCRIPTION_KIND, PARAMETER_REQUIREMENT_KIND } from "./values.js";
|
|
4
4
|
export type CommandPath = readonly string[];
|
|
5
5
|
export type ParameterRequirementDescription = {
|
|
6
6
|
readonly kind: typeof PARAMETER_REQUIREMENT_KIND.required;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../src/introspection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EAEnB,qBAAqB,EACrB,UAAU,EACX,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../src/introspection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EAEnB,qBAAqB,EACrB,UAAU,EACX,MAAM,YAAY,CAAC;AAYpB,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AAOrB,MAAM,MAAM,WAAW,GAAG,SAAS,MAAM,EAAE,CAAC;AAE5C,MAAM,MAAM,+BAA+B,GACvC;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,QAAQ,CAAA;CAAE,GAC7D;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,QAAQ,CAAC;CAC3D,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,0BAA0B,CAAC,mBAAmB,CAAC;IACrE,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC;CAClC,CAAC;AAEN,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,+BAA+B,CAAC;IACtD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,6BAA6B,CAAC,KAAK,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,6BAA6B,CAAC,IAAI,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAClD,CAAC;AAOF,wBAAgB,aAAa,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,CAoB5E;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,mBAAmB,GACxB,kBAAkB,CA4CpB;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,GACtE,+BAA+B,CAWjC"}
|
package/dist/introspection.js
CHANGED
|
@@ -1,49 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ownedArray, ownedRecord, snapshotJsonValue, snapshotValueShape, } from "./owned.js";
|
|
2
|
+
import { EntryTreeDefinitionError, validateEntryTreeStructure, visitEntryTree, } from "./schema.js";
|
|
3
|
+
import { ENTRY_OUTPUT_DESCRIPTION_KIND, PARAMETER_REQUIREMENT_KIND, } from "./values.js";
|
|
3
4
|
const ENTRY_INTROSPECTION_ERROR = {
|
|
4
5
|
missingChild: "Entry tree child could not be described",
|
|
5
6
|
missingRoot: "Entry tree root could not be described",
|
|
6
7
|
};
|
|
7
8
|
export function describeEntry(entry) {
|
|
8
|
-
return {
|
|
9
|
+
return ownedRecord({
|
|
9
10
|
name: entry.name,
|
|
10
|
-
parameters: entry.parameters.map((parameter) => ({
|
|
11
|
+
parameters: ownedArray(entry.parameters.map((parameter) => ownedRecord({
|
|
11
12
|
name: parameter.name,
|
|
12
13
|
description: parameter.description,
|
|
13
14
|
requirement: describeParameterRequirement(parameter.requirement),
|
|
14
|
-
shape: parameter.codec.shape,
|
|
15
|
-
})),
|
|
16
|
-
output: {
|
|
15
|
+
shape: snapshotValueShape(parameter.codec.shape),
|
|
16
|
+
}))),
|
|
17
|
+
output: ownedRecord({
|
|
17
18
|
kind: entry.output === null
|
|
18
19
|
? ENTRY_OUTPUT_DESCRIPTION_KIND.none
|
|
19
20
|
: ENTRY_OUTPUT_DESCRIPTION_KIND.typed,
|
|
20
|
-
},
|
|
21
|
-
};
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
22
23
|
}
|
|
23
24
|
export function describeEntryTree(tree) {
|
|
25
|
+
const issues = validateEntryTreeStructure(tree);
|
|
26
|
+
if (issues.length > 0) {
|
|
27
|
+
throw new EntryTreeDefinitionError(issues);
|
|
28
|
+
}
|
|
24
29
|
const frames = new Map();
|
|
25
30
|
const entries = [];
|
|
26
31
|
visitEntryTree(tree, {
|
|
27
32
|
visitNode(node, path) {
|
|
28
|
-
entries.push({ node, path: path.slice(1) });
|
|
33
|
+
entries.push({ node, path: ownedArray(path.slice(1)) });
|
|
29
34
|
},
|
|
30
35
|
visitEndpoint() { },
|
|
31
36
|
});
|
|
32
37
|
for (const current of entries.reverse()) {
|
|
33
|
-
frames.set(current.node, {
|
|
38
|
+
frames.set(current.node, ownedRecord({
|
|
34
39
|
path: current.path,
|
|
35
40
|
name: current.node.name,
|
|
36
41
|
description: current.node.description,
|
|
37
42
|
documentation: current.node.documentation,
|
|
38
|
-
endpoints: current.node.endpoints.map((endpoint) => describeEntry(endpoint.entry)),
|
|
39
|
-
children: current.node.children.map((child) => {
|
|
43
|
+
endpoints: ownedArray(current.node.endpoints.map((endpoint) => describeEntry(endpoint.entry))),
|
|
44
|
+
children: ownedArray(current.node.children.map((child) => {
|
|
40
45
|
const description = frames.get(child);
|
|
41
46
|
if (description === undefined) {
|
|
42
47
|
throw new Error(ENTRY_INTROSPECTION_ERROR.missingChild);
|
|
43
48
|
}
|
|
44
49
|
return description;
|
|
45
|
-
}),
|
|
46
|
-
});
|
|
50
|
+
})),
|
|
51
|
+
}));
|
|
47
52
|
}
|
|
48
53
|
const root = frames.get(tree.root);
|
|
49
54
|
if (root === undefined) {
|
|
@@ -52,5 +57,14 @@ export function describeEntryTree(tree) {
|
|
|
52
57
|
return root;
|
|
53
58
|
}
|
|
54
59
|
export function describeParameterRequirement(requirement) {
|
|
55
|
-
|
|
60
|
+
switch (requirement.kind) {
|
|
61
|
+
case PARAMETER_REQUIREMENT_KIND.required:
|
|
62
|
+
case PARAMETER_REQUIREMENT_KIND.optional:
|
|
63
|
+
return ownedRecord({ kind: requirement.kind });
|
|
64
|
+
case PARAMETER_REQUIREMENT_KIND.optionalWithDefault:
|
|
65
|
+
return ownedRecord({
|
|
66
|
+
kind: requirement.kind,
|
|
67
|
+
defaultValue: snapshotJsonValue(requirement.defaultValue),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
56
70
|
}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import { type
|
|
5
|
-
import { CHANNEL
|
|
6
|
-
export type McpBinding<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import { type ParameterDescription } from "./introspection.js";
|
|
2
|
+
import type { EntryChannelBinding, RawValueMap } from "./model.js";
|
|
3
|
+
import type { InputPlan, Registration } from "./registration.js";
|
|
4
|
+
import { type ContextFor, type EndpointForChannel, type EndpointsOfSource, type EntryTreeSource, type OutputFor } from "./registration.js";
|
|
5
|
+
import { CHANNEL } from "./values.js";
|
|
6
|
+
export type McpBinding<Name extends string = string, Excluded extends readonly string[] = readonly string[]> = {
|
|
7
|
+
readonly name: Name;
|
|
8
|
+
readonly exclude: Excluded;
|
|
9
|
+
};
|
|
10
|
+
export declare function bindMcpEntry<const Name extends string, const Excluded extends readonly string[] = readonly []>(input: {
|
|
11
|
+
readonly name: Name;
|
|
12
|
+
readonly exclude?: Excluded;
|
|
13
|
+
}): EntryChannelBinding<typeof CHANNEL.mcp, McpBinding<Name, Excluded>>;
|
|
14
|
+
export type McpRegistration<Values extends RawValueMap, Input, Context, Output> = Omit<Registration<Values, Input, Context, Output>, "binding"> & {
|
|
15
|
+
readonly binding: EntryChannelBinding<typeof CHANNEL.mcp, McpBinding>;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly parameters: readonly ParameterDescription[];
|
|
18
|
+
readonly input: InputPlan<Values, Input>;
|
|
19
|
+
};
|
|
20
|
+
export interface McpToolCollection<Context, Output> {
|
|
21
|
+
readonly length: number;
|
|
22
|
+
readonly forEach: (visit: <Values extends RawValueMap, Input>(item: McpRegistration<Values, Input, Context, Output>) => void) => void;
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
export declare function
|
|
25
|
-
export
|
|
24
|
+
type McpEndpointFor<Source extends EntryTreeSource> = EndpointForChannel<EndpointsOfSource<Source>, typeof CHANNEL.mcp>;
|
|
25
|
+
export declare function collectMcpTools<const Source extends EntryTreeSource>(source: Source): McpToolCollection<ContextFor<McpEndpointFor<Source>>, OutputFor<McpEndpointFor<Source>>>;
|
|
26
|
+
export {};
|
|
26
27
|
//# sourceMappingURL=mcp.d.ts.map
|
package/dist/mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAMA,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,KAAK,EACV,mBAAmB,EAEnB,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,SAAS,EAEf,MAAM,mBAAmB,CAAC;AAa3B,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,MAAM,UAAU,CACpB,IAAI,SAAS,MAAM,GAAG,MAAM,EAC5B,QAAQ,SAAS,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,IACpD;IACF,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;CAC5B,CAAC;AAEF,wBAAgB,YAAY,CAC1B,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,KAAK,CAAC,QAAQ,SAAS,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,EACtD,KAAK,EAAE;IACP,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;CAC7B,GAAG,mBAAmB,CAAC,OAAO,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAOtE;AAED,MAAM,MAAM,eAAe,CACzB,MAAM,SAAS,WAAW,EAC1B,KAAK,EACL,OAAO,EACP,MAAM,IACJ,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG;IAClE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,OAAO,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACrD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;CAC1C,CAAC;AAEF,MAAM,WAAW,iBAAiB,CAAC,OAAO,EAAE,MAAM;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,CAChB,KAAK,EAAE,CAAC,MAAM,SAAS,WAAW,EAAE,KAAK,EACvC,IAAI,EAAE,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,KAClD,IAAI,KACN,IAAI,CAAC;CACX;AAED,KAAK,cAAc,CAAC,MAAM,SAAS,eAAe,IAAI,kBAAkB,CACtE,iBAAiB,CAAC,MAAM,CAAC,EACzB,OAAO,OAAO,CAAC,GAAG,CACnB,CAAC;AAOF,wBAAgB,eAAe,CAAC,KAAK,CAAC,MAAM,SAAS,eAAe,EAClE,MAAM,EAAE,MAAM,GACb,iBAAiB,CAClB,UAAU,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,EAClC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAClC,CA0CA"}
|
package/dist/mcp.js
CHANGED
|
@@ -1,67 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { compileParameterExclusions, PARAMETER_BINDING_ISSUE_KIND, } from "./binding-compiler.js";
|
|
2
|
+
import { compileInputPlan } from "./input-compiler.js";
|
|
3
|
+
import { describeEntry } from "./introspection.js";
|
|
4
|
+
import { ownedArray, ownedRecord } from "./owned.js";
|
|
5
|
+
import { packRegistrationCollection, } from "./registration.js";
|
|
6
|
+
import { EntryTreeDefinitionError, findChannelBinding, validateEntryTreeStructure, visitEntryTreeSource, } from "./schema.js";
|
|
5
7
|
import { createEntryChannelBinding, isChannelPayload, PAYLOAD_KIND, registerChannelPayload, } from "./tree-internal.js";
|
|
6
|
-
import { CHANNEL
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
registerChannelPayload(PAYLOAD_KIND.mcpBinding,
|
|
13
|
-
return createEntryChannelBinding(CHANNEL.mcp,
|
|
8
|
+
import { CHANNEL } from "./values.js";
|
|
9
|
+
export function bindMcpEntry(input) {
|
|
10
|
+
const payload = ownedRecord({
|
|
11
|
+
name: input.name,
|
|
12
|
+
exclude: ownedArray(input.exclude ?? []),
|
|
13
|
+
});
|
|
14
|
+
registerChannelPayload(PAYLOAD_KIND.mcpBinding, payload);
|
|
15
|
+
return createEntryChannelBinding(CHANNEL.mcp, payload);
|
|
14
16
|
}
|
|
15
|
-
export function
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
export function collectMcpTools(source) {
|
|
18
|
+
const structuralIssues = validateEntryTreeStructure(source);
|
|
19
|
+
if (structuralIssues.length > 0) {
|
|
20
|
+
throw new EntryTreeDefinitionError(structuralIssues);
|
|
21
|
+
}
|
|
22
|
+
const issues = [];
|
|
23
|
+
const registrations = [];
|
|
24
|
+
const seenEndpoints = new Set();
|
|
25
|
+
visitEntryTreeSource(source, {
|
|
26
|
+
visitNode() { },
|
|
27
|
+
visitEndpoint(endpoint) {
|
|
28
|
+
if (seenEndpoints.has(endpoint)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const channelBinding = findChannelBinding(endpoint, CHANNEL.mcp);
|
|
32
|
+
if (channelBinding === null) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
seenEndpoints.add(endpoint);
|
|
36
|
+
if (!isMcpChannelBinding(channelBinding)) {
|
|
37
|
+
issues.push(`${endpoint.entry.name}: invalid MCP binding`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const registration = compileMcpRegistration(endpoint, channelBinding, channelBinding.binding, issues);
|
|
41
|
+
registrations.push(registration);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
validateMcpNames(registrations, issues);
|
|
45
|
+
if (issues.length > 0) {
|
|
46
|
+
throw new EntryTreeDefinitionError(issues);
|
|
40
47
|
}
|
|
41
|
-
return
|
|
48
|
+
return packRegistrationCollection(registrations);
|
|
42
49
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (channelBindings.length !== 1 ||
|
|
47
|
-
!channelBinding ||
|
|
48
|
-
!isMcpBinding(channelBinding.binding)) {
|
|
49
|
-
throw new EntryTreeDefinitionError([
|
|
50
|
-
`${endpoint.entry.name}: endpoint must have exactly one valid MCP binding`,
|
|
51
|
-
]);
|
|
50
|
+
function compileMcpRegistration(endpoint, channelBinding, binding, issues) {
|
|
51
|
+
if (binding.name.trim().length === 0) {
|
|
52
|
+
issues.push(`${endpoint.entry.name}: MCP name must not be blank`);
|
|
52
53
|
}
|
|
53
|
-
const compilation =
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const compilation = compileParameterExclusions(endpoint.entry, binding.exclude);
|
|
55
|
+
issues.push(...compilation.issues.map((issue) => formatMcpParameterBindingIssue(endpoint.entry.name, issue)));
|
|
56
|
+
const input = compileInputPlan({
|
|
57
|
+
entry: endpoint.entry,
|
|
58
|
+
bindings: compilation.bindings,
|
|
59
|
+
});
|
|
60
|
+
const exposed = new Set(input.parameterNames);
|
|
61
|
+
return ownedRecord({
|
|
62
|
+
endpoint,
|
|
63
|
+
entry: endpoint.entry,
|
|
64
|
+
binding: channelBinding,
|
|
65
|
+
name: binding.name,
|
|
66
|
+
parameters: ownedArray(describeEntry(endpoint.entry).parameters.filter((parameter) => exposed.has(parameter.name))),
|
|
67
|
+
input: ownedRecord({
|
|
68
|
+
schema: input.schema,
|
|
69
|
+
parse: input.parse,
|
|
70
|
+
project: input.project,
|
|
71
|
+
}),
|
|
72
|
+
handler: endpoint.handler,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function validateMcpNames(registrations, issues) {
|
|
76
|
+
const names = new Map();
|
|
77
|
+
for (const registration of registrations) {
|
|
78
|
+
const existing = names.get(registration.name);
|
|
79
|
+
if (existing !== undefined && existing.endpoint !== registration.endpoint) {
|
|
80
|
+
issues.push(`MCP name ${JSON.stringify(registration.name)} is owned by different endpoints`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
names.set(registration.name, registration);
|
|
84
|
+
}
|
|
56
85
|
}
|
|
57
|
-
return compilation.bindings;
|
|
58
86
|
}
|
|
59
87
|
function formatMcpParameterBindingIssue(entryName, issue) {
|
|
60
88
|
switch (issue.kind) {
|
|
61
89
|
case PARAMETER_BINDING_ISSUE_KIND.duplicate:
|
|
62
|
-
return `${entryName}: duplicate MCP
|
|
90
|
+
return `${entryName}: duplicate exposed MCP parameter ${issue.parameterName}`;
|
|
91
|
+
case PARAMETER_BINDING_ISSUE_KIND.duplicateExclusion:
|
|
92
|
+
return `${entryName}: duplicate MCP exclusion for parameter ${issue.parameterName}`;
|
|
93
|
+
case PARAMETER_BINDING_ISSUE_KIND.nonexistentExclusion:
|
|
94
|
+
return `${entryName}: MCP excludes nonexistent parameter ${issue.parameterName}`;
|
|
63
95
|
case PARAMETER_BINDING_ISSUE_KIND.nonexistent:
|
|
64
|
-
return `${entryName}: MCP
|
|
96
|
+
return `${entryName}: MCP exposes nonexistent parameter ${issue.parameterName}`;
|
|
65
97
|
case PARAMETER_BINDING_ISSUE_KIND.requiredUnreachable:
|
|
66
98
|
return `${entryName}: required parameter ${issue.parameterName} is unreachable from MCP`;
|
|
67
99
|
}
|
|
@@ -69,16 +101,6 @@ function formatMcpParameterBindingIssue(entryName, issue) {
|
|
|
69
101
|
function isMcpBinding(value) {
|
|
70
102
|
return isChannelPayload(PAYLOAD_KIND.mcpBinding, value);
|
|
71
103
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const schema = requireEntryOutput(endpoint.entry).schema;
|
|
75
|
-
const normalized = schema.type === ZOD_SCHEMA_TYPE.readonly ? unwrapZodSchema(schema) : schema;
|
|
76
|
-
if (normalized.type !== ZOD_SCHEMA_TYPE.object) {
|
|
77
|
-
throw new Error(`Entry ${endpoint.entry.name} output contract must use a Zod object schema`);
|
|
78
|
-
}
|
|
79
|
-
return normalized;
|
|
80
|
-
}
|
|
81
|
-
function unwrapZodSchema(schema) {
|
|
82
|
-
const wrapper = schema;
|
|
83
|
-
return wrapper.unwrap();
|
|
104
|
+
function isMcpChannelBinding(binding) {
|
|
105
|
+
return binding.channel === CHANNEL.mcp && isMcpBinding(binding.binding);
|
|
84
106
|
}
|