@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
package/dist/index.js
CHANGED
|
@@ -1453,6 +1453,110 @@ __name(pascalToDotCase, "pascalToDotCase");
|
|
|
1453
1453
|
// src/codegen-operation.ts
|
|
1454
1454
|
import { resolveModifiers, resolveSecurity, SECURITY_NONE, classifyContentType, emittedResponses, PATH_PARAM_RE_G, toIdentifier } from "@contractkit/core";
|
|
1455
1455
|
import { basename, dirname as dirname3, relative as relative3 } from "path";
|
|
1456
|
+
|
|
1457
|
+
// src/server-framework-koa.ts
|
|
1458
|
+
var KOA_RUNTIME_MODULE = "@maroonedsoftware/koa";
|
|
1459
|
+
var KOA_RUNTIME_SYMBOLS = [
|
|
1460
|
+
"ServerKitRouter",
|
|
1461
|
+
"bodyParserMiddleware",
|
|
1462
|
+
"requirePolicy",
|
|
1463
|
+
"requireSignature"
|
|
1464
|
+
];
|
|
1465
|
+
var KOA_SERVER_FRAMEWORK = {
|
|
1466
|
+
name: "koa",
|
|
1467
|
+
imports(uses) {
|
|
1468
|
+
const symbols = KOA_RUNTIME_SYMBOLS.filter(uses);
|
|
1469
|
+
return symbols.length > 0 ? [
|
|
1470
|
+
`import { ${symbols.join(", ")} } from '${KOA_RUNTIME_MODULE}';`
|
|
1471
|
+
] : [];
|
|
1472
|
+
},
|
|
1473
|
+
routerDeclaration(routerName) {
|
|
1474
|
+
return `export const ${routerName} = ServerKitRouter();`;
|
|
1475
|
+
},
|
|
1476
|
+
pathParam(identifier) {
|
|
1477
|
+
return `:${identifier}`;
|
|
1478
|
+
},
|
|
1479
|
+
routeOpen(routerName, method, path, middlewares) {
|
|
1480
|
+
const middlewareStr = middlewares.length > 0 ? `, ${middlewares.join(", ")},` : ",";
|
|
1481
|
+
return `${routerName}.${method}('${path}'${middlewareStr} async ctx => {`;
|
|
1482
|
+
},
|
|
1483
|
+
routeClose() {
|
|
1484
|
+
return [
|
|
1485
|
+
"});"
|
|
1486
|
+
];
|
|
1487
|
+
},
|
|
1488
|
+
middleware: {
|
|
1489
|
+
policy(args) {
|
|
1490
|
+
return `requirePolicy(${args})`;
|
|
1491
|
+
},
|
|
1492
|
+
bodyParser(tokensExpr) {
|
|
1493
|
+
return `bodyParserMiddleware([${tokensExpr}])`;
|
|
1494
|
+
},
|
|
1495
|
+
signature(args) {
|
|
1496
|
+
return `requireSignature(${args})`;
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
request: {
|
|
1500
|
+
params: "ctx.params",
|
|
1501
|
+
query: "ctx.query",
|
|
1502
|
+
headers: "ctx.headers",
|
|
1503
|
+
// Not `ctx.request.body`: the ServerKit body parser drains the stream and writes its result
|
|
1504
|
+
// here, and in Koa `ctx.body` is the *response* body.
|
|
1505
|
+
parsedBody: "ctx.parsedBody",
|
|
1506
|
+
// Koa strips the parameters off `Content-Type` for this accessor already.
|
|
1507
|
+
contentType: "ctx.request.type"
|
|
1508
|
+
},
|
|
1509
|
+
resolveService(className) {
|
|
1510
|
+
return `ctx.container.get(${className})`;
|
|
1511
|
+
},
|
|
1512
|
+
response: {
|
|
1513
|
+
status(expr) {
|
|
1514
|
+
return `ctx.status = ${expr};`;
|
|
1515
|
+
},
|
|
1516
|
+
header(name, valueExpr) {
|
|
1517
|
+
return `ctx.set('${name}', ${valueExpr});`;
|
|
1518
|
+
},
|
|
1519
|
+
type(expr) {
|
|
1520
|
+
return `ctx.type = ${expr};`;
|
|
1521
|
+
},
|
|
1522
|
+
send(bodyExpr) {
|
|
1523
|
+
return bodyExpr === void 0 ? [] : [
|
|
1524
|
+
`ctx.body = ${bodyExpr};`
|
|
1525
|
+
];
|
|
1526
|
+
},
|
|
1527
|
+
caseEnd() {
|
|
1528
|
+
return [
|
|
1529
|
+
"break;"
|
|
1530
|
+
];
|
|
1531
|
+
}
|
|
1532
|
+
},
|
|
1533
|
+
mcpRouter({ path }) {
|
|
1534
|
+
return `import { ServerKitRouter, bodyParserMiddleware, requireSignature } from '${KOA_RUNTIME_MODULE}';
|
|
1535
|
+
import { McpDispatcher, createMcpRequestContext, MCP_AUTH_POLICY } from '@maroonedsoftware/mcp';
|
|
1536
|
+
|
|
1537
|
+
/** Mount the MCP endpoint onto a ServerKit router. Bind \`registerMcpTools\` to the \`McpToolHandlerMap\` token. */
|
|
1538
|
+
export function mountMcp(router: ReturnType<typeof ServerKitRouter>): void {
|
|
1539
|
+
router.post('${path}', bodyParserMiddleware(['json']), requireSignature('mcp', { policy: MCP_AUTH_POLICY }), async (ctx) => {
|
|
1540
|
+
const dispatcher = ctx.container.get(McpDispatcher);
|
|
1541
|
+
const context = createMcpRequestContext({ requestId: ctx.requestId, logger: ctx.logger });
|
|
1542
|
+
if (dispatcher.sessionMode === 'stateful') {
|
|
1543
|
+
ctx.respond = false;
|
|
1544
|
+
await dispatcher.dispatchStateful(
|
|
1545
|
+
{ req: ctx.req, res: ctx.res, body: ctx.parsedBody, sessionId: ctx.get('mcp-session-id') },
|
|
1546
|
+
context,
|
|
1547
|
+
);
|
|
1548
|
+
} else {
|
|
1549
|
+
const response = await dispatcher.dispatch(JSON.parse(String(ctx.rawBody)), context);
|
|
1550
|
+
if (response) ctx.body = response;
|
|
1551
|
+
else ctx.status = 202; // a notification \u2014 nothing to return
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
`;
|
|
1556
|
+
}
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
// src/codegen-operation.ts
|
|
1456
1560
|
function bodyParserToken(contentType) {
|
|
1457
1561
|
switch (classifyContentType(contentType)) {
|
|
1458
1562
|
case "urlencoded":
|
|
@@ -1525,6 +1629,11 @@ function bodyTypesStructurallyEqual(a, b) {
|
|
|
1525
1629
|
}
|
|
1526
1630
|
__name(bodyTypesStructurallyEqual, "bodyTypesStructurallyEqual");
|
|
1527
1631
|
function generateOp(root, options = {}) {
|
|
1632
|
+
const resolved = {
|
|
1633
|
+
...options,
|
|
1634
|
+
framework: options.framework ?? KOA_SERVER_FRAMEWORK
|
|
1635
|
+
};
|
|
1636
|
+
const framework = resolved.framework;
|
|
1528
1637
|
const types = collectTypes(root, options.modelsWithInput, options.modelsWithOutput);
|
|
1529
1638
|
const services = collectServices(root);
|
|
1530
1639
|
const routerName = deriveRouterName(root.file);
|
|
@@ -1533,13 +1642,13 @@ function generateOp(root, options = {}) {
|
|
|
1533
1642
|
lines.push("/**");
|
|
1534
1643
|
lines.push(` * generated from ${sourceLink(basename(root.file), options.outPath, root.file)}`);
|
|
1535
1644
|
lines.push("*/");
|
|
1536
|
-
lines.push(
|
|
1645
|
+
lines.push(framework.routerDeclaration(routerName));
|
|
1537
1646
|
lines.push("");
|
|
1538
1647
|
const includeInternal = options.includeInternal ?? true;
|
|
1539
1648
|
for (const route of root.routes) {
|
|
1540
1649
|
for (const op of route.operations) {
|
|
1541
1650
|
if (!includeInternal && resolveModifiers(route, op).includes("internal")) continue;
|
|
1542
|
-
lines.push(...generateHandler(route, op, root,
|
|
1651
|
+
lines.push(...generateHandler(route, op, root, resolved));
|
|
1543
1652
|
lines.push("");
|
|
1544
1653
|
}
|
|
1545
1654
|
}
|
|
@@ -1574,15 +1683,7 @@ function generateOp(root, options = {}) {
|
|
|
1574
1683
|
].join("\n");
|
|
1575
1684
|
const uses = /* @__PURE__ */ __name((symbol) => new RegExp(`\\b${symbol}\\b`).test(generated), "uses");
|
|
1576
1685
|
const body = [];
|
|
1577
|
-
|
|
1578
|
-
"ServerKitRouter",
|
|
1579
|
-
"bodyParserMiddleware",
|
|
1580
|
-
"requirePolicy",
|
|
1581
|
-
"requireSignature"
|
|
1582
|
-
].filter(uses);
|
|
1583
|
-
if (koaImports.length > 0) {
|
|
1584
|
-
body.push(`import { ${koaImports.join(", ")} } from '@maroonedsoftware/koa';`);
|
|
1585
|
-
}
|
|
1686
|
+
body.push(...framework.imports(uses));
|
|
1586
1687
|
for (const svc of services.filter(uses)) {
|
|
1587
1688
|
const modulePath = root.services?.[svc] ?? root.meta[svc] ?? deriveModulePath(svc, options.servicePathTemplate);
|
|
1588
1689
|
body.push(`import { ${svc} } from '${modulePath}';`);
|
|
@@ -1626,6 +1727,7 @@ function generateHandler(route, op, root, options) {
|
|
|
1626
1727
|
const file = root.file;
|
|
1627
1728
|
const outPath = options.outPath;
|
|
1628
1729
|
const modelsWithInput = options.modelsWithInput;
|
|
1730
|
+
const framework = options.framework;
|
|
1629
1731
|
lines.push("/**");
|
|
1630
1732
|
const desc = op.description ?? route.description;
|
|
1631
1733
|
if (desc) {
|
|
@@ -1641,7 +1743,7 @@ function generateHandler(route, op, root, options) {
|
|
|
1641
1743
|
if (mods.includes("deprecated")) lines.push(` * @deprecated`);
|
|
1642
1744
|
lines.push("*/");
|
|
1643
1745
|
const method = op.method;
|
|
1644
|
-
const path = route.path.replace(PATH_PARAM_RE_G, (_m, name) =>
|
|
1746
|
+
const path = route.path.replace(PATH_PARAM_RE_G, (_m, name) => framework.pathParam(toIdentifier(name)));
|
|
1645
1747
|
const bodies = op.request?.bodies ?? [];
|
|
1646
1748
|
const hasBody = bodies.length > 0;
|
|
1647
1749
|
const isSingleMultipart = bodies.length === 1 && bodies[0].contentType === "multipart/form-data";
|
|
@@ -1649,42 +1751,41 @@ function generateHandler(route, op, root, options) {
|
|
|
1649
1751
|
if (effectiveSecurity !== SECURITY_NONE) {
|
|
1650
1752
|
const policy = effectiveSecurity?.policy;
|
|
1651
1753
|
const args = policy === void 0 ? "" : policy === false ? "{ policy: false }" : `{ policy: '${policy}' }`;
|
|
1652
|
-
middlewares.push(
|
|
1754
|
+
middlewares.push(framework.middleware.policy(args));
|
|
1653
1755
|
}
|
|
1654
1756
|
if (hasBody) {
|
|
1655
1757
|
const parserTokens = Array.from(new Set(bodies.map((b) => bodyParserToken(b.contentType))));
|
|
1656
1758
|
const tokensExpr = parserTokens.map((t) => `'${t}'`).join(", ");
|
|
1657
|
-
middlewares.push(
|
|
1759
|
+
middlewares.push(framework.middleware.bodyParser(tokensExpr));
|
|
1658
1760
|
}
|
|
1659
1761
|
if (op.signature) {
|
|
1660
1762
|
const sigArgs = op.signaturePolicy ? `'${escapeSingleQuoted(op.signature)}', { policy: '${escapeSingleQuoted(op.signaturePolicy)}' }` : `'${escapeSingleQuoted(op.signature)}'`;
|
|
1661
|
-
middlewares.push(
|
|
1763
|
+
middlewares.push(framework.middleware.signature(sigArgs));
|
|
1662
1764
|
}
|
|
1663
|
-
|
|
1664
|
-
lines.push(
|
|
1665
|
-
lines.push(...generateParamValidation(
|
|
1666
|
-
lines.push(...generateParamValidation(op.
|
|
1667
|
-
lines.push(...generateParamValidation(op.headers, "ctx.headers", "headers", op.headersMode ?? "strip", "", modelsWithInput));
|
|
1765
|
+
lines.push(framework.routeOpen(deriveRouterName(file), method, path, middlewares));
|
|
1766
|
+
lines.push(...generateParamValidation(route.params, "params", framework.request.params, route.paramsMode ?? "strict", "", modelsWithInput));
|
|
1767
|
+
lines.push(...generateParamValidation(op.query, "query", framework.request.query, op.queryMode ?? "strict", "", modelsWithInput));
|
|
1768
|
+
lines.push(...generateParamValidation(op.headers, "headers", framework.request.headers, op.headersMode ?? "strip", "", modelsWithInput));
|
|
1668
1769
|
if (hasBody && op.request) {
|
|
1669
1770
|
if (isSingleMultipart) {
|
|
1670
|
-
lines.push(` const multipartBody =
|
|
1771
|
+
lines.push(` const multipartBody = ${framework.request.parsedBody} as MultipartBody;`);
|
|
1671
1772
|
lines.push("");
|
|
1672
1773
|
} else if (bodies.length === 1) {
|
|
1673
|
-
lines.push(` const body = await parseAndValidate(
|
|
1774
|
+
lines.push(` const body = await parseAndValidate(${framework.request.parsedBody}, ${renderInputType(bodies[0].bodyType, modelsWithInput)});`);
|
|
1674
1775
|
lines.push("");
|
|
1675
1776
|
} else if (bodies.every((b) => bodyTypesStructurallyEqual(b.bodyType, bodies[0].bodyType))) {
|
|
1676
|
-
lines.push(` const body = await parseAndValidate(
|
|
1777
|
+
lines.push(` const body = await parseAndValidate(${framework.request.parsedBody}, ${renderInputType(bodies[0].bodyType, modelsWithInput)});`);
|
|
1677
1778
|
lines.push("");
|
|
1678
1779
|
} else {
|
|
1679
1780
|
const annotation = bodies.map((b) => b.contentType === "multipart/form-data" ? "MultipartBody" : `z.infer<typeof ${renderInputType(b.bodyType, modelsWithInput)}>`).join(" | ");
|
|
1680
1781
|
lines.push(` let body!: ${annotation};`);
|
|
1681
|
-
lines.push(` switch (
|
|
1782
|
+
lines.push(` switch (${framework.request.contentType}) {`);
|
|
1682
1783
|
for (const b of bodies) {
|
|
1683
1784
|
lines.push(` case '${b.contentType}':`);
|
|
1684
1785
|
if (b.contentType === "multipart/form-data") {
|
|
1685
|
-
lines.push(` body =
|
|
1786
|
+
lines.push(` body = ${framework.request.parsedBody} as MultipartBody;`);
|
|
1686
1787
|
} else {
|
|
1687
|
-
lines.push(` body = await parseAndValidate(
|
|
1788
|
+
lines.push(` body = await parseAndValidate(${framework.request.parsedBody}, ${renderInputType(b.bodyType, modelsWithInput)});`);
|
|
1688
1789
|
}
|
|
1689
1790
|
lines.push(` break;`);
|
|
1690
1791
|
}
|
|
@@ -1700,11 +1801,12 @@ function generateHandler(route, op, root, options) {
|
|
|
1700
1801
|
} else {
|
|
1701
1802
|
lines.push(...generateSingleStatusResult(emitted[0], op, serviceParts.className, call, options));
|
|
1702
1803
|
}
|
|
1703
|
-
lines.push(
|
|
1804
|
+
lines.push(...framework.routeClose());
|
|
1704
1805
|
return lines;
|
|
1705
1806
|
}
|
|
1706
1807
|
__name(generateHandler, "generateHandler");
|
|
1707
1808
|
function generateSingleStatusResult(resp, op, className, call, options) {
|
|
1809
|
+
const framework = options.framework;
|
|
1708
1810
|
const lines = [];
|
|
1709
1811
|
const bodies = resp ? resp.bodies : [];
|
|
1710
1812
|
const respHeaders = resp?.headers ?? [];
|
|
@@ -1715,7 +1817,7 @@ function generateSingleStatusResult(resp, op, className, call, options) {
|
|
|
1715
1817
|
const { annotation, prelude } = formatTypeAnnotation(bodies[0].bodyType, options.modelsWithOutput);
|
|
1716
1818
|
if (prelude) lines.push(` ${prelude}`);
|
|
1717
1819
|
bodySchema = responseBodySchema(bodies[0].bodyType, options, prelude ? "resultType" : void 0);
|
|
1718
|
-
lines.push(` const service =
|
|
1820
|
+
lines.push(` const service = ${framework.resolveService(className)};`);
|
|
1719
1821
|
if (hasRespHeaders) {
|
|
1720
1822
|
lines.push(` const result: { body: ${annotation}; headers: ${headersAnnotation} } = ${call};`);
|
|
1721
1823
|
} else {
|
|
@@ -1729,10 +1831,10 @@ function generateSingleStatusResult(resp, op, className, call, options) {
|
|
|
1729
1831
|
const { members, preludes } = rendered;
|
|
1730
1832
|
bodySchema = rendered.bodySchema;
|
|
1731
1833
|
for (const prelude of preludes) lines.push(` ${prelude}`);
|
|
1732
|
-
lines.push(` const service =
|
|
1834
|
+
lines.push(` const service = ${framework.resolveService(className)};`);
|
|
1733
1835
|
lines.push(` const result: ${members.join(" | ")} = ${call};`);
|
|
1734
1836
|
} else {
|
|
1735
|
-
lines.push(` const service =
|
|
1837
|
+
lines.push(` const service = ${framework.resolveService(className)};`);
|
|
1736
1838
|
if (hasRespHeaders) {
|
|
1737
1839
|
lines.push(` const result: { headers: ${headersAnnotation} } = ${call};`);
|
|
1738
1840
|
} else {
|
|
@@ -1740,19 +1842,22 @@ function generateSingleStatusResult(resp, op, className, call, options) {
|
|
|
1740
1842
|
}
|
|
1741
1843
|
}
|
|
1742
1844
|
lines.push("");
|
|
1743
|
-
lines.push(`
|
|
1744
|
-
lines.push(...headerSetLines(respHeaders, " "));
|
|
1845
|
+
lines.push(` ${framework.response.status(String(resp?.statusCode ?? 204))}`);
|
|
1846
|
+
lines.push(...headerSetLines(respHeaders, " ", framework));
|
|
1745
1847
|
if (bodies.length === 1) {
|
|
1746
|
-
lines.push(`
|
|
1747
|
-
lines.push(
|
|
1848
|
+
lines.push(` ${framework.response.type(`'${bodies[0].contentType}'`)}`);
|
|
1849
|
+
lines.push(...indent(framework.response.send(responseBodyExpr(hasRespHeaders ? "result.body" : "result", bodySchema)), " "));
|
|
1748
1850
|
} else if (bodies.length > 1) {
|
|
1749
|
-
lines.push(`
|
|
1750
|
-
lines.push(
|
|
1851
|
+
lines.push(` ${framework.response.type("result.contentType")}`);
|
|
1852
|
+
lines.push(...indent(framework.response.send(responseBodyExpr("result.body", bodySchema)), " "));
|
|
1853
|
+
} else {
|
|
1854
|
+
lines.push(...indent(framework.response.send(void 0), " "));
|
|
1751
1855
|
}
|
|
1752
1856
|
return lines;
|
|
1753
1857
|
}
|
|
1754
1858
|
__name(generateSingleStatusResult, "generateSingleStatusResult");
|
|
1755
1859
|
function generateMultiStatusResult(emitted, className, call, options) {
|
|
1860
|
+
const framework = options.framework;
|
|
1756
1861
|
const lines = [];
|
|
1757
1862
|
const members = [];
|
|
1758
1863
|
const preludes = [];
|
|
@@ -1767,21 +1872,23 @@ function generateMultiStatusResult(emitted, className, call, options) {
|
|
|
1767
1872
|
bodySchemas.set(resp.statusCode, rendered.bodySchema);
|
|
1768
1873
|
}
|
|
1769
1874
|
for (const prelude of preludes) lines.push(` ${prelude}`);
|
|
1770
|
-
lines.push(` const service =
|
|
1875
|
+
lines.push(` const service = ${framework.resolveService(className)};`);
|
|
1771
1876
|
lines.push(` const result:`);
|
|
1772
1877
|
for (const member of members) lines.push(` | ${member}`);
|
|
1773
1878
|
lines.push(` = ${call};`);
|
|
1774
1879
|
lines.push("");
|
|
1775
|
-
lines.push(`
|
|
1880
|
+
lines.push(` ${framework.response.status("result.status")}`);
|
|
1776
1881
|
lines.push(` switch (result.status) {`);
|
|
1777
1882
|
for (const resp of emitted) {
|
|
1778
1883
|
lines.push(` case ${resp.statusCode}:`);
|
|
1779
|
-
lines.push(...headerSetLines(resp.headers ?? [], " "));
|
|
1884
|
+
lines.push(...headerSetLines(resp.headers ?? [], " ", framework));
|
|
1780
1885
|
if (resp.bodies.length > 0) {
|
|
1781
|
-
lines.push(`
|
|
1782
|
-
lines.push(
|
|
1886
|
+
lines.push(` ${framework.response.type("result.contentType")}`);
|
|
1887
|
+
lines.push(...indent(framework.response.send(responseBodyExpr("result.body", bodySchemas.get(resp.statusCode))), " "));
|
|
1888
|
+
} else {
|
|
1889
|
+
lines.push(...indent(framework.response.send(void 0), " "));
|
|
1783
1890
|
}
|
|
1784
|
-
lines.push(
|
|
1891
|
+
lines.push(...indent(framework.response.caseEnd(), " "));
|
|
1785
1892
|
}
|
|
1786
1893
|
lines.push(` }`);
|
|
1787
1894
|
return lines;
|
|
@@ -1848,13 +1955,18 @@ function renderHeadersAnnotation(headers, modelsWithOutput) {
|
|
|
1848
1955
|
return `{ ${fields.join("; ")} }`;
|
|
1849
1956
|
}
|
|
1850
1957
|
__name(renderHeadersAnnotation, "renderHeadersAnnotation");
|
|
1851
|
-
function headerSetLines(headers,
|
|
1958
|
+
function headerSetLines(headers, pad, framework) {
|
|
1852
1959
|
return headers.map((h) => {
|
|
1853
1960
|
const accessor = `result.headers[${JSON.stringify(headerNameToProperty(h.name))}]`;
|
|
1854
|
-
|
|
1961
|
+
const write = framework.response.header(h.name, `String(${accessor})`);
|
|
1962
|
+
return h.optional ? `${pad}if (${accessor} !== undefined) ${write}` : `${pad}${write}`;
|
|
1855
1963
|
});
|
|
1856
1964
|
}
|
|
1857
1965
|
__name(headerSetLines, "headerSetLines");
|
|
1966
|
+
function indent(lines, pad) {
|
|
1967
|
+
return lines.map((line) => `${pad}${line}`);
|
|
1968
|
+
}
|
|
1969
|
+
__name(indent, "indent");
|
|
1858
1970
|
function inferService(op, route, file) {
|
|
1859
1971
|
if (op.service) {
|
|
1860
1972
|
const [cls = "", method] = op.service.split(".");
|
|
@@ -2014,21 +2126,21 @@ function responseBodyExpr(value, schema) {
|
|
|
2014
2126
|
return schema ? `await parseAndValidate(${value}, ${schema}, 500)` : value;
|
|
2015
2127
|
}
|
|
2016
2128
|
__name(responseBodyExpr, "responseBodyExpr");
|
|
2017
|
-
function generateParamValidation(source,
|
|
2129
|
+
function generateParamValidation(source, kind, sourceExpr, mode, suffix = "", modelsWithInput) {
|
|
2018
2130
|
if (!source) return [];
|
|
2019
2131
|
const lines = [];
|
|
2020
|
-
const isQuery =
|
|
2132
|
+
const isQuery = kind === "query";
|
|
2133
|
+
const isPathParams = kind === "params";
|
|
2021
2134
|
if (source.kind === "ref") {
|
|
2022
2135
|
const typeName = modelsWithInput?.has(source.name) ? `${source.name}Input` : source.name;
|
|
2023
|
-
lines.push(` const ${
|
|
2136
|
+
lines.push(` const ${kind} = await parseAndValidate(${sourceExpr}, ${typeName}.${mode}());`);
|
|
2024
2137
|
lines.push("");
|
|
2025
2138
|
} else if (source.kind === "params") {
|
|
2026
2139
|
if (source.nodes.length > 0) {
|
|
2027
|
-
const isPathParams = ctxExpr === "ctx.params";
|
|
2028
2140
|
const bind = /* @__PURE__ */ __name((name) => isPathParams ? toIdentifier(name) : name, "bind");
|
|
2029
|
-
const lhs =
|
|
2141
|
+
const lhs = isPathParams ? `{ ${source.nodes.map((p) => bind(p.name)).join(", ")} }` : kind;
|
|
2030
2142
|
lines.push(` const ${lhs} = await parseAndValidate(`);
|
|
2031
|
-
lines.push(` ${
|
|
2143
|
+
lines.push(` ${sourceExpr},`);
|
|
2032
2144
|
lines.push(` ${modeToWrapper(mode)}({`);
|
|
2033
2145
|
for (const param of source.nodes) {
|
|
2034
2146
|
const bound = bind(param.name);
|
|
@@ -2042,7 +2154,7 @@ function generateParamValidation(source, ctxExpr, varName, mode, suffix = "", mo
|
|
|
2042
2154
|
}
|
|
2043
2155
|
} else {
|
|
2044
2156
|
const schema = isQuery ? renderQueryType(source.node, modelsWithInput) : renderInputType(source.node, modelsWithInput);
|
|
2045
|
-
lines.push(` const ${
|
|
2157
|
+
lines.push(` const ${kind} = await parseAndValidate(${sourceExpr}, (${schema}).${mode}());`);
|
|
2046
2158
|
lines.push("");
|
|
2047
2159
|
}
|
|
2048
2160
|
return lines;
|
|
@@ -2896,7 +3008,7 @@ function sdkResponseMembers(resp, modelsWithOutput, includeStatus) {
|
|
|
2896
3008
|
].join("; ")} }`);
|
|
2897
3009
|
}
|
|
2898
3010
|
__name(sdkResponseMembers, "sdkResponseMembers");
|
|
2899
|
-
function sdkReturnLines(resp, modelsWithOutput,
|
|
3011
|
+
function sdkReturnLines(resp, modelsWithOutput, indent2, includeStatus, revive, where) {
|
|
2900
3012
|
const bodies = resp.bodies;
|
|
2901
3013
|
const headers = resp.headers ?? [];
|
|
2902
3014
|
const leading = includeStatus ? [
|
|
@@ -2907,7 +3019,7 @@ function sdkReturnLines(resp, modelsWithOutput, indent, includeStatus, revive, w
|
|
|
2907
3019
|
] : [];
|
|
2908
3020
|
if (bodies.length === 0) {
|
|
2909
3021
|
return [
|
|
2910
|
-
`${
|
|
3022
|
+
`${indent2}return { ${[
|
|
2911
3023
|
...leading,
|
|
2912
3024
|
...trailing
|
|
2913
3025
|
].join(", ")} };`
|
|
@@ -2921,7 +3033,7 @@ function sdkReturnLines(resp, modelsWithOutput, indent, includeStatus, revive, w
|
|
|
2921
3033
|
...trailing
|
|
2922
3034
|
];
|
|
2923
3035
|
return [
|
|
2924
|
-
`${
|
|
3036
|
+
`${indent2}return { ${fields.join(", ")} };`
|
|
2925
3037
|
];
|
|
2926
3038
|
}
|
|
2927
3039
|
const dataTypes = bodies.map((b) => sdkDataType(b, modelsWithOutput));
|
|
@@ -2934,11 +3046,11 @@ function sdkReturnLines(resp, modelsWithOutput, indent, includeStatus, revive, w
|
|
|
2934
3046
|
...trailing
|
|
2935
3047
|
];
|
|
2936
3048
|
return [
|
|
2937
|
-
`${
|
|
3049
|
+
`${indent2}return { ${fields.join(", ")} };`
|
|
2938
3050
|
];
|
|
2939
3051
|
}
|
|
2940
3052
|
const lines = [
|
|
2941
|
-
`${
|
|
3053
|
+
`${indent2}switch (readContentType(result)) {`
|
|
2942
3054
|
];
|
|
2943
3055
|
for (const [i, body] of bodies.slice(1).entries()) {
|
|
2944
3056
|
const fields = [
|
|
@@ -2947,8 +3059,8 @@ function sdkReturnLines(resp, modelsWithOutput, indent, includeStatus, revive, w
|
|
|
2947
3059
|
`data: ${sdkReadExpr(body, modelsWithOutput, hint(revive, `${resp.statusCode}_${i + 1}`))}`,
|
|
2948
3060
|
...trailing
|
|
2949
3061
|
];
|
|
2950
|
-
lines.push(`${
|
|
2951
|
-
lines.push(`${
|
|
3062
|
+
lines.push(`${indent2} case '${body.contentType}':`);
|
|
3063
|
+
lines.push(`${indent2} return { ${fields.join(", ")} };`);
|
|
2952
3064
|
}
|
|
2953
3065
|
const first = bodies[0];
|
|
2954
3066
|
const fallbackFields = [
|
|
@@ -2957,9 +3069,9 @@ function sdkReturnLines(resp, modelsWithOutput, indent, includeStatus, revive, w
|
|
|
2957
3069
|
`data: ${sdkReadExpr(first, modelsWithOutput, hint(revive, `${resp.statusCode}_0`))}`,
|
|
2958
3070
|
...trailing
|
|
2959
3071
|
];
|
|
2960
|
-
lines.push(`${
|
|
2961
|
-
lines.push(`${
|
|
2962
|
-
lines.push(`${
|
|
3072
|
+
lines.push(`${indent2} default:`);
|
|
3073
|
+
lines.push(`${indent2} return { ${fallbackFields.join(", ")} };`);
|
|
3074
|
+
lines.push(`${indent2}}`);
|
|
2963
3075
|
return lines;
|
|
2964
3076
|
}
|
|
2965
3077
|
__name(sdkReturnLines, "sdkReturnLines");
|
|
@@ -4103,6 +4215,24 @@ function renderOutputField(field, outputCase, modelsWithOutput, target) {
|
|
|
4103
4215
|
}
|
|
4104
4216
|
__name(renderOutputField, "renderOutputField");
|
|
4105
4217
|
|
|
4218
|
+
// src/server-framework.ts
|
|
4219
|
+
var SERVER_FRAMEWORK_NAMES = [
|
|
4220
|
+
"koa"
|
|
4221
|
+
];
|
|
4222
|
+
var DEFAULT_SERVER_FRAMEWORK_NAME = "koa";
|
|
4223
|
+
var SERVER_FRAMEWORKS = {
|
|
4224
|
+
koa: KOA_SERVER_FRAMEWORK
|
|
4225
|
+
};
|
|
4226
|
+
function resolveServerFramework(name) {
|
|
4227
|
+
const resolved = name ?? DEFAULT_SERVER_FRAMEWORK_NAME;
|
|
4228
|
+
const framework = SERVER_FRAMEWORKS[resolved];
|
|
4229
|
+
if (!framework) {
|
|
4230
|
+
throw new Error(`plugin-typescript: server.framework '${resolved}' is not supported \u2014 expected one of: ${SERVER_FRAMEWORK_NAMES.join(", ")}.`);
|
|
4231
|
+
}
|
|
4232
|
+
return framework;
|
|
4233
|
+
}
|
|
4234
|
+
__name(resolveServerFramework, "resolveServerFramework");
|
|
4235
|
+
|
|
4106
4236
|
// src/codegen-mcp.ts
|
|
4107
4237
|
import { resolveModifiers as resolveModifiers3, emittedResponses as emittedResponses2, toIdentifier as toIdentifier3 } from "@contractkit/core";
|
|
4108
4238
|
import { basename as basename3, dirname as dirname5, relative as relative5 } from "path";
|
|
@@ -4415,7 +4545,8 @@ function renderToolClass(plan, file, options) {
|
|
|
4415
4545
|
const callArgs = buildArgs(route, op);
|
|
4416
4546
|
const isVoid = !primaryResponseBody(op);
|
|
4417
4547
|
const structured = !!outExpr;
|
|
4418
|
-
|
|
4548
|
+
const argsParam = destructure.length > 0 ? "args" : "_args";
|
|
4549
|
+
lines.push(` async handle(${argsParam}: Record<string, unknown>, _context: McpToolContext): Promise<CallToolResult> {`);
|
|
4419
4550
|
if (destructure.length > 0) {
|
|
4420
4551
|
lines.push(` const { ${destructure.join(", ")} } = await parseAndValidate(args, ${argsConstName});`);
|
|
4421
4552
|
}
|
|
@@ -4503,40 +4634,29 @@ function generateMcpAggregator(entries) {
|
|
|
4503
4634
|
lines.push(`import { McpToolHandlerMap } from '@maroonedsoftware/mcp';`);
|
|
4504
4635
|
for (const e of sorted) lines.push(`import { ${e.registerFn} } from '${e.importPath}';`);
|
|
4505
4636
|
lines.push("");
|
|
4506
|
-
lines.push("/**
|
|
4637
|
+
lines.push("/**");
|
|
4638
|
+
lines.push(" * Build the MCP tool catalog.");
|
|
4639
|
+
lines.push(" *");
|
|
4640
|
+
lines.push(" * Bind it to the `McpToolHandlerMap` token from a factory, which is what supplies the");
|
|
4641
|
+
lines.push(" * `Container` needed to resolve each handler:");
|
|
4642
|
+
lines.push(" *");
|
|
4643
|
+
lines.push(" * ```ts");
|
|
4644
|
+
lines.push(" * registry.register(McpToolHandlerMap).useFactory(registerMcpTools).asSingleton();");
|
|
4645
|
+
lines.push(" * ```");
|
|
4646
|
+
lines.push(" */");
|
|
4507
4647
|
lines.push("export function registerMcpTools(container: Container): McpToolHandlerMap {");
|
|
4508
4648
|
lines.push(" const map = new McpToolHandlerMap();");
|
|
4509
4649
|
for (const e of sorted) lines.push(` ${e.registerFn}(map, container);`);
|
|
4510
|
-
lines.push(" container.register(McpToolHandlerMap, { useValue: map });");
|
|
4511
4650
|
lines.push(" return map;");
|
|
4512
4651
|
lines.push("}");
|
|
4513
4652
|
return lines.join("\n") + "\n";
|
|
4514
4653
|
}
|
|
4515
4654
|
__name(generateMcpAggregator, "generateMcpAggregator");
|
|
4516
4655
|
function generateMcpRouter(options = {}) {
|
|
4517
|
-
const
|
|
4518
|
-
return
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
/** Mount the MCP endpoint onto a ServerKit router. Call \`registerMcpTools(container)\` at startup. */
|
|
4522
|
-
export function mountMcp(router: ReturnType<typeof ServerKitRouter>): void {
|
|
4523
|
-
router.post('${path}', bodyParserMiddleware(['json']), requireSignature('mcp', { policy: MCP_AUTH_POLICY }), async (ctx) => {
|
|
4524
|
-
const dispatcher = ctx.container.get(McpDispatcher);
|
|
4525
|
-
const context = createMcpRequestContext({ requestId: ctx.requestId, logger: ctx.logger });
|
|
4526
|
-
if (dispatcher.sessionMode === 'stateful') {
|
|
4527
|
-
ctx.respond = false;
|
|
4528
|
-
await dispatcher.dispatchStateful(
|
|
4529
|
-
{ req: ctx.req, res: ctx.res, body: ctx.parsedBody, sessionId: ctx.get('mcp-session-id') },
|
|
4530
|
-
context,
|
|
4531
|
-
);
|
|
4532
|
-
} else {
|
|
4533
|
-
const response = await dispatcher.dispatch(JSON.parse(String(ctx.rawBody)), context);
|
|
4534
|
-
if (response) ctx.body = response;
|
|
4535
|
-
else ctx.status = 202; // a notification \u2014 nothing to return
|
|
4536
|
-
}
|
|
4537
|
-
});
|
|
4538
|
-
}
|
|
4539
|
-
`;
|
|
4656
|
+
const framework = options.framework ?? KOA_SERVER_FRAMEWORK;
|
|
4657
|
+
return framework.mcpRouter({
|
|
4658
|
+
path: options.path ?? "/mcp"
|
|
4659
|
+
});
|
|
4540
4660
|
}
|
|
4541
4661
|
__name(generateMcpRouter, "generateMcpRouter");
|
|
4542
4662
|
|
|
@@ -4770,6 +4890,10 @@ function createTypescriptPlugin(config, rootDir) {
|
|
|
4770
4890
|
}
|
|
4771
4891
|
__name(createTypescriptPlugin, "createTypescriptPlugin");
|
|
4772
4892
|
function assertValidConfig(config) {
|
|
4893
|
+
const framework = config.server?.framework;
|
|
4894
|
+
if (framework !== void 0 && !SERVER_FRAMEWORK_NAMES.includes(framework)) {
|
|
4895
|
+
throw new Error(`plugin-typescript: server.framework '${String(framework)}' is not supported \u2014 expected one of: ${SERVER_FRAMEWORK_NAMES.join(", ")}.`);
|
|
4896
|
+
}
|
|
4773
4897
|
if (config.server?.validateResponses && !config.server.zod) {
|
|
4774
4898
|
throw new Error("plugin-typescript: server.validateResponses requires server.zod: true \u2014 without it output.types emits plain TypeScript interfaces, which are types with no runtime schema value for the router to validate against.");
|
|
4775
4899
|
}
|
|
@@ -4899,6 +5023,7 @@ function sliceModelSet(refs, ownNames, set) {
|
|
|
4899
5023
|
__name(sliceModelSet, "sliceModelSet");
|
|
4900
5024
|
function collectServerOutput(config, rootDir, inputs, units) {
|
|
4901
5025
|
const serverBase = resolve2(rootDir, config.baseDir ?? ".");
|
|
5026
|
+
const framework = resolveServerFramework(config.framework);
|
|
4902
5027
|
const modelsWithInput = inputs.modelsWithInput;
|
|
4903
5028
|
const modelsWithOutput = inputs.modelsWithOutput;
|
|
4904
5029
|
const modelsWithTransform = computeModelsWithCaseTransform(inputs.contractRoots.flatMap((r) => r.models));
|
|
@@ -4947,7 +5072,7 @@ function collectServerOutput(config, rootDir, inputs, units) {
|
|
|
4947
5072
|
currentOutPath: typeOutPath,
|
|
4948
5073
|
modelsWithInput,
|
|
4949
5074
|
modelsWithOutput,
|
|
4950
|
-
// These types are consumed by
|
|
5075
|
+
// These types are consumed by server handlers, so `binary` is a Buffer, not a Blob.
|
|
4951
5076
|
target: "server"
|
|
4952
5077
|
};
|
|
4953
5078
|
const content = config.zod ? generateContract(ast, renderCtx) : generatePlainTypes(ast, renderCtx);
|
|
@@ -4978,6 +5103,9 @@ function collectServerOutput(config, rootDir, inputs, units) {
|
|
|
4978
5103
|
// this router's output with no change to `root` or the config.
|
|
4979
5104
|
modelsWithTransform: sliceModelSet(refs, /* @__PURE__ */ new Set(), modelsWithTransform),
|
|
4980
5105
|
validateResponses: config.validateResponses ?? false,
|
|
5106
|
+
// Covered by `sub` already, which is the whole sub-config; explicit for the same reason
|
|
5107
|
+
// `validateResponses` is — the inputs that change a router's text read at a glance.
|
|
5108
|
+
framework: framework.name,
|
|
4981
5109
|
sub: subConfigKey
|
|
4982
5110
|
});
|
|
4983
5111
|
units.push({
|
|
@@ -4994,7 +5122,8 @@ function collectServerOutput(config, rootDir, inputs, units) {
|
|
|
4994
5122
|
modelsWithOutput,
|
|
4995
5123
|
modelsWithTransform,
|
|
4996
5124
|
includeInternal: config.includeInternal,
|
|
4997
|
-
validateResponses: config.validateResponses
|
|
5125
|
+
validateResponses: config.validateResponses,
|
|
5126
|
+
framework
|
|
4998
5127
|
})
|
|
4999
5128
|
}
|
|
5000
5129
|
], "render")
|
|
@@ -5616,10 +5745,12 @@ function collectMcpOutput(config, fullConfig, rootDir, inputs, units, globalFile
|
|
|
5616
5745
|
});
|
|
5617
5746
|
if (config.emitRouter !== false) {
|
|
5618
5747
|
const routerPath = join2(mcpBase, config.output?.router ?? "mcp.router.ts");
|
|
5748
|
+
const framework = resolveServerFramework(fullConfig.server?.framework);
|
|
5619
5749
|
globalFiles.push({
|
|
5620
5750
|
relativePath: routerPath,
|
|
5621
5751
|
content: generateMcpRouter({
|
|
5622
|
-
path: config.path
|
|
5752
|
+
path: config.path,
|
|
5753
|
+
framework
|
|
5623
5754
|
})
|
|
5624
5755
|
});
|
|
5625
5756
|
}
|
|
@@ -5677,8 +5808,13 @@ function stableSubConfig(config) {
|
|
|
5677
5808
|
}
|
|
5678
5809
|
__name(stableSubConfig, "stableSubConfig");
|
|
5679
5810
|
export {
|
|
5811
|
+
DEFAULT_SERVER_FRAMEWORK_NAME,
|
|
5812
|
+
KOA_SERVER_FRAMEWORK,
|
|
5813
|
+
SERVER_FRAMEWORKS,
|
|
5814
|
+
SERVER_FRAMEWORK_NAMES,
|
|
5680
5815
|
TYPESCRIPT_CODEGEN_VERSION,
|
|
5681
5816
|
createTypescriptPlugin,
|
|
5682
|
-
index_default as default
|
|
5817
|
+
index_default as default,
|
|
5818
|
+
resolveServerFramework
|
|
5683
5819
|
};
|
|
5684
5820
|
//# sourceMappingURL=index.js.map
|