@contractkit/plugin-typescript 0.34.0 → 0.35.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/.turbo/turbo-build$colon$ci.log +5 -5
- package/.turbo/turbo-test$colon$ci.log +23 -18
- package/CHANGELOG.md +40 -0
- package/README.md +4 -3
- package/dist/codegen-mcp.d.ts +8 -1
- package/dist/codegen-mcp.d.ts.map +1 -1
- package/dist/codegen-operation.d.ts +12 -4
- package/dist/codegen-operation.d.ts.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +227 -91
- package/dist/index.js.map +1 -1
- package/dist/server-framework-koa.d.ts +7 -0
- package/dist/server-framework-koa.d.ts.map +1 -0
- package/dist/server-framework.d.ts +94 -0
- package/dist/server-framework.d.ts.map +1 -0
- package/llms.txt +15 -1
- package/package.json +1 -1
- package/src/codegen-mcp.ts +25 -28
- package/src/codegen-operation.ts +99 -74
- package/src/index.ts +36 -5
- package/src/server-framework-koa.ts +112 -0
- package/src/server-framework.ts +119 -0
- package/tests/codegen-mcp.test.ts +35 -1
- package/tests/codegen-operation-framework.test.ts +140 -0
- package/tests/codegen-operation.test.ts +29 -0
- package/tests/codegen-server.test.ts +56 -0
- package/tests/server-framework-koa.test.ts +115 -0
- package/tests/server-framework.test.ts +24 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ServerFramework } from './server-framework.js';
|
|
2
|
+
/**
|
|
3
|
+
* ServerKit on Koa: the router is a `@koa/router` instance, handlers take a single `ctx`, and a
|
|
4
|
+
* response is written by assigning to `ctx.status` / `ctx.type` / `ctx.body` rather than returned.
|
|
5
|
+
*/
|
|
6
|
+
export declare const KOA_SERVER_FRAMEWORK: ServerFramework;
|
|
7
|
+
//# sourceMappingURL=server-framework-koa.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-framework-koa.d.ts","sourceRoot":"","sources":["../src/server-framework-koa.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAW7D;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,eAgGlC,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP frameworks the server sub-generator can target. Adding a name here without adding an adapter
|
|
3
|
+
* to {@link SERVER_FRAMEWORKS} fails to compile, which is the point of keeping the two in step.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SERVER_FRAMEWORK_NAMES: readonly ["koa"];
|
|
6
|
+
/** One of {@link SERVER_FRAMEWORK_NAMES}. */
|
|
7
|
+
export type ServerFrameworkName = (typeof SERVER_FRAMEWORK_NAMES)[number];
|
|
8
|
+
/** The framework assumed when a config names none. */
|
|
9
|
+
export declare const DEFAULT_SERVER_FRAMEWORK_NAME: ServerFrameworkName;
|
|
10
|
+
/**
|
|
11
|
+
* Every framework-specific string the router and MCP router generators emit.
|
|
12
|
+
*
|
|
13
|
+
* Granularity is one statement (or one fragment) per method, so the shared codegen keeps ownership
|
|
14
|
+
* of control flow — which branches exist, what order they run in, and which values reach them — and
|
|
15
|
+
* an adapter only decides how a given step is spelled. Anything an adapter cannot express as a
|
|
16
|
+
* statement, such as ending a response, is returned as a list of lines so it can also be empty.
|
|
17
|
+
*/
|
|
18
|
+
export interface ServerFramework {
|
|
19
|
+
readonly name: ServerFrameworkName;
|
|
20
|
+
/**
|
|
21
|
+
* Import lines for the framework runtime, already filtered down to what the generated body uses.
|
|
22
|
+
*
|
|
23
|
+
* The adapter applies `uses` itself rather than declaring a symbol list, because a framework may
|
|
24
|
+
* need more than one import line, and because only names the adapter chooses ever go through the
|
|
25
|
+
* word-boundary probe — a handler-local identifier can never be mistaken for an import.
|
|
26
|
+
*/
|
|
27
|
+
imports(uses: (symbol: string) => boolean): string[];
|
|
28
|
+
/** The module-level router value every handler attaches to. */
|
|
29
|
+
routerDeclaration(routerName: string): string;
|
|
30
|
+
/** Placeholder syntax for one path parameter, given a name already mapped to a valid identifier. */
|
|
31
|
+
pathParam(identifier: string): string;
|
|
32
|
+
/** Opening line of a handler, including its middleware and the handler function's parameters. */
|
|
33
|
+
routeOpen(routerName: string, method: string, path: string, middlewares: readonly string[]): string;
|
|
34
|
+
/** Lines that close a handler opened by {@link routeOpen}. */
|
|
35
|
+
routeClose(): string[];
|
|
36
|
+
/** Route middleware factory calls, rendered as expressions for {@link routeOpen}. */
|
|
37
|
+
readonly middleware: {
|
|
38
|
+
policy(args: string): string;
|
|
39
|
+
bodyParser(tokensExpr: string): string;
|
|
40
|
+
signature(args: string): string;
|
|
41
|
+
};
|
|
42
|
+
/** Expressions a handler reads the request through. */
|
|
43
|
+
readonly request: {
|
|
44
|
+
params: string;
|
|
45
|
+
query: string;
|
|
46
|
+
headers: string;
|
|
47
|
+
/** The body already parsed by the body-parser middleware. */
|
|
48
|
+
parsedBody: string;
|
|
49
|
+
/**
|
|
50
|
+
* The request's media type with any parameters stripped. It is matched against declared MIME
|
|
51
|
+
* literals, so an adapter whose framework exposes only the raw header must normalise it here
|
|
52
|
+
* — a `; charset=utf-8` left on the end matches nothing.
|
|
53
|
+
*/
|
|
54
|
+
contentType: string;
|
|
55
|
+
};
|
|
56
|
+
/** Expression resolving a service class out of the request-scoped DI container. */
|
|
57
|
+
resolveService(className: string): string;
|
|
58
|
+
/** Statements a handler writes the response with. */
|
|
59
|
+
readonly response: {
|
|
60
|
+
status(expr: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* One statement setting a response header. It is emitted bare or behind an `if` guard for an
|
|
63
|
+
* optional header, so it must stay a single statement.
|
|
64
|
+
*/
|
|
65
|
+
header(name: string, valueExpr: string): string;
|
|
66
|
+
type(expr: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* The terminal write for a response, or for one without a body when `bodyExpr` is undefined.
|
|
69
|
+
* A framework that ends a response by returning needs a statement in both cases; Koa, which
|
|
70
|
+
* ends it by assignment, emits nothing for a bodyless one.
|
|
71
|
+
*/
|
|
72
|
+
send(bodyExpr: string | undefined): string[];
|
|
73
|
+
/** What closes one `case` of the multi-status switch, after that status has been written. */
|
|
74
|
+
caseEnd(): string[];
|
|
75
|
+
};
|
|
76
|
+
/** The whole `mcp.router.ts` file, which is boilerplate rather than a per-operation render. */
|
|
77
|
+
mcpRouter(options: {
|
|
78
|
+
path: string;
|
|
79
|
+
}): string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Every supported framework, keyed by name. The annotation is what ties this to
|
|
83
|
+
* {@link SERVER_FRAMEWORK_NAMES}: adding a name without an adapter is a compile error.
|
|
84
|
+
*/
|
|
85
|
+
export declare const SERVER_FRAMEWORKS: Readonly<Record<ServerFrameworkName, ServerFramework>>;
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a configured framework name to its adapter.
|
|
88
|
+
*
|
|
89
|
+
* @param name The `server.framework` value, or undefined for {@link DEFAULT_SERVER_FRAMEWORK_NAME}.
|
|
90
|
+
* @throws When `name` is not a supported framework. Config arrives as JSON, so this is a runtime
|
|
91
|
+
* check and not something the `ServerFrameworkName` type can enforce on its own.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveServerFramework(name: string | undefined): ServerFramework;
|
|
94
|
+
//# sourceMappingURL=server-framework.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-framework.d.ts","sourceRoot":"","sources":["../src/server-framework.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,sBAAsB,kBAAmB,CAAC;AAEvD,6CAA6C;AAC7C,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,sDAAsD;AACtD,eAAO,MAAM,6BAA6B,EAAE,mBAA2B,CAAC;AAExE;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAEnC;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,EAAE,CAAC;IAErD,+DAA+D;IAC/D,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAE9C,oGAAoG;IACpG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAEtC,iGAAiG;IACjG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC;IAEpG,8DAA8D;IAC9D,UAAU,IAAI,MAAM,EAAE,CAAC;IAEvB,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE;QACjB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAC7B,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;QACvC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;KACnC,CAAC;IAEF,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE;QACd,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,6DAA6D;QAC7D,UAAU,EAAE,MAAM,CAAC;QACnB;;;;WAIG;QACH,WAAW,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF,mFAAmF;IACnF,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAE1C,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE;QACf,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAC7B;;;WAGG;QACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;QAChD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;QAC3B;;;;WAIG;QACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAAC;QAC7C,6FAA6F;QAC7F,OAAO,IAAI,MAAM,EAAE,CAAC;KACvB,CAAC;IAEF,+FAA+F;IAC/F,SAAS,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC;CAChD;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAEpF,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,CAShF"}
|
package/llms.txt
CHANGED
|
@@ -56,7 +56,7 @@ Path templates accept `{filename}`, `{dir}`, `{area}`, and — in `sdk.output.sd
|
|
|
56
56
|
|
|
57
57
|
| Key | Emits |
|
|
58
58
|
| --- | --- |
|
|
59
|
-
| `server` |
|
|
59
|
+
| `server` | Server routers from `operation` declarations, plus the type or Zod files they import. `framework` selects the HTTP framework; only `koa` today |
|
|
60
60
|
| `sdk` | The SDK class, per-area operation clients, and their types |
|
|
61
61
|
| `zod` | Standalone Zod schemas, independent of `server` and `sdk` |
|
|
62
62
|
| `types` | Standalone plain TypeScript types, independent of `server` and `sdk` |
|
|
@@ -82,6 +82,20 @@ Options worth knowing:
|
|
|
82
82
|
schema for argument validation and `z.toJSONSchema`. It falls back to `server.output.types` (when
|
|
83
83
|
`server.zod`) or the `zod` sub-config's output.
|
|
84
84
|
|
|
85
|
+
## Wiring the MCP tools
|
|
86
|
+
|
|
87
|
+
`mcp.tools.ts` exports `registerMcpTools(container)`, which **builds and returns** the
|
|
88
|
+
`McpToolHandlerMap`. It registers nothing: `register` belongs to InjectKit's `Registry`
|
|
89
|
+
(composition phase), while a `Container` (resolution phase) only resolves. Bind the aggregator from
|
|
90
|
+
a factory, which is also what supplies the `Container` it needs:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
registry.register(McpToolHandlerMap).useFactory(registerMcpTools).asSingleton();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The emitted tool classes are not registered for you either, so register each one on the same
|
|
97
|
+
`Registry` before the aggregator resolves it.
|
|
98
|
+
|
|
85
99
|
## Programmatic use
|
|
86
100
|
|
|
87
101
|
```typescript
|
package/package.json
CHANGED
package/src/codegen-mcp.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { inferService, deriveModulePath, buildArgs, deriveBaseName } from './cod
|
|
|
5
5
|
import { quoteKey, escapeSingleQuoted, sourceLink } from './ts-render.js';
|
|
6
6
|
import { DECIMAL_IMPORT, DECIMAL_PRELUDE_LINES } from './decimal-runtime.js';
|
|
7
7
|
import { basename, dirname, relative } from 'node:path';
|
|
8
|
+
import type { ServerFramework } from './server-framework.js';
|
|
9
|
+
import { KOA_SERVER_FRAMEWORK } from './server-framework-koa.js';
|
|
8
10
|
|
|
9
11
|
// ─── Options ────────────────────────────────────────────────────────────────
|
|
10
12
|
|
|
@@ -372,7 +374,10 @@ function renderToolClass(plan: ToolPlan, file: string, options: McpCodegenOption
|
|
|
372
374
|
const isVoid = !primaryResponseBody(op);
|
|
373
375
|
const structured = !!outExpr;
|
|
374
376
|
|
|
375
|
-
|
|
377
|
+
// No args to destructure means the parameter goes unread, which trips no-unused-vars in
|
|
378
|
+
// consumers that lint generated output; the leading underscore opts it out.
|
|
379
|
+
const argsParam = destructure.length > 0 ? 'args' : '_args';
|
|
380
|
+
lines.push(` async handle(${argsParam}: Record<string, unknown>, _context: McpToolContext): Promise<CallToolResult> {`);
|
|
376
381
|
if (destructure.length > 0) {
|
|
377
382
|
lines.push(` const { ${destructure.join(', ')} } = await parseAndValidate(args, ${argsConstName});`);
|
|
378
383
|
}
|
|
@@ -477,39 +482,31 @@ export function generateMcpAggregator(entries: McpAggregatorEntry[]): string {
|
|
|
477
482
|
lines.push(`import { McpToolHandlerMap } from '@maroonedsoftware/mcp';`);
|
|
478
483
|
for (const e of sorted) lines.push(`import { ${e.registerFn} } from '${e.importPath}';`);
|
|
479
484
|
lines.push('');
|
|
480
|
-
lines.push('/**
|
|
485
|
+
lines.push('/**');
|
|
486
|
+
lines.push(' * Build the MCP tool catalog.');
|
|
487
|
+
lines.push(' *');
|
|
488
|
+
lines.push(' * Bind it to the `McpToolHandlerMap` token from a factory, which is what supplies the');
|
|
489
|
+
lines.push(' * `Container` needed to resolve each handler:');
|
|
490
|
+
lines.push(' *');
|
|
491
|
+
lines.push(' * ```ts');
|
|
492
|
+
lines.push(' * registry.register(McpToolHandlerMap).useFactory(registerMcpTools).asSingleton();');
|
|
493
|
+
lines.push(' * ```');
|
|
494
|
+
lines.push(' */');
|
|
481
495
|
lines.push('export function registerMcpTools(container: Container): McpToolHandlerMap {');
|
|
482
496
|
lines.push(' const map = new McpToolHandlerMap();');
|
|
483
497
|
for (const e of sorted) lines.push(` ${e.registerFn}(map, container);`);
|
|
484
|
-
lines.push(' container.register(McpToolHandlerMap, { useValue: map });');
|
|
485
498
|
lines.push(' return map;');
|
|
486
499
|
lines.push('}');
|
|
487
500
|
return lines.join('\n') + '\n';
|
|
488
501
|
}
|
|
489
502
|
|
|
490
|
-
/**
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const dispatcher = ctx.container.get(McpDispatcher);
|
|
500
|
-
const context = createMcpRequestContext({ requestId: ctx.requestId, logger: ctx.logger });
|
|
501
|
-
if (dispatcher.sessionMode === 'stateful') {
|
|
502
|
-
ctx.respond = false;
|
|
503
|
-
await dispatcher.dispatchStateful(
|
|
504
|
-
{ req: ctx.req, res: ctx.res, body: ctx.parsedBody, sessionId: ctx.get('mcp-session-id') },
|
|
505
|
-
context,
|
|
506
|
-
);
|
|
507
|
-
} else {
|
|
508
|
-
const response = await dispatcher.dispatch(JSON.parse(String(ctx.rawBody)), context);
|
|
509
|
-
if (response) ctx.body = response;
|
|
510
|
-
else ctx.status = 202; // a notification — nothing to return
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
`;
|
|
503
|
+
/**
|
|
504
|
+
* Generate the optional `mcp.router.ts` — the standard ServerKit route wiring for the dispatcher.
|
|
505
|
+
*
|
|
506
|
+
* The whole file is framework-specific boilerplate rather than a per-operation render, so the
|
|
507
|
+
* adapter owns the template. Defaults to Koa, matching the router generator.
|
|
508
|
+
*/
|
|
509
|
+
export function generateMcpRouter(options: { path?: string; framework?: ServerFramework } = {}): string {
|
|
510
|
+
const framework = options.framework ?? KOA_SERVER_FRAMEWORK;
|
|
511
|
+
return framework.mcpRouter({ path: options.path ?? '/mcp' });
|
|
515
512
|
}
|