@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.
Files changed (67) hide show
  1. package/dist/analyzer/goal-crawler.d.ts.map +1 -1
  2. package/dist/analyzer/goal-crawler.js +2 -1
  3. package/dist/analyzer/goal-crawler.js.map +1 -1
  4. package/dist/analyzer/site-crawler.d.ts.map +1 -1
  5. package/dist/analyzer/site-crawler.js +9 -3
  6. package/dist/analyzer/site-crawler.js.map +1 -1
  7. package/dist/emitter/index.d.ts.map +1 -1
  8. package/dist/emitter/index.js +128 -18
  9. package/dist/emitter/index.js.map +1 -1
  10. package/dist/emitter/mcpb-bundler.d.ts +14 -0
  11. package/dist/emitter/mcpb-bundler.d.ts.map +1 -1
  12. package/dist/emitter/mcpb-bundler.js +52 -4
  13. package/dist/emitter/mcpb-bundler.js.map +1 -1
  14. package/dist/emitter/python-annotations.d.ts +77 -0
  15. package/dist/emitter/python-annotations.d.ts.map +1 -0
  16. package/dist/emitter/python-annotations.js +330 -0
  17. package/dist/emitter/python-annotations.js.map +1 -0
  18. package/dist/emitter/python-templates/server.py.hbs +29 -8
  19. package/dist/emitter/site-templates/browser-manager.ts.hbs +140 -30
  20. package/dist/emitter/site-templates/config.ts.hbs +13 -1
  21. package/dist/emitter/site-templates/env.example.hbs +8 -0
  22. package/dist/emitter/site-templates/telemetry.ts.hbs +13 -2
  23. package/dist/emitter/templates/discovery.ts.hbs +43 -4
  24. package/dist/emitter/templates/server-main-http.ts.hbs +79 -1
  25. package/dist/emitter/worker-templates/wrangler.toml.hbs +5 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/parser/har-normalizer.d.ts.map +1 -1
  31. package/dist/parser/har-normalizer.js +27 -2
  32. package/dist/parser/har-normalizer.js.map +1 -1
  33. package/dist/parser/postman-loader.d.ts.map +1 -1
  34. package/dist/parser/postman-loader.js +24 -2
  35. package/dist/parser/postman-loader.js.map +1 -1
  36. package/dist/parser/schema-converter.d.ts +8 -1
  37. package/dist/parser/schema-converter.d.ts.map +1 -1
  38. package/dist/parser/schema-converter.js +20 -2
  39. package/dist/parser/schema-converter.js.map +1 -1
  40. package/dist/recorder/browser-recorder.d.ts.map +1 -1
  41. package/dist/recorder/browser-recorder.js +2 -1
  42. package/dist/recorder/browser-recorder.js.map +1 -1
  43. package/dist/rescan/rescan-scheduler.d.ts +53 -2
  44. package/dist/rescan/rescan-scheduler.d.ts.map +1 -1
  45. package/dist/rescan/rescan-scheduler.js +163 -2
  46. package/dist/rescan/rescan-scheduler.js.map +1 -1
  47. package/dist/transformer/catalog-builder.d.ts +7 -1
  48. package/dist/transformer/catalog-builder.d.ts.map +1 -1
  49. package/dist/transformer/catalog-builder.js +1 -0
  50. package/dist/transformer/catalog-builder.js.map +1 -1
  51. package/dist/transformer/tool-builder.d.ts.map +1 -1
  52. package/dist/transformer/tool-builder.js +4 -1
  53. package/dist/transformer/tool-builder.js.map +1 -1
  54. package/dist/types/index.d.ts +39 -0
  55. package/dist/types/index.d.ts.map +1 -1
  56. package/dist/utils/logger.d.ts +38 -1
  57. package/dist/utils/logger.d.ts.map +1 -1
  58. package/dist/utils/logger.js +75 -1
  59. package/dist/utils/logger.js.map +1 -1
  60. package/dist/utils/playwright-loader.d.ts +21 -0
  61. package/dist/utils/playwright-loader.d.ts.map +1 -0
  62. package/dist/utils/playwright-loader.js +21 -0
  63. package/dist/utils/playwright-loader.js.map +1 -0
  64. package/dist/utils/ssrf-guard.d.ts.map +1 -1
  65. package/dist/utils/ssrf-guard.js +8 -0
  66. package/dist/utils/ssrf-guard.js.map +1 -1
  67. 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}}({{#each pyPathParams}}{{pyName}}: str, {{/each}}{{#each pyQueryParams}}{{pyName}}: str = "", {{/each}}{{#each pyHeaderParams}}{{pyName}}: str = "", {{/each}}{{#each pyCookieParams}}{{pyName}}: str = "", {{/each}}{{#if hasRequestBody}}body: dict | None = None{{/if}}) -> list[TextContent]:
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
- params = {k: v for k, v in params.items() if v}
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=body,
164
+ data=_body,
144
165
  {{else}}
145
166
  {{#if (eq bodyEncoding "multipart")}}
146
- files=body,
167
+ files=_body,
147
168
  {{else}}
148
- json=body,
169
+ json=_body,
149
170
  {{/if}}
150
171
  {{/if}}
151
172
  {{/if}}