@mcpmake/core 0.3.4 → 0.3.5
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/dist/analyzer/goal-crawler.d.ts.map +1 -1
- package/dist/analyzer/goal-crawler.js +2 -1
- package/dist/analyzer/goal-crawler.js.map +1 -1
- package/dist/analyzer/site-crawler.d.ts.map +1 -1
- package/dist/analyzer/site-crawler.js +9 -3
- package/dist/analyzer/site-crawler.js.map +1 -1
- package/dist/emitter/index.d.ts.map +1 -1
- package/dist/emitter/index.js +128 -18
- package/dist/emitter/index.js.map +1 -1
- package/dist/emitter/mcpb-bundler.d.ts +14 -0
- package/dist/emitter/mcpb-bundler.d.ts.map +1 -1
- package/dist/emitter/mcpb-bundler.js +52 -4
- package/dist/emitter/mcpb-bundler.js.map +1 -1
- package/dist/emitter/python-annotations.d.ts +77 -0
- package/dist/emitter/python-annotations.d.ts.map +1 -0
- package/dist/emitter/python-annotations.js +330 -0
- package/dist/emitter/python-annotations.js.map +1 -0
- package/dist/emitter/python-templates/server.py.hbs +29 -8
- package/dist/emitter/site-templates/browser-manager.ts.hbs +140 -30
- package/dist/emitter/site-templates/config.ts.hbs +13 -1
- package/dist/emitter/site-templates/env.example.hbs +8 -0
- package/dist/emitter/site-templates/telemetry.ts.hbs +13 -2
- package/dist/emitter/templates/discovery.ts.hbs +43 -4
- package/dist/emitter/templates/server-main-http.ts.hbs +79 -1
- package/dist/emitter/worker-templates/wrangler.toml.hbs +5 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/parser/har-normalizer.d.ts.map +1 -1
- package/dist/parser/har-normalizer.js +27 -2
- package/dist/parser/har-normalizer.js.map +1 -1
- package/dist/parser/postman-loader.d.ts.map +1 -1
- package/dist/parser/postman-loader.js +24 -2
- package/dist/parser/postman-loader.js.map +1 -1
- package/dist/parser/schema-converter.d.ts +8 -1
- package/dist/parser/schema-converter.d.ts.map +1 -1
- package/dist/parser/schema-converter.js +20 -2
- package/dist/parser/schema-converter.js.map +1 -1
- package/dist/recorder/browser-recorder.d.ts.map +1 -1
- package/dist/recorder/browser-recorder.js +2 -1
- package/dist/recorder/browser-recorder.js.map +1 -1
- package/dist/rescan/rescan-scheduler.d.ts +53 -2
- package/dist/rescan/rescan-scheduler.d.ts.map +1 -1
- package/dist/rescan/rescan-scheduler.js +163 -2
- package/dist/rescan/rescan-scheduler.js.map +1 -1
- package/dist/transformer/catalog-builder.d.ts +7 -1
- package/dist/transformer/catalog-builder.d.ts.map +1 -1
- package/dist/transformer/catalog-builder.js +1 -0
- package/dist/transformer/catalog-builder.js.map +1 -1
- package/dist/transformer/tool-builder.d.ts.map +1 -1
- package/dist/transformer/tool-builder.js +4 -1
- package/dist/transformer/tool-builder.js.map +1 -1
- package/dist/types/index.d.ts +39 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +38 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +75 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/playwright-loader.d.ts +21 -0
- package/dist/utils/playwright-loader.d.ts.map +1 -0
- package/dist/utils/playwright-loader.js +21 -0
- package/dist/utils/playwright-loader.js.map +1 -0
- package/dist/utils/ssrf-guard.d.ts.map +1 -1
- package/dist/utils/ssrf-guard.js +8 -0
- package/dist/utils/ssrf-guard.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map JSON Schema fragments onto precise Python type annotations + Pydantic
|
|
3
|
+
* models so the generated FastMCP server's inferred tool inputSchema regains
|
|
4
|
+
* full fidelity (A4-H2).
|
|
5
|
+
*
|
|
6
|
+
* FastMCP infers a tool's inputSchema from the handler function SIGNATURE, so
|
|
7
|
+
* the fidelity of the schema is exactly the fidelity of the annotations we emit:
|
|
8
|
+
* - scalars → `str` / `int` / `float` / `bool`
|
|
9
|
+
* - string enums → `Literal["a", "b", ...]`
|
|
10
|
+
* - arrays → `list[<item>]`
|
|
11
|
+
* - bounds (`minimum`/`maxLength`/…) → `Field(ge=.., max_length=.., ...)`
|
|
12
|
+
* - a plain-object request body → a Pydantic `BaseModel` (nested objects
|
|
13
|
+
* become nested models), which FastMCP expands into a nested object schema.
|
|
14
|
+
*
|
|
15
|
+
* Everything an OpenAPI schema can throw at us that we cannot model precisely
|
|
16
|
+
* (`$ref` left unresolved, `allOf`/`oneOf`/`anyOf`, free-form objects) degrades
|
|
17
|
+
* to a permissive but always-VALID annotation (`str`, `Any`, `dict`) — the
|
|
18
|
+
* emitter must never produce Python that fails to import.
|
|
19
|
+
*/
|
|
20
|
+
import type { JsonSchema } from '../types/index.js';
|
|
21
|
+
/** A Python type annotation plus the `Field(...)` keyword args it needs. */
|
|
22
|
+
export interface PyAnnotation {
|
|
23
|
+
/** The base Python type, e.g. `int`, `Literal["a", "b"]`, `list[str]`. */
|
|
24
|
+
annotation: string;
|
|
25
|
+
/** Ordered `Field(...)` kwargs, e.g. `['ge=1', 'le=100', 'description="..."']`. */
|
|
26
|
+
fieldArgs: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Produce the base Python type + Field kwargs for a single path/query/header/
|
|
30
|
+
* cookie parameter (or a nested non-object body property). `optional` is handled
|
|
31
|
+
* by the caller (it wraps the base type in `| None` and appends `= None`).
|
|
32
|
+
*/
|
|
33
|
+
export declare function jsonSchemaToPyAnnotation(schema: JsonSchema | undefined): PyAnnotation;
|
|
34
|
+
/**
|
|
35
|
+
* Render a complete parameter annotation (the text after the `:` in a function
|
|
36
|
+
* signature) for a base type + Field kwargs + optionality.
|
|
37
|
+
*
|
|
38
|
+
* required, no args → `int`
|
|
39
|
+
* required, args → `Annotated[int, Field(ge=1)]`
|
|
40
|
+
* optional, no args → `int | None = None`
|
|
41
|
+
* optional, args → `Annotated[int | None, Field(ge=1)] = None`
|
|
42
|
+
*
|
|
43
|
+
* Returns the full RHS so the template stays a dumb interpolation.
|
|
44
|
+
*/
|
|
45
|
+
export declare function renderParamAnnotation(base: string, fieldArgs: string[], optional: boolean): string;
|
|
46
|
+
/** Whether a body schema is a plain object we can faithfully model as a Pydantic class. */
|
|
47
|
+
export declare function isModellableObject(schema: JsonSchema | undefined): boolean;
|
|
48
|
+
/** A generated Pydantic model: its class name and the full source of its `class` block. */
|
|
49
|
+
export interface PydanticModel {
|
|
50
|
+
className: string;
|
|
51
|
+
source: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Allocates unique, valid, non-reserved Pydantic model class names. Seeded with
|
|
55
|
+
* Python/runtime names that already appear in server.py so a model can never
|
|
56
|
+
* shadow `BaseModel`, `Field`, `FastMCP`, etc.
|
|
57
|
+
*/
|
|
58
|
+
export declare class ModelNameAllocator {
|
|
59
|
+
private readonly used;
|
|
60
|
+
constructor(seed?: Iterable<string>);
|
|
61
|
+
allocate(hint: string): string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Generate a Pydantic `BaseModel` class for a plain-object schema, recursing for
|
|
65
|
+
* nested object properties (each becomes its own nested model). Appends every
|
|
66
|
+
* model it creates to `models` (parent last is fine — Python resolves names
|
|
67
|
+
* lazily at runtime, but we emit children before parents so the source reads
|
|
68
|
+
* top-down). Returns the class name to reference for this object.
|
|
69
|
+
*
|
|
70
|
+
* Property keys that are not valid Python identifiers (e.g. `user-id`) use a
|
|
71
|
+
* sanitized field name plus `Field(alias="<wireKey>")`; the model carries
|
|
72
|
+
* `model_config = ConfigDict(populate_by_name=True)` so it accepts either name,
|
|
73
|
+
* and the handler serializes with `by_alias=True` to send the original wire key.
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildPydanticModel(schema: JsonSchema, nameHint: string, alloc: ModelNameAllocator, models: PydanticModel[]): string;
|
|
76
|
+
export declare function isPyKeyword(name: string): boolean;
|
|
77
|
+
//# sourceMappingURL=python-annotations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python-annotations.d.ts","sourceRoot":"","sources":["../../src/emitter/python-annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AA+FD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,YAAY,CAOrF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,EAAE,OAAO,GAChB,MAAM,CAOR;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,CAO1E;AAED,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;gBAEvB,IAAI,GAAE,QAAQ,CAAC,MAAM,CAAM;IAmBvC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAiB/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,aAAa,EAAE,GACtB,MAAM,CAwER;AAuED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD"}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { sanitizePyIdentifier } from '../utils/sanitize.js';
|
|
2
|
+
/**
|
|
3
|
+
* Escape a string for a double-quoted Python literal. Kept local (rather than
|
|
4
|
+
* importing the Handlebars-bound `escapePyString`) so this module has no
|
|
5
|
+
* template-loader dependency and stays unit-testable in isolation.
|
|
6
|
+
*/
|
|
7
|
+
function pyStringLiteral(value) {
|
|
8
|
+
return `"${value
|
|
9
|
+
.replace(/\\/g, '\\\\')
|
|
10
|
+
.replace(/"/g, '\\"')
|
|
11
|
+
.replace(/\n/g, '\\n')
|
|
12
|
+
.replace(/\r/g, '\\r')}"`;
|
|
13
|
+
}
|
|
14
|
+
/** A JSON-Schema `type` may be a string or an array of strings (OpenAPI 3.1). */
|
|
15
|
+
function primaryType(schema) {
|
|
16
|
+
const t = schema.type;
|
|
17
|
+
if (typeof t === 'string')
|
|
18
|
+
return t;
|
|
19
|
+
if (Array.isArray(t)) {
|
|
20
|
+
// Prefer the first non-"null" member (nullable shorthand) for the base type.
|
|
21
|
+
const nonNull = t.find((x) => x !== 'null');
|
|
22
|
+
return typeof nonNull === 'string' ? nonNull : undefined;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Collect numeric/string-length/pattern bounds into `Field(...)` kwargs. */
|
|
27
|
+
function boundsFieldArgs(schema) {
|
|
28
|
+
const args = [];
|
|
29
|
+
const numeric = (key, kw) => {
|
|
30
|
+
const v = schema[key];
|
|
31
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
32
|
+
args.push(`${kw}=${v}`);
|
|
33
|
+
};
|
|
34
|
+
numeric('minimum', 'ge');
|
|
35
|
+
numeric('maximum', 'le');
|
|
36
|
+
// OpenAPI 3.0 boolean exclusiveMinimum/Maximum pairs with minimum/maximum;
|
|
37
|
+
// only the 3.1 numeric form maps cleanly to gt/lt, so guard on `typeof number`
|
|
38
|
+
// (the `numeric` helper already does). A boolean value is ignored.
|
|
39
|
+
numeric('exclusiveMinimum', 'gt');
|
|
40
|
+
numeric('exclusiveMaximum', 'lt');
|
|
41
|
+
numeric('minLength', 'min_length');
|
|
42
|
+
numeric('maxLength', 'max_length');
|
|
43
|
+
if (typeof schema.pattern === 'string' && schema.pattern.length > 0) {
|
|
44
|
+
args.push(`pattern=${pyStringLiteral(schema.pattern)}`);
|
|
45
|
+
}
|
|
46
|
+
return args;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Map a (non-body-root) schema to a Python base type. Body objects are handled
|
|
50
|
+
* separately as Pydantic models; here an `object` degrades to `dict`. Returns
|
|
51
|
+
* only the base type string — bounds/description are added by the caller.
|
|
52
|
+
*/
|
|
53
|
+
function baseTypeFor(schema) {
|
|
54
|
+
if (!schema || typeof schema !== 'object')
|
|
55
|
+
return 'Any';
|
|
56
|
+
// String enum → Literal[...]. Only a homogeneous all-string enum maps to a
|
|
57
|
+
// Literal; a mixed-type enum degrades to the base scalar type below.
|
|
58
|
+
const enumVals = schema.enum;
|
|
59
|
+
if (Array.isArray(enumVals) &&
|
|
60
|
+
enumVals.length > 0 &&
|
|
61
|
+
enumVals.every((v) => typeof v === 'string')) {
|
|
62
|
+
const members = enumVals.map(pyStringLiteral).join(', ');
|
|
63
|
+
return `Literal[${members}]`;
|
|
64
|
+
}
|
|
65
|
+
const t = primaryType(schema);
|
|
66
|
+
switch (t) {
|
|
67
|
+
case 'string':
|
|
68
|
+
return 'str';
|
|
69
|
+
case 'integer':
|
|
70
|
+
return 'int';
|
|
71
|
+
case 'number':
|
|
72
|
+
return 'float';
|
|
73
|
+
case 'boolean':
|
|
74
|
+
return 'bool';
|
|
75
|
+
case 'array': {
|
|
76
|
+
const items = schema.items;
|
|
77
|
+
if (items && typeof items === 'object' && !Array.isArray(items)) {
|
|
78
|
+
return `list[${baseTypeFor(items)}]`;
|
|
79
|
+
}
|
|
80
|
+
return 'list';
|
|
81
|
+
}
|
|
82
|
+
case 'object':
|
|
83
|
+
return 'dict';
|
|
84
|
+
default:
|
|
85
|
+
// Unknown / unresolved $ref / allOf|oneOf|anyOf with no `type`: stay
|
|
86
|
+
// permissive but valid. `Any` accepts anything and never breaks import.
|
|
87
|
+
return 'Any';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Produce the base Python type + Field kwargs for a single path/query/header/
|
|
92
|
+
* cookie parameter (or a nested non-object body property). `optional` is handled
|
|
93
|
+
* by the caller (it wraps the base type in `| None` and appends `= None`).
|
|
94
|
+
*/
|
|
95
|
+
export function jsonSchemaToPyAnnotation(schema) {
|
|
96
|
+
const annotation = baseTypeFor(schema);
|
|
97
|
+
const fieldArgs = schema ? boundsFieldArgs(schema) : [];
|
|
98
|
+
if (schema && typeof schema.description === 'string' && schema.description.length > 0) {
|
|
99
|
+
fieldArgs.push(`description=${pyStringLiteral(schema.description)}`);
|
|
100
|
+
}
|
|
101
|
+
return { annotation, fieldArgs };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Render a complete parameter annotation (the text after the `:` in a function
|
|
105
|
+
* signature) for a base type + Field kwargs + optionality.
|
|
106
|
+
*
|
|
107
|
+
* required, no args → `int`
|
|
108
|
+
* required, args → `Annotated[int, Field(ge=1)]`
|
|
109
|
+
* optional, no args → `int | None = None`
|
|
110
|
+
* optional, args → `Annotated[int | None, Field(ge=1)] = None`
|
|
111
|
+
*
|
|
112
|
+
* Returns the full RHS so the template stays a dumb interpolation.
|
|
113
|
+
*/
|
|
114
|
+
export function renderParamAnnotation(base, fieldArgs, optional) {
|
|
115
|
+
const typePart = optional ? `${base} | None` : base;
|
|
116
|
+
const suffix = optional ? ' = None' : '';
|
|
117
|
+
if (fieldArgs.length === 0) {
|
|
118
|
+
return `${typePart}${suffix}`;
|
|
119
|
+
}
|
|
120
|
+
return `Annotated[${typePart}, Field(${fieldArgs.join(', ')})]${suffix}`;
|
|
121
|
+
}
|
|
122
|
+
/** Whether a body schema is a plain object we can faithfully model as a Pydantic class. */
|
|
123
|
+
export function isModellableObject(schema) {
|
|
124
|
+
if (!schema || typeof schema !== 'object')
|
|
125
|
+
return false;
|
|
126
|
+
if (primaryType(schema) !== 'object')
|
|
127
|
+
return false;
|
|
128
|
+
const props = schema.properties;
|
|
129
|
+
return (!!props && typeof props === 'object' && !Array.isArray(props) && Object.keys(props).length > 0);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Allocates unique, valid, non-reserved Pydantic model class names. Seeded with
|
|
133
|
+
* Python/runtime names that already appear in server.py so a model can never
|
|
134
|
+
* shadow `BaseModel`, `Field`, `FastMCP`, etc.
|
|
135
|
+
*/
|
|
136
|
+
export class ModelNameAllocator {
|
|
137
|
+
used;
|
|
138
|
+
constructor(seed = []) {
|
|
139
|
+
this.used = new Set([
|
|
140
|
+
// Imported / runtime names the model defs sit alongside in server.py.
|
|
141
|
+
'BaseModel',
|
|
142
|
+
'Field',
|
|
143
|
+
'ConfigDict',
|
|
144
|
+
'FastMCP',
|
|
145
|
+
'TextContent',
|
|
146
|
+
'Annotated',
|
|
147
|
+
'Literal',
|
|
148
|
+
'Optional',
|
|
149
|
+
'Any',
|
|
150
|
+
'BASE_URL',
|
|
151
|
+
'server',
|
|
152
|
+
'client',
|
|
153
|
+
...seed,
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
allocate(hint) {
|
|
157
|
+
// PascalCase the hint, strip to identifier chars, prefix when it would start
|
|
158
|
+
// with a digit / be empty so the class name is always valid Python.
|
|
159
|
+
const cleaned = sanitizePyIdentifier(hint);
|
|
160
|
+
const pascal = cleaned
|
|
161
|
+
.split('_')
|
|
162
|
+
.filter(Boolean)
|
|
163
|
+
.map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1))
|
|
164
|
+
.join('');
|
|
165
|
+
let base = pascal && /^[A-Za-z]/.test(pascal) ? pascal : `Model${pascal}`;
|
|
166
|
+
if (!base)
|
|
167
|
+
base = 'Model';
|
|
168
|
+
let candidate = base;
|
|
169
|
+
let n = 1;
|
|
170
|
+
while (this.used.has(candidate))
|
|
171
|
+
candidate = `${base}${n++}`;
|
|
172
|
+
this.used.add(candidate);
|
|
173
|
+
return candidate;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Generate a Pydantic `BaseModel` class for a plain-object schema, recursing for
|
|
178
|
+
* nested object properties (each becomes its own nested model). Appends every
|
|
179
|
+
* model it creates to `models` (parent last is fine — Python resolves names
|
|
180
|
+
* lazily at runtime, but we emit children before parents so the source reads
|
|
181
|
+
* top-down). Returns the class name to reference for this object.
|
|
182
|
+
*
|
|
183
|
+
* Property keys that are not valid Python identifiers (e.g. `user-id`) use a
|
|
184
|
+
* sanitized field name plus `Field(alias="<wireKey>")`; the model carries
|
|
185
|
+
* `model_config = ConfigDict(populate_by_name=True)` so it accepts either name,
|
|
186
|
+
* and the handler serializes with `by_alias=True` to send the original wire key.
|
|
187
|
+
*/
|
|
188
|
+
export function buildPydanticModel(schema, nameHint, alloc, models) {
|
|
189
|
+
const className = alloc.allocate(nameHint);
|
|
190
|
+
const props = (schema.properties ?? {});
|
|
191
|
+
const requiredList = Array.isArray(schema.required) ? schema.required : [];
|
|
192
|
+
const requiredSet = new Set(requiredList);
|
|
193
|
+
const fieldLines = [];
|
|
194
|
+
let needsAlias = false;
|
|
195
|
+
const usedFieldNames = new Set();
|
|
196
|
+
for (const [wireKey, propSchema] of Object.entries(props)) {
|
|
197
|
+
const isRequired = requiredSet.has(wireKey);
|
|
198
|
+
// Determine the python field name. Valid identifiers are used as-is; anything
|
|
199
|
+
// else is sanitized and aliased to the wire key. De-dupe sanitized names so
|
|
200
|
+
// two wire keys that sanitize to the same identifier don't collide.
|
|
201
|
+
// NOTE: a Pydantic v2 model field may NOT begin with `_` (those are treated
|
|
202
|
+
// as private attributes — `NameError: Fields must not use names with leading
|
|
203
|
+
// underscores`). So a leading-underscore wire key is NOT a valid field name
|
|
204
|
+
// here (unlike a function arg), and the sanitizer must never emit a leading
|
|
205
|
+
// `_`. sanitizePyFieldName prefixes `f_` instead.
|
|
206
|
+
const validIdent = /^[A-Za-z][A-Za-z0-9_]*$/.test(wireKey) &&
|
|
207
|
+
!isPyKeyword(wireKey) &&
|
|
208
|
+
!isReservedPydanticField(wireKey);
|
|
209
|
+
let fieldName = validIdent ? wireKey : sanitizePyFieldName(wireKey);
|
|
210
|
+
while (usedFieldNames.has(fieldName))
|
|
211
|
+
fieldName = `${fieldName}_`;
|
|
212
|
+
usedFieldNames.add(fieldName);
|
|
213
|
+
const aliased = fieldName !== wireKey;
|
|
214
|
+
if (aliased)
|
|
215
|
+
needsAlias = true;
|
|
216
|
+
// Nested plain-object property → its own model; otherwise a scalar/array.
|
|
217
|
+
let baseType;
|
|
218
|
+
let fieldArgs;
|
|
219
|
+
if (isModellableObject(propSchema)) {
|
|
220
|
+
baseType = buildPydanticModel(propSchema, `${className}_${fieldName}`, alloc, models);
|
|
221
|
+
fieldArgs = [];
|
|
222
|
+
if (typeof propSchema.description === 'string' && propSchema.description.length > 0) {
|
|
223
|
+
fieldArgs.push(`description=${pyStringLiteral(propSchema.description)}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
const ann = jsonSchemaToPyAnnotation(propSchema);
|
|
228
|
+
baseType = ann.annotation;
|
|
229
|
+
fieldArgs = ann.fieldArgs;
|
|
230
|
+
}
|
|
231
|
+
const typePart = isRequired ? baseType : `${baseType} | None`;
|
|
232
|
+
// Compose the Field(...) call. alias must come first for readability; the
|
|
233
|
+
// default sentinel for an optional field is `None` (passed as the Field
|
|
234
|
+
// default so the property is genuinely optional in the inferred schema).
|
|
235
|
+
const callArgs = [];
|
|
236
|
+
if (aliased)
|
|
237
|
+
callArgs.push(`alias=${pyStringLiteral(wireKey)}`);
|
|
238
|
+
if (!isRequired)
|
|
239
|
+
callArgs.push('default=None');
|
|
240
|
+
callArgs.push(...fieldArgs);
|
|
241
|
+
let rhs;
|
|
242
|
+
if (callArgs.length === 0) {
|
|
243
|
+
// Required, no metadata, valid identifier → bare typed field.
|
|
244
|
+
rhs = '';
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
rhs = ` = Field(${callArgs.join(', ')})`;
|
|
248
|
+
}
|
|
249
|
+
fieldLines.push(` ${fieldName}: ${typePart}${rhs}`);
|
|
250
|
+
}
|
|
251
|
+
const header = `class ${className}(BaseModel):`;
|
|
252
|
+
const configLine = needsAlias ? ' model_config = ConfigDict(populate_by_name=True)\n' : '';
|
|
253
|
+
const body = fieldLines.length > 0 ? fieldLines.join('\n') : ' pass';
|
|
254
|
+
const source = `${header}\n${configLine}${body}\n`;
|
|
255
|
+
models.push({ className, source });
|
|
256
|
+
return className;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Whether a wire key — even though it's a syntactically valid Python identifier —
|
|
260
|
+
* would be rejected by Pydantic v2 as a model field name. Pydantic reserves the
|
|
261
|
+
* `model_` namespace (`model_config` is the special class attribute; `model_dump`/
|
|
262
|
+
* `model_validate`/… are BaseModel methods), so a body property literally named
|
|
263
|
+
* `model_config`/`model_dump` (common in ML/AI API specs) would otherwise crash
|
|
264
|
+
* the generated server at import. Such keys are remapped to an `f_`-prefixed
|
|
265
|
+
* field with `Field(alias="<wireKey>")` preserving the wire name.
|
|
266
|
+
*/
|
|
267
|
+
function isReservedPydanticField(name) {
|
|
268
|
+
return name.startsWith('model_');
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Sanitize a wire key into a legal Pydantic v2 model FIELD name. Like
|
|
272
|
+
* {@link sanitizePyIdentifier} but it never emits a leading underscore (Pydantic
|
|
273
|
+
* rejects `_`-leading field names) — a result that wouldn't start with a letter
|
|
274
|
+
* is prefixed with `f_`. A Python keyword or a Pydantic-reserved (`model_…`) name
|
|
275
|
+
* is also remapped. The original wire key is preserved via `Field(alias=…)`, so
|
|
276
|
+
* the on-the-wire name is unchanged.
|
|
277
|
+
*/
|
|
278
|
+
function sanitizePyFieldName(wireKey) {
|
|
279
|
+
let id = wireKey.replace(/[^A-Za-z0-9_]/g, '_');
|
|
280
|
+
if (!/^[A-Za-z]/.test(id))
|
|
281
|
+
id = `f_${id}`;
|
|
282
|
+
if (isPyKeyword(id))
|
|
283
|
+
id = `${id}_`;
|
|
284
|
+
if (isReservedPydanticField(id))
|
|
285
|
+
id = `f_${id}`;
|
|
286
|
+
return id;
|
|
287
|
+
}
|
|
288
|
+
const PY_KEYWORDS = new Set([
|
|
289
|
+
'False',
|
|
290
|
+
'None',
|
|
291
|
+
'True',
|
|
292
|
+
'and',
|
|
293
|
+
'as',
|
|
294
|
+
'assert',
|
|
295
|
+
'async',
|
|
296
|
+
'await',
|
|
297
|
+
'break',
|
|
298
|
+
'class',
|
|
299
|
+
'continue',
|
|
300
|
+
'def',
|
|
301
|
+
'del',
|
|
302
|
+
'elif',
|
|
303
|
+
'else',
|
|
304
|
+
'except',
|
|
305
|
+
'finally',
|
|
306
|
+
'for',
|
|
307
|
+
'from',
|
|
308
|
+
'global',
|
|
309
|
+
'if',
|
|
310
|
+
'import',
|
|
311
|
+
'in',
|
|
312
|
+
'is',
|
|
313
|
+
'lambda',
|
|
314
|
+
'nonlocal',
|
|
315
|
+
'not',
|
|
316
|
+
'or',
|
|
317
|
+
'pass',
|
|
318
|
+
'raise',
|
|
319
|
+
'return',
|
|
320
|
+
'try',
|
|
321
|
+
'while',
|
|
322
|
+
'with',
|
|
323
|
+
'yield',
|
|
324
|
+
'match',
|
|
325
|
+
'case',
|
|
326
|
+
]);
|
|
327
|
+
export function isPyKeyword(name) {
|
|
328
|
+
return PY_KEYWORDS.has(name);
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=python-annotations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python-annotations.js","sourceRoot":"","sources":["../../src/emitter/python-annotations.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import json
|
|
5
5
|
import httpx
|
|
6
|
+
from typing import Annotated, Literal, Optional, Any
|
|
6
7
|
from urllib.parse import quote
|
|
7
8
|
from dotenv import load_dotenv
|
|
9
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
8
10
|
from mcp.server.fastmcp import FastMCP
|
|
9
11
|
from mcp.types import TextContent
|
|
10
12
|
|
|
@@ -91,9 +93,17 @@ def _auth_query_params() -> dict[str, str]:
|
|
|
91
93
|
return params
|
|
92
94
|
|
|
93
95
|
|
|
96
|
+
{{#if pydanticModels}}
|
|
97
|
+
# Request-body models (A4-H2). Each plain-object request body is modelled as a
|
|
98
|
+
# Pydantic BaseModel so FastMCP infers a full-fidelity nested-object input schema
|
|
99
|
+
# (typed properties, required set, descriptions). Fields whose wire name is not a
|
|
100
|
+
# valid Python identifier use Field(alias=...) + populate_by_name; the handler
|
|
101
|
+
# serializes with by_alias=True to restore the original key on the wire.
|
|
102
|
+
{{{pydanticModels}}}
|
|
103
|
+
{{/if}}
|
|
94
104
|
{{#each tools}}
|
|
95
105
|
@server.tool(name="{{pyStr name}}")
|
|
96
|
-
async def {{functionName}}({{
|
|
106
|
+
async def {{functionName}}({{{pySignature}}}) -> list[TextContent]:
|
|
97
107
|
"""{{pyDocstring description}}"""
|
|
98
108
|
url = BASE_URL + "{{pyStr pathTemplate}}"
|
|
99
109
|
{{#each pyPathParams}}
|
|
@@ -101,7 +111,8 @@ async def {{functionName}}({{#each pyPathParams}}{{pyName}}: str, {{/each}}{{#ea
|
|
|
101
111
|
{{/each}}
|
|
102
112
|
{{#if pyQueryParams.length}}
|
|
103
113
|
params = { {{#each pyQueryParams}}"{{pyStr apiName}}": {{pyName}}, {{/each}} }
|
|
104
|
-
|
|
114
|
+
# Drop only unset (None) optionals — a legitimate 0/false/"" must still be sent.
|
|
115
|
+
params = {k: v for k, v in params.items() if v is not None}
|
|
105
116
|
{{#if @root.hasQueryApiKey}}
|
|
106
117
|
params.update(_auth_query_params())
|
|
107
118
|
{{/if}}
|
|
@@ -115,13 +126,13 @@ async def {{functionName}}({{#each pyPathParams}}{{pyName}}: str, {{/each}}{{#ea
|
|
|
115
126
|
# names, merged over the auth headers. Empty values are omitted.
|
|
116
127
|
req_headers = dict(_headers())
|
|
117
128
|
{{#each pyHeaderParams}}
|
|
118
|
-
if {{pyName}}:
|
|
119
|
-
req_headers["{{pyStr wireName}}"] = {{pyName}}
|
|
129
|
+
if {{pyName}} is not None:
|
|
130
|
+
req_headers["{{pyStr wireName}}"] = str({{pyName}})
|
|
120
131
|
{{/each}}
|
|
121
132
|
{{#if pyCookieParams.length}}
|
|
122
133
|
_cookie_parts = []
|
|
123
134
|
{{#each pyCookieParams}}
|
|
124
|
-
if {{pyName}}:
|
|
135
|
+
if {{pyName}} is not None:
|
|
125
136
|
_cookie_parts.append("{{pyStr wireName}}=" + quote(str({{pyName}}), safe=""))
|
|
126
137
|
{{/each}}
|
|
127
138
|
if _cookie_parts:
|
|
@@ -130,6 +141,16 @@ async def {{functionName}}({{#each pyPathParams}}{{pyName}}: str, {{/each}}{{#ea
|
|
|
130
141
|
req_headers["Cookie"] = (_existing + "; " + _joined) if _existing else _joined
|
|
131
142
|
{{/if}}
|
|
132
143
|
{{/if}}
|
|
144
|
+
{{#if hasRequestBody}}
|
|
145
|
+
{{#if bodyIsModel}}
|
|
146
|
+
# Pydantic body → plain dict for httpx. by_alias restores any non-identifier
|
|
147
|
+
# wire keys; exclude_none drops unset optionals so they are omitted, not nulled.
|
|
148
|
+
_body = body.model_dump(exclude_none=True, by_alias=True) if body is not None else None
|
|
149
|
+
{{else}}
|
|
150
|
+
# Scalar / array / free-form body kept as the raw typed value.
|
|
151
|
+
_body = body
|
|
152
|
+
{{/if}}
|
|
153
|
+
{{/if}}
|
|
133
154
|
try:
|
|
134
155
|
async with client.stream(
|
|
135
156
|
"{{method}}",
|
|
@@ -140,12 +161,12 @@ async def {{functionName}}({{#each pyPathParams}}{{pyName}}: str, {{/each}}{{#ea
|
|
|
140
161
|
{{/if}}
|
|
141
162
|
{{#if hasRequestBody}}
|
|
142
163
|
{{#if (eq bodyEncoding "form")}}
|
|
143
|
-
data=
|
|
164
|
+
data=_body,
|
|
144
165
|
{{else}}
|
|
145
166
|
{{#if (eq bodyEncoding "multipart")}}
|
|
146
|
-
files=
|
|
167
|
+
files=_body,
|
|
147
168
|
{{else}}
|
|
148
|
-
json=
|
|
169
|
+
json=_body,
|
|
149
170
|
{{/if}}
|
|
150
171
|
{{/if}}
|
|
151
172
|
{{/if}}
|