@daloyjs/core 1.0.0-beta.5 → 1.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -509,7 +509,7 @@ The core only ever sees `Request → Response`. Adapters live at the edge.
509
509
 
510
510
  ## Status
511
511
 
512
- DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.5`). The public API is feature-complete and stable for the 1.0 line; from `1.0.0` onward, breaking changes follow SemVer and deprecations get at least one minor cycle. Small adjustments are still possible before the `1.0.0` GA if beta feedback surfaces something. The framework is already in use for production trials.
512
+ DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.6`). The public API is feature-complete and stable for the 1.0 line; from `1.0.0` onward, breaking changes follow SemVer and deprecations get at least one minor cycle. Small adjustments are still possible before the `1.0.0` GA if beta feedback surfaces something. The framework is already in use for production trials.
513
513
 
514
514
  **Release quality bar.** Every release ships with **≥90% line + function coverage and ≥90% branch coverage**, strict TypeScript, OpenSSF Scorecard, CodeQL + Opengrep dual SAST, zizmor workflow linting, and npm provenance. Coverage was relaxed from a former 100% gate so complex security work isn't blocked chasing throwaway tests for unreachable defensive branches or tsx source-map phantoms; see [AGENTS.md](AGENTS.md) for the policy.
515
515
 
@@ -520,6 +520,7 @@ DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.5`). The public API is featu
520
520
  - Zero-config OpenAPI `info` autofill from `package.json` (Node / Bun) or `deno.json` / `deno.jsonc` (Deno); explicit `openapi.info` values always win.
521
521
  - RFC 7231 + RFC 5789 HTTP-method allowlist enforced inside `app.route()` (WebDAV, `TRACE`, `CONNECT` rejected at the framework boundary).
522
522
  - AI-friendly route metadata via optional `meta: { examples, extensions, summary, description, tags }`; examples are validated against your schemas at build time, surfaced as OpenAPI `examples` + `x-daloy-*` extensions, and dumped as `routes.json` / `routes.yaml` via `daloy inspect --ai`.
523
+ - Dependency-free MCP Streamable HTTP server helpers at `@daloyjs/core/mcp`: `createMcpHandler()` exposes tools, resources, and prompts over JSON-RPC 2.0, while `mcpRoutes("/mcp", handler)` mounts the POST / GET / OPTIONS Daloy routes for a dedicated MCP service with the same auth, rate-limit, body-limit, and timeout middleware as any other app.
523
524
  - API lifecycle and breaking-change detection: mark routes `deprecated` or give them a `sunset` date to emit RFC 8594 `Deprecation` / `Sunset` headers and an `x-sunset` OpenAPI extension, then gate CI with `diffOpenAPI()` / the `daloy diff` command, which fail on a breaking change versus the last published spec.
524
525
  - In-process test client (`app.request()`), contract-test runner (gated in CI via `daloy inspect --check` and shipped as a default test in every `create-daloy` template), in-process typed client, and Hey API codegen via `pnpm gen`.
525
526
 
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export type { StandardSchemaV1 } from "./schema.js";
19
19
  export { validate, isStandardSchema } from "./schema.js";
20
20
  export { diffOpenAPI, hasBreakingChanges } from "./openapi-diff.js";
21
21
  export type { ChangeSeverity, OpenAPIChange, OpenAPIDiffResult } from "./openapi-diff.js";
22
+ export { MCP_DEFAULT_MAX_BODY_BYTES, MCP_PROTOCOL_VERSION, MCP_PROTOCOL_VERSIONS, McpToolError, createMcpHandler, mcpRoutes, } from "./mcp.js";
23
+ export type { McpContent, McpEmbeddedResourceContent, McpHandler, McpHandlerOptions, McpImageContent, McpJsonObject, McpJsonRpcId, McpJsonSchema, McpJsonValue, McpPrompt, McpPromptArgument, McpPromptDefinition, McpPromptMessage, McpPromptResult, McpRequestContext, McpResource, McpResourceContents, McpResourceDefinition, McpServerInfo, McpTextContent, McpTool, McpToolHandler, McpToolResult, } from "./mcp.js";
22
24
  export { readBodyLimited, safeJsonParse, isForbiddenObjectKey, sanitizeHeaderName, sanitizeHeaderValue, timingSafeEqual, randomId, assertNoDuplicateSingletonHeaders, assertNoReservedInternalHeaders, assertHeaderCountWithinLimit, DEFAULT_MAX_HEADER_COUNT, RESERVED_INBOUND_HEADER_PREFIXES, SMUGGLING_SINGLETON_HEADERS, verifyWebhookSignature, signWebhookPayload, WEBHOOK_DEFAULT_TOLERANCE_SECONDS, assertStrongSecret, MIN_PROD_SECRET_BYTES, WEAK_SECRET_STRINGS, sanitizeFilename, assertSafeRelativePath, hasMongoOperatorKeys, assertNoMongoOperators, } from "./security.js";
23
25
  export type { WebhookHmacAlgorithm } from "./security.js";
24
26
  export { requestId, secureHeaders, SECURE_HEADERS_MARKER, cors, CORS_HOOK_MARKER, CORS_ORIGIN_ALLOW_MARKER, CORS_WILDCARD_ORIGIN_MARKER, rateLimit, loginThrottle, timing, bearerAuth, basicAuth, csrf, CSRF_HOOK_MARKER, fetchMetadata, requireScopes, REQUIRE_SCOPES_AGGREGATE_KEY, REQUIRE_SCOPES_HOOK_MARKER, _resetSharedRateLimitStoresForTests, } from "./middleware.js";
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ export { defineDependency, DEPENDENCY_MARKER } from "./dependency.js";
11
11
  export { HttpError, BadRequestError, ValidationError, NotFoundError, ConflictError, UnauthorizedError, ForbiddenError, MethodNotAllowedError, PayloadTooLargeError, RequestHeaderFieldsTooLargeError, UnsupportedMediaTypeError, TooManyRequestsError, RequestTimeoutError, InternalError, MessageLeakError, httpError, SAFE_CUSTOM_ERROR_RESPONSE_HEADERS, checkCustomErrorResponseHeaders, } from "./errors.js";
12
12
  export { validate, isStandardSchema } from "./schema.js";
13
13
  export { diffOpenAPI, hasBreakingChanges } from "./openapi-diff.js";
14
+ export { MCP_DEFAULT_MAX_BODY_BYTES, MCP_PROTOCOL_VERSION, MCP_PROTOCOL_VERSIONS, McpToolError, createMcpHandler, mcpRoutes, } from "./mcp.js";
14
15
  export { readBodyLimited, safeJsonParse, isForbiddenObjectKey, sanitizeHeaderName, sanitizeHeaderValue, timingSafeEqual, randomId, assertNoDuplicateSingletonHeaders, assertNoReservedInternalHeaders, assertHeaderCountWithinLimit, DEFAULT_MAX_HEADER_COUNT, RESERVED_INBOUND_HEADER_PREFIXES, SMUGGLING_SINGLETON_HEADERS, verifyWebhookSignature, signWebhookPayload, WEBHOOK_DEFAULT_TOLERANCE_SECONDS, assertStrongSecret, MIN_PROD_SECRET_BYTES, WEAK_SECRET_STRINGS, sanitizeFilename, assertSafeRelativePath, hasMongoOperatorKeys, assertNoMongoOperators, } from "./security.js";
15
16
  export { requestId, secureHeaders, SECURE_HEADERS_MARKER, cors, CORS_HOOK_MARKER, CORS_ORIGIN_ALLOW_MARKER, CORS_WILDCARD_ORIGIN_MARKER, rateLimit, loginThrottle, timing, bearerAuth, basicAuth, csrf, CSRF_HOOK_MARKER, fetchMetadata, requireScopes, REQUIRE_SCOPES_AGGREGATE_KEY, REQUIRE_SCOPES_HOOK_MARKER, _resetSharedRateLimitStoresForTests, } from "./middleware.js";
16
17
  export { etag } from "./etag.js";
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,432 @@
1
+ import type { PathString, RouteDefinition } from "./types.js";
2
+ /**
3
+ * Latest MCP protocol version DaloyJS negotiates by default.
4
+ *
5
+ * @see https://modelcontextprotocol.io/specification/2025-11-25
6
+ * @since 1.0.0
7
+ */
8
+ export declare const MCP_PROTOCOL_VERSION = "2025-11-25";
9
+ /**
10
+ * Protocol revisions accepted by {@link createMcpHandler} unless the caller
11
+ * provides an explicit `protocolVersions` list.
12
+ *
13
+ * @since 1.0.0
14
+ */
15
+ export declare const MCP_PROTOCOL_VERSIONS: readonly string[];
16
+ /**
17
+ * Default maximum accepted JSON-RPC request body for a DaloyJS MCP endpoint.
18
+ * The cap is intentionally small because MCP calls should carry parameters,
19
+ * not bulk uploads. Raise it per endpoint when a real tool needs larger input.
20
+ *
21
+ * @since 1.0.0
22
+ */
23
+ export declare const MCP_DEFAULT_MAX_BODY_BYTES: number;
24
+ /**
25
+ * JSON value accepted in MCP schemas, structured results, and metadata.
26
+ *
27
+ * @since 1.0.0
28
+ */
29
+ export type McpJsonValue = null | boolean | number | string | McpJsonValue[] | {
30
+ [key: string]: McpJsonValue;
31
+ };
32
+ /**
33
+ * JSON object used for MCP tool arguments and structured payloads.
34
+ *
35
+ * @since 1.0.0
36
+ */
37
+ export type McpJsonObject = {
38
+ [key: string]: McpJsonValue;
39
+ };
40
+ /**
41
+ * JSON Schema fragment advertised to MCP clients for a tool or prompt
42
+ * argument object. DaloyJS does not bundle a schema validator here, so the
43
+ * schema is documentation and client-side guidance. Validate sensitive inputs
44
+ * inside your handler before touching databases, files, or remote services.
45
+ *
46
+ * @since 1.0.0
47
+ */
48
+ export type McpJsonSchema = McpJsonObject;
49
+ /**
50
+ * JSON-RPC id type accepted by MCP requests.
51
+ *
52
+ * @since 1.0.0
53
+ */
54
+ export type McpJsonRpcId = string | number | null;
55
+ /**
56
+ * Identity block returned from the MCP `initialize` handshake.
57
+ *
58
+ * @since 1.0.0
59
+ */
60
+ export interface McpServerInfo {
61
+ /** Stable machine-readable server name, for example `"acme-inventory-mcp"`. */
62
+ name: string;
63
+ /** Optional human-readable display title for MCP clients. */
64
+ title?: string;
65
+ /** Server version surfaced to clients for debugging and compatibility. */
66
+ version: string;
67
+ }
68
+ /**
69
+ * Per-request context passed to tool, resource, and prompt handlers.
70
+ *
71
+ * @since 1.0.0
72
+ */
73
+ export interface McpRequestContext {
74
+ /** The original HTTP request received by the DaloyJS route. */
75
+ request: Request;
76
+ /**
77
+ * Protocol version selected for this call. Before `initialize`, this is the
78
+ * version from the `MCP-Protocol-Version` header when present, otherwise the
79
+ * handler's preferred protocol version.
80
+ */
81
+ protocolVersion: string;
82
+ /** JSON-RPC id for request/response correlation. */
83
+ id: McpJsonRpcId;
84
+ /** Raw MCP method name, such as `"tools/call"` or `"resources/read"`. */
85
+ method: string;
86
+ }
87
+ /**
88
+ * Text content block returned from an MCP tool, resource, or prompt.
89
+ *
90
+ * @since 1.0.0
91
+ */
92
+ export interface McpTextContent {
93
+ type: "text";
94
+ text: string;
95
+ }
96
+ /**
97
+ * Image content block returned from an MCP tool.
98
+ *
99
+ * `data` is base64-encoded image bytes. Keep images small; for large assets,
100
+ * return a resource link or URL-bearing text instead.
101
+ *
102
+ * @since 1.0.0
103
+ */
104
+ export interface McpImageContent {
105
+ type: "image";
106
+ data: string;
107
+ mimeType: string;
108
+ }
109
+ /**
110
+ * Embedded resource content block returned from an MCP tool.
111
+ *
112
+ * @since 1.0.0
113
+ */
114
+ export interface McpEmbeddedResourceContent {
115
+ type: "resource";
116
+ resource: McpResourceContents;
117
+ }
118
+ /**
119
+ * Content block supported by the dependency-free MCP helper.
120
+ *
121
+ * @since 1.0.0
122
+ */
123
+ export type McpContent = McpTextContent | McpImageContent | McpEmbeddedResourceContent;
124
+ /**
125
+ * Result returned by an MCP tool handler.
126
+ *
127
+ * `isError` marks caller-correctable tool failures, such as invalid input or a
128
+ * domain error. Unexpected thrown errors become JSON-RPC internal errors and
129
+ * are redacted in production.
130
+ *
131
+ * @since 1.0.0
132
+ */
133
+ export interface McpToolResult {
134
+ /** Human or model-readable content blocks returned to the MCP client. */
135
+ content: McpContent[];
136
+ /** Optional structured payload for clients that can consume typed output. */
137
+ structuredContent?: McpJsonObject;
138
+ /** Set to `true` for domain/tool errors the model may recover from. */
139
+ isError?: boolean;
140
+ }
141
+ /**
142
+ * Handler for a single MCP tool.
143
+ *
144
+ * @typeParam TArgs - Type expected in `params.arguments` for this tool.
145
+ * @param args - Tool arguments supplied by the MCP client. They are typed for
146
+ * developer experience but are still untrusted JSON at runtime.
147
+ * @param ctx - Request metadata and the original HTTP request.
148
+ * @returns Text shorthand or a full {@link McpToolResult}.
149
+ * @throws {McpToolError} for caller-correctable failures that should be
150
+ * returned as an MCP tool error result.
151
+ *
152
+ * @since 1.0.0
153
+ */
154
+ export type McpToolHandler<TArgs extends Record<string, unknown> = Record<string, unknown>> = (args: TArgs, ctx: McpRequestContext) => string | McpToolResult | Promise<string | McpToolResult>;
155
+ /**
156
+ * Definition of a callable MCP tool.
157
+ *
158
+ * Tools are model-controlled in MCP: clients may let the language model decide
159
+ * when to call them. Treat every tool as a public API operation and enforce
160
+ * authentication, authorization, rate limits, and validation before side
161
+ * effects.
162
+ *
163
+ * @typeParam TArgs - Type expected by this tool's handler.
164
+ * @since 1.0.0
165
+ */
166
+ export interface McpTool<TArgs extends Record<string, unknown> = Record<string, unknown>> {
167
+ /** Unique tool name within this MCP server. Prefer namespaced verbs. */
168
+ name: string;
169
+ /** Optional human-readable title displayed by clients. */
170
+ title?: string;
171
+ /** Clear description of when the model should use this tool. */
172
+ description: string;
173
+ /** JSON Schema for `params.arguments`. */
174
+ inputSchema: McpJsonSchema;
175
+ /** Execute the tool with untrusted JSON arguments. */
176
+ handler: McpToolHandler<TArgs>;
177
+ }
178
+ /**
179
+ * Resource metadata returned from `resources/list`.
180
+ *
181
+ * @since 1.0.0
182
+ */
183
+ export interface McpResource {
184
+ /** Unique resource URI, for example `"daloy://schema/inventory"`. */
185
+ uri: string;
186
+ /** Stable resource name. */
187
+ name: string;
188
+ /** Optional human-readable title. */
189
+ title?: string;
190
+ /** Optional description shown by clients. */
191
+ description?: string;
192
+ /** MIME type returned by `resources/read`, such as `"application/json"`. */
193
+ mimeType?: string;
194
+ }
195
+ /**
196
+ * Resource payload returned from `resources/read`.
197
+ *
198
+ * Use either `text` for UTF-8 content or `blob` for base64-encoded binary
199
+ * content. Set `mimeType` so clients know how to present the resource.
200
+ *
201
+ * @since 1.0.0
202
+ */
203
+ export interface McpResourceContents {
204
+ /** URI of the resource being returned. */
205
+ uri: string;
206
+ /** MIME type of the returned content. */
207
+ mimeType?: string;
208
+ /** UTF-8 text content. */
209
+ text?: string;
210
+ /** Base64-encoded binary content. */
211
+ blob?: string;
212
+ }
213
+ /**
214
+ * Definition of a readable MCP resource.
215
+ *
216
+ * Resources are application-controlled context. They are a good fit for
217
+ * schemas, read-only records, catalogs, runbooks, and other context a client
218
+ * can choose to include before a tool call.
219
+ *
220
+ * @since 1.0.0
221
+ */
222
+ export interface McpResourceDefinition extends McpResource {
223
+ /**
224
+ * Read the resource contents for `resources/read`.
225
+ *
226
+ * @param ctx - Request metadata and the original HTTP request.
227
+ * @returns One or more content entries for this resource.
228
+ */
229
+ read: (ctx: McpRequestContext) => McpResourceContents | McpResourceContents[] | Promise<McpResourceContents | McpResourceContents[]>;
230
+ }
231
+ /**
232
+ * Argument metadata for an MCP prompt.
233
+ *
234
+ * @since 1.0.0
235
+ */
236
+ export interface McpPromptArgument {
237
+ /** Argument name accepted by the prompt. */
238
+ name: string;
239
+ /** Optional description displayed by clients. */
240
+ description?: string;
241
+ /** Whether the argument is required. */
242
+ required?: boolean;
243
+ }
244
+ /**
245
+ * Prompt metadata returned from `prompts/list`.
246
+ *
247
+ * @since 1.0.0
248
+ */
249
+ export interface McpPrompt {
250
+ /** Unique prompt name within this MCP server. */
251
+ name: string;
252
+ /** Optional human-readable title. */
253
+ title?: string;
254
+ /** Optional prompt description. */
255
+ description?: string;
256
+ /** Prompt arguments clients may supply to `prompts/get`. */
257
+ arguments?: McpPromptArgument[];
258
+ }
259
+ /**
260
+ * Message returned from `prompts/get`.
261
+ *
262
+ * @since 1.0.0
263
+ */
264
+ export interface McpPromptMessage {
265
+ /** Role that should receive the prompt content. */
266
+ role: "user" | "assistant";
267
+ /** Prompt content block. */
268
+ content: McpTextContent | McpImageContent | McpEmbeddedResourceContent;
269
+ }
270
+ /**
271
+ * Result returned by an MCP prompt handler.
272
+ *
273
+ * @since 1.0.0
274
+ */
275
+ export interface McpPromptResult {
276
+ /** Optional description of the rendered prompt. */
277
+ description?: string;
278
+ /** Messages the client can inject into the model conversation. */
279
+ messages: McpPromptMessage[];
280
+ }
281
+ /**
282
+ * Definition of a reusable MCP prompt.
283
+ *
284
+ * @since 1.0.0
285
+ */
286
+ export interface McpPromptDefinition extends McpPrompt {
287
+ /**
288
+ * Render the prompt for `prompts/get`.
289
+ *
290
+ * @param args - Prompt arguments supplied by the MCP client.
291
+ * @param ctx - Request metadata and the original HTTP request.
292
+ * @returns Prompt messages.
293
+ */
294
+ get: (args: Record<string, unknown>, ctx: McpRequestContext) => McpPromptResult | Promise<McpPromptResult>;
295
+ }
296
+ /**
297
+ * Caller-correctable MCP tool/resource/prompt error.
298
+ *
299
+ * Throw this when the model supplied bad arguments, referenced a missing
300
+ * domain object, or otherwise made a recoverable call. Tool errors become
301
+ * `{ isError: true }` tool results; resource and prompt errors become
302
+ * JSON-RPC invalid-params errors. Unexpected errors are treated as internal
303
+ * server failures and are redacted in production.
304
+ *
305
+ * @since 1.0.0
306
+ */
307
+ export declare class McpToolError extends Error {
308
+ /**
309
+ * Create a recoverable MCP handler error.
310
+ *
311
+ * @param message - Safe, caller-visible explanation.
312
+ */
313
+ constructor(message: string);
314
+ }
315
+ /**
316
+ * Options for {@link createMcpHandler}.
317
+ *
318
+ * @since 1.0.0
319
+ */
320
+ export interface McpHandlerOptions {
321
+ /** Server identity returned from the `initialize` handshake. */
322
+ serverInfo: McpServerInfo;
323
+ /** Optional guidance returned from `initialize`. */
324
+ instructions?: string;
325
+ /** Callable tools exposed through `tools/list` and `tools/call`. */
326
+ tools?: readonly McpTool[];
327
+ /** Readable resources exposed through `resources/list` and `resources/read`. */
328
+ resources?: readonly McpResourceDefinition[];
329
+ /** Reusable prompts exposed through `prompts/list` and `prompts/get`. */
330
+ prompts?: readonly McpPromptDefinition[];
331
+ /** Accepted MCP protocol versions. Defaults to {@link MCP_PROTOCOL_VERSIONS}. */
332
+ protocolVersions?: readonly string[];
333
+ /**
334
+ * Protocol version returned when the client asks for an unsupported version.
335
+ * Defaults to {@link MCP_PROTOCOL_VERSION}.
336
+ */
337
+ preferredProtocolVersion?: string;
338
+ /** Maximum accepted JSON-RPC body size in bytes. Defaults to 256 KiB. */
339
+ maxBodyBytes?: number;
340
+ /**
341
+ * Extra headers added to every JSON response. Use this for endpoint-local
342
+ * cache, CORS, or deployment metadata. Authentication should usually live in
343
+ * DaloyJS middleware before the MCP route.
344
+ */
345
+ headers?: Record<string, string>;
346
+ /**
347
+ * Include development error details in JSON-RPC internal errors. Defaults to
348
+ * `process.env.NODE_ENV !== "production"` when `process` exists.
349
+ */
350
+ exposeInternalErrors?: boolean;
351
+ }
352
+ /**
353
+ * Fetch-compatible handler returned by {@link createMcpHandler}.
354
+ *
355
+ * @param request - Incoming HTTP request for the MCP endpoint.
356
+ * @returns A standard `Response` containing a JSON-RPC response, `202` for
357
+ * accepted notifications, or `405` for unsupported HTTP methods.
358
+ *
359
+ * @since 1.0.0
360
+ */
361
+ export type McpHandler = (request: Request) => Promise<Response>;
362
+ /**
363
+ * Create a dependency-free MCP Streamable HTTP endpoint handler.
364
+ *
365
+ * The handler implements the server side of MCP over one HTTP endpoint:
366
+ * `initialize`, `ping`, `tools/list`, `tools/call`, `resources/list`,
367
+ * `resources/read`, `prompts/list`, and `prompts/get`. It accepts JSON-RPC
368
+ * requests over `POST`, acknowledges notifications with `202`, validates the
369
+ * `MCP-Protocol-Version` header, bounds request bodies, and returns JSON-RPC
370
+ * errors for malformed input.
371
+ *
372
+ * It intentionally does not spawn stdio servers, manage OAuth metadata, keep
373
+ * durable sessions, or open server-initiated SSE streams. Use DaloyJS
374
+ * middleware for authentication and authorization, and run this on a dedicated
375
+ * Daloy app when your MCP server has a different trust boundary than your REST
376
+ * API.
377
+ *
378
+ * @param options - Server identity, capabilities, limits, and response headers.
379
+ * @returns A Fetch-compatible request handler suitable for {@link mcpRoutes}
380
+ * or for direct use in any web-standard runtime.
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * const mcp = createMcpHandler({
385
+ * serverInfo: { name: "inventory-mcp", version: "1.0.0" },
386
+ * tools: [
387
+ * {
388
+ * name: "inventory_lookup",
389
+ * description: "Look up inventory by SKU.",
390
+ * inputSchema: {
391
+ * type: "object",
392
+ * properties: { sku: { type: "string" } },
393
+ * required: ["sku"],
394
+ * additionalProperties: false,
395
+ * },
396
+ * handler: async ({ sku }) => `SKU ${sku} has 42 units.`,
397
+ * },
398
+ * ],
399
+ * });
400
+ * ```
401
+ *
402
+ * @since 1.0.0
403
+ */
404
+ export declare function createMcpHandler(options: McpHandlerOptions): McpHandler;
405
+ /**
406
+ * Build the Daloy route definitions for a Streamable HTTP MCP endpoint.
407
+ *
408
+ * Register each returned route on the Daloy app that should host MCP. A
409
+ * separate app is often the cleanest production shape: the REST API can keep
410
+ * its public contract and auth policy, while the MCP server can use its own
411
+ * bearer token, rate limit, network allowlist, and tool set.
412
+ *
413
+ * @param path - Public MCP endpoint path, usually `"/mcp"`.
414
+ * @param handler - Handler returned by {@link createMcpHandler}.
415
+ * @returns Route definitions for `POST`, `GET`, and `OPTIONS` on the same
416
+ * path. `POST` is the actual MCP transport; `GET` gives a human-readable
417
+ * 405 hint because this helper does not open server-initiated SSE streams;
418
+ * `OPTIONS` supports preflight when CORS middleware is installed.
419
+ *
420
+ * @example
421
+ * ```ts
422
+ * const app = new App();
423
+ * const mcp = createMcpHandler({ serverInfo, tools });
424
+ *
425
+ * for (const route of mcpRoutes("/mcp", mcp)) {
426
+ * app.route(route);
427
+ * }
428
+ * ```
429
+ *
430
+ * @since 1.0.0
431
+ */
432
+ export declare function mcpRoutes(path: PathString, handler: McpHandler): RouteDefinition<PathString, "GET" | "POST" | "OPTIONS">[];
package/dist/mcp.js ADDED
@@ -0,0 +1,419 @@
1
+ /**
2
+ * Latest MCP protocol version DaloyJS negotiates by default.
3
+ *
4
+ * @see https://modelcontextprotocol.io/specification/2025-11-25
5
+ * @since 1.0.0
6
+ */
7
+ export const MCP_PROTOCOL_VERSION = "2025-11-25";
8
+ /**
9
+ * Protocol revisions accepted by {@link createMcpHandler} unless the caller
10
+ * provides an explicit `protocolVersions` list.
11
+ *
12
+ * @since 1.0.0
13
+ */
14
+ export const MCP_PROTOCOL_VERSIONS = Object.freeze([
15
+ "2024-11-05",
16
+ "2025-03-26",
17
+ "2025-06-18",
18
+ "2025-11-25",
19
+ ]);
20
+ /**
21
+ * Default maximum accepted JSON-RPC request body for a DaloyJS MCP endpoint.
22
+ * The cap is intentionally small because MCP calls should carry parameters,
23
+ * not bulk uploads. Raise it per endpoint when a real tool needs larger input.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+ export const MCP_DEFAULT_MAX_BODY_BYTES = 1 << 18;
28
+ const PARSE_ERROR = -32700;
29
+ const INVALID_REQUEST = -32600;
30
+ const METHOD_NOT_FOUND = -32601;
31
+ const INVALID_PARAMS = -32602;
32
+ const INTERNAL_ERROR = -32603;
33
+ const MCP_JSON_RESPONSE_SCHEMA = {
34
+ "~standard": {
35
+ version: 1,
36
+ vendor: "daloyjs",
37
+ validate: (value) => ({ value }),
38
+ },
39
+ };
40
+ /**
41
+ * Caller-correctable MCP tool/resource/prompt error.
42
+ *
43
+ * Throw this when the model supplied bad arguments, referenced a missing
44
+ * domain object, or otherwise made a recoverable call. Tool errors become
45
+ * `{ isError: true }` tool results; resource and prompt errors become
46
+ * JSON-RPC invalid-params errors. Unexpected errors are treated as internal
47
+ * server failures and are redacted in production.
48
+ *
49
+ * @since 1.0.0
50
+ */
51
+ export class McpToolError extends Error {
52
+ /**
53
+ * Create a recoverable MCP handler error.
54
+ *
55
+ * @param message - Safe, caller-visible explanation.
56
+ */
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = "McpToolError";
60
+ }
61
+ }
62
+ function isJsonRpcId(value) {
63
+ return value === null || typeof value === "string" || typeof value === "number";
64
+ }
65
+ function jsonResponse(body, status, extraHeaders) {
66
+ return new Response(JSON.stringify(body), {
67
+ status,
68
+ headers: {
69
+ "content-type": "application/json; charset=utf-8",
70
+ "cache-control": "no-store",
71
+ ...(extraHeaders ?? {}),
72
+ },
73
+ });
74
+ }
75
+ function rpcResult(id, result, extraHeaders) {
76
+ return jsonResponse({ jsonrpc: "2.0", id, result }, 200, extraHeaders);
77
+ }
78
+ function rpcError(id, code, message, data, status, extraHeaders) {
79
+ const error = { code, message };
80
+ if (data !== undefined)
81
+ error.data = data;
82
+ return jsonResponse({ jsonrpc: "2.0", id, error }, status, extraHeaders);
83
+ }
84
+ function safeInternalErrorData(error, expose) {
85
+ if (!expose)
86
+ return undefined;
87
+ return { detail: error instanceof Error ? error.message : String(error) };
88
+ }
89
+ function asRecord(value) {
90
+ return value && typeof value === "object" && !Array.isArray(value)
91
+ ? value
92
+ : {};
93
+ }
94
+ function publicTool(tool) {
95
+ const { handler: _handler, ...rest } = tool;
96
+ return rest;
97
+ }
98
+ function publicResource(resource) {
99
+ const { read: _read, ...rest } = resource;
100
+ return rest;
101
+ }
102
+ function publicPrompt(prompt) {
103
+ const { get: _get, ...rest } = prompt;
104
+ return rest;
105
+ }
106
+ function normalizeToolResult(value) {
107
+ return typeof value === "string" ? { content: [{ type: "text", text: value }] } : value;
108
+ }
109
+ function selectedProtocolVersion(requested, supported, preferred) {
110
+ return supported.has(requested) ? requested : preferred;
111
+ }
112
+ /**
113
+ * Create a dependency-free MCP Streamable HTTP endpoint handler.
114
+ *
115
+ * The handler implements the server side of MCP over one HTTP endpoint:
116
+ * `initialize`, `ping`, `tools/list`, `tools/call`, `resources/list`,
117
+ * `resources/read`, `prompts/list`, and `prompts/get`. It accepts JSON-RPC
118
+ * requests over `POST`, acknowledges notifications with `202`, validates the
119
+ * `MCP-Protocol-Version` header, bounds request bodies, and returns JSON-RPC
120
+ * errors for malformed input.
121
+ *
122
+ * It intentionally does not spawn stdio servers, manage OAuth metadata, keep
123
+ * durable sessions, or open server-initiated SSE streams. Use DaloyJS
124
+ * middleware for authentication and authorization, and run this on a dedicated
125
+ * Daloy app when your MCP server has a different trust boundary than your REST
126
+ * API.
127
+ *
128
+ * @param options - Server identity, capabilities, limits, and response headers.
129
+ * @returns A Fetch-compatible request handler suitable for {@link mcpRoutes}
130
+ * or for direct use in any web-standard runtime.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * const mcp = createMcpHandler({
135
+ * serverInfo: { name: "inventory-mcp", version: "1.0.0" },
136
+ * tools: [
137
+ * {
138
+ * name: "inventory_lookup",
139
+ * description: "Look up inventory by SKU.",
140
+ * inputSchema: {
141
+ * type: "object",
142
+ * properties: { sku: { type: "string" } },
143
+ * required: ["sku"],
144
+ * additionalProperties: false,
145
+ * },
146
+ * handler: async ({ sku }) => `SKU ${sku} has 42 units.`,
147
+ * },
148
+ * ],
149
+ * });
150
+ * ```
151
+ *
152
+ * @since 1.0.0
153
+ */
154
+ export function createMcpHandler(options) {
155
+ if (options.serverInfo.name.trim().length === 0) {
156
+ throw new TypeError("MCP serverInfo.name is required.");
157
+ }
158
+ if (options.serverInfo.version.trim().length === 0) {
159
+ throw new TypeError("MCP serverInfo.version is required.");
160
+ }
161
+ const protocolVersions = options.protocolVersions ?? MCP_PROTOCOL_VERSIONS;
162
+ if (protocolVersions.length === 0) {
163
+ throw new TypeError("MCP protocolVersions must contain at least one version.");
164
+ }
165
+ const preferred = options.preferredProtocolVersion ?? MCP_PROTOCOL_VERSION;
166
+ const supported = new Set(protocolVersions);
167
+ if (!supported.has(preferred)) {
168
+ throw new TypeError("MCP preferredProtocolVersion must be listed in protocolVersions.");
169
+ }
170
+ const maxBodyBytes = options.maxBodyBytes ?? MCP_DEFAULT_MAX_BODY_BYTES;
171
+ if (!Number.isSafeInteger(maxBodyBytes) || maxBodyBytes < 1) {
172
+ throw new TypeError("MCP maxBodyBytes must be a positive safe integer.");
173
+ }
174
+ const tools = options.tools ?? [];
175
+ const resources = options.resources ?? [];
176
+ const prompts = options.prompts ?? [];
177
+ const toolMap = new Map(tools.map((tool) => [tool.name, tool]));
178
+ const resourceMap = new Map(resources.map((resource) => [resource.uri, resource]));
179
+ const promptMap = new Map(prompts.map((prompt) => [prompt.name, prompt]));
180
+ if (toolMap.size !== tools.length)
181
+ throw new TypeError("MCP tool names must be unique.");
182
+ if (resourceMap.size !== resources.length)
183
+ throw new TypeError("MCP resource URIs must be unique.");
184
+ if (promptMap.size !== prompts.length)
185
+ throw new TypeError("MCP prompt names must be unique.");
186
+ const exposeInternalErrors = options.exposeInternalErrors ??
187
+ (typeof process === "object" && process.env?.NODE_ENV !== "production");
188
+ const headers = options.headers;
189
+ async function handleRpcRequest(message, request) {
190
+ const id = (message.id ?? null);
191
+ const method = message.method;
192
+ const params = asRecord(message.params);
193
+ const protocolVersion = selectedProtocolVersion(typeof params.protocolVersion === "string"
194
+ ? params.protocolVersion
195
+ : (request.headers.get("mcp-protocol-version") ?? ""), supported, preferred);
196
+ const ctx = { request, protocolVersion, id, method };
197
+ switch (method) {
198
+ case "initialize":
199
+ return rpcResult(id, {
200
+ protocolVersion,
201
+ capabilities: {
202
+ ...(tools.length > 0 ? { tools: {} } : {}),
203
+ ...(resources.length > 0 ? { resources: {} } : {}),
204
+ ...(prompts.length > 0 ? { prompts: {} } : {}),
205
+ },
206
+ serverInfo: options.serverInfo,
207
+ ...(options.instructions ? { instructions: options.instructions } : {}),
208
+ }, headers);
209
+ case "ping":
210
+ return rpcResult(id, {}, headers);
211
+ case "tools/list":
212
+ return rpcResult(id, { tools: tools.map(publicTool) }, headers);
213
+ case "tools/call": {
214
+ const name = typeof params.name === "string" ? params.name : "";
215
+ const tool = toolMap.get(name);
216
+ if (!tool) {
217
+ return rpcError(id, INVALID_PARAMS, `Unknown tool: ${name || "<missing>"}`, undefined, 200, headers);
218
+ }
219
+ try {
220
+ const result = await tool.handler(asRecord(params.arguments), ctx);
221
+ return rpcResult(id, normalizeToolResult(result), headers);
222
+ }
223
+ catch (error) {
224
+ if (error instanceof McpToolError) {
225
+ return rpcResult(id, { content: [{ type: "text", text: error.message }], isError: true }, headers);
226
+ }
227
+ return rpcError(id, INTERNAL_ERROR, "Tool execution failed.", safeInternalErrorData(error, exposeInternalErrors), 200, headers);
228
+ }
229
+ }
230
+ case "resources/list":
231
+ return rpcResult(id, { resources: resources.map(publicResource) }, headers);
232
+ case "resources/read": {
233
+ const uri = typeof params.uri === "string" ? params.uri : "";
234
+ const resource = resourceMap.get(uri);
235
+ if (!resource) {
236
+ return rpcError(id, INVALID_PARAMS, `Unknown resource: ${uri || "<missing>"}`, undefined, 200, headers);
237
+ }
238
+ try {
239
+ const read = await resource.read(ctx);
240
+ return rpcResult(id, { contents: Array.isArray(read) ? read : [read] }, headers);
241
+ }
242
+ catch (error) {
243
+ const message = error instanceof McpToolError ? error.message : "Resource read failed.";
244
+ const data = error instanceof McpToolError
245
+ ? undefined
246
+ : safeInternalErrorData(error, exposeInternalErrors);
247
+ return rpcError(id, error instanceof McpToolError ? INVALID_PARAMS : INTERNAL_ERROR, message, data, 200, headers);
248
+ }
249
+ }
250
+ case "prompts/list":
251
+ return rpcResult(id, { prompts: prompts.map(publicPrompt) }, headers);
252
+ case "prompts/get": {
253
+ const name = typeof params.name === "string" ? params.name : "";
254
+ const prompt = promptMap.get(name);
255
+ if (!prompt) {
256
+ return rpcError(id, INVALID_PARAMS, `Unknown prompt: ${name || "<missing>"}`, undefined, 200, headers);
257
+ }
258
+ try {
259
+ return rpcResult(id, await prompt.get(asRecord(params.arguments), ctx), headers);
260
+ }
261
+ catch (error) {
262
+ const message = error instanceof McpToolError ? error.message : "Prompt rendering failed.";
263
+ const data = error instanceof McpToolError
264
+ ? undefined
265
+ : safeInternalErrorData(error, exposeInternalErrors);
266
+ return rpcError(id, error instanceof McpToolError ? INVALID_PARAMS : INTERNAL_ERROR, message, data, 200, headers);
267
+ }
268
+ }
269
+ default:
270
+ return rpcError(id, METHOD_NOT_FOUND, `Method not found: ${method}`, undefined, 200, headers);
271
+ }
272
+ }
273
+ return async function handleMcpRequest(request) {
274
+ if (request.method === "OPTIONS") {
275
+ return new Response(null, {
276
+ status: 204,
277
+ headers: { allow: "GET, POST, OPTIONS", ...(headers ?? {}) },
278
+ });
279
+ }
280
+ if (request.method === "GET") {
281
+ return jsonResponse({
282
+ transport: "streamable-http",
283
+ protocolVersions,
284
+ capabilities: {
285
+ tools: tools.map((tool) => tool.name),
286
+ resources: resources.map((resource) => resource.uri),
287
+ prompts: prompts.map((prompt) => prompt.name),
288
+ },
289
+ hint: "Send JSON-RPC 2.0 over HTTP POST to this endpoint.",
290
+ }, 405, { allow: "POST, OPTIONS", ...(headers ?? {}) });
291
+ }
292
+ if (request.method !== "POST") {
293
+ return jsonResponse({ error: "MCP Streamable HTTP endpoints accept POST requests." }, 405, {
294
+ allow: "POST, OPTIONS",
295
+ ...(headers ?? {}),
296
+ });
297
+ }
298
+ const contentType = request.headers.get("content-type") ?? "";
299
+ if (!contentType.toLowerCase().includes("application/json")) {
300
+ return rpcError(null, INVALID_REQUEST, "MCP POST requests must use application/json.", undefined, 415, headers);
301
+ }
302
+ const protocolHeader = request.headers.get("mcp-protocol-version");
303
+ if (protocolHeader && !supported.has(protocolHeader)) {
304
+ return rpcError(null, INVALID_REQUEST, `Unsupported MCP-Protocol-Version: ${protocolHeader}`, { supported: protocolVersions }, 400, headers);
305
+ }
306
+ const declaredLength = Number(request.headers.get("content-length") ?? "");
307
+ if (Number.isFinite(declaredLength) && declaredLength > maxBodyBytes) {
308
+ return rpcError(null, INVALID_REQUEST, "Request body too large.", undefined, 413, headers);
309
+ }
310
+ const body = await request.arrayBuffer();
311
+ if (body.byteLength > maxBodyBytes) {
312
+ return rpcError(null, INVALID_REQUEST, "Request body too large.", undefined, 413, headers);
313
+ }
314
+ let raw;
315
+ try {
316
+ raw = new TextDecoder("utf-8", { fatal: true }).decode(body);
317
+ }
318
+ catch {
319
+ return rpcError(null, PARSE_ERROR, "Request body must be valid UTF-8.", undefined, 400, headers);
320
+ }
321
+ let message;
322
+ try {
323
+ message = JSON.parse(raw);
324
+ }
325
+ catch {
326
+ return rpcError(null, PARSE_ERROR, "Invalid JSON in request body.", undefined, 400, headers);
327
+ }
328
+ if (Array.isArray(message)) {
329
+ return rpcError(null, INVALID_REQUEST, "JSON-RPC batch requests are not supported.", undefined, 400, headers);
330
+ }
331
+ if (!message || typeof message !== "object" || message.jsonrpc !== "2.0") {
332
+ return rpcError(null, INVALID_REQUEST, "Request must be a JSON-RPC 2.0 message.", undefined, 400, headers);
333
+ }
334
+ if (message.id !== undefined && !isJsonRpcId(message.id)) {
335
+ return rpcError(null, INVALID_REQUEST, "JSON-RPC id must be a string, number, or null.", undefined, 400, headers);
336
+ }
337
+ if (message.method === undefined) {
338
+ if (!("result" in message) && !("error" in message)) {
339
+ return rpcError(null, INVALID_REQUEST, "JSON-RPC message is missing `method`, `result`, or `error`.", undefined, 400, headers);
340
+ }
341
+ return new Response(null, { status: 202, headers });
342
+ }
343
+ if (typeof message.method !== "string") {
344
+ return rpcError(null, INVALID_REQUEST, "JSON-RPC method must be a string.", undefined, 400, headers);
345
+ }
346
+ if (message.id === undefined) {
347
+ return new Response(null, { status: 202, headers });
348
+ }
349
+ try {
350
+ return await handleRpcRequest(message, request);
351
+ }
352
+ catch (error) {
353
+ return rpcError(message.id, INTERNAL_ERROR, "Internal server error.", safeInternalErrorData(error, exposeInternalErrors), 200, headers);
354
+ }
355
+ };
356
+ }
357
+ /**
358
+ * Build the Daloy route definitions for a Streamable HTTP MCP endpoint.
359
+ *
360
+ * Register each returned route on the Daloy app that should host MCP. A
361
+ * separate app is often the cleanest production shape: the REST API can keep
362
+ * its public contract and auth policy, while the MCP server can use its own
363
+ * bearer token, rate limit, network allowlist, and tool set.
364
+ *
365
+ * @param path - Public MCP endpoint path, usually `"/mcp"`.
366
+ * @param handler - Handler returned by {@link createMcpHandler}.
367
+ * @returns Route definitions for `POST`, `GET`, and `OPTIONS` on the same
368
+ * path. `POST` is the actual MCP transport; `GET` gives a human-readable
369
+ * 405 hint because this helper does not open server-initiated SSE streams;
370
+ * `OPTIONS` supports preflight when CORS middleware is installed.
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * const app = new App();
375
+ * const mcp = createMcpHandler({ serverInfo, tools });
376
+ *
377
+ * for (const route of mcpRoutes("/mcp", mcp)) {
378
+ * app.route(route);
379
+ * }
380
+ * ```
381
+ *
382
+ * @since 1.0.0
383
+ */
384
+ export function mcpRoutes(path, handler) {
385
+ const responses = {
386
+ 200: { description: "MCP JSON-RPC response", body: MCP_JSON_RESPONSE_SCHEMA },
387
+ 202: { description: "MCP notification accepted", body: MCP_JSON_RESPONSE_SCHEMA },
388
+ 204: { description: "CORS preflight accepted" },
389
+ 400: { description: "Invalid MCP request" },
390
+ 405: { description: "Unsupported MCP transport method" },
391
+ 413: { description: "MCP request body too large" },
392
+ };
393
+ return [
394
+ {
395
+ method: "POST",
396
+ path,
397
+ operationId: "mcpPost",
398
+ summary: "MCP Streamable HTTP endpoint",
399
+ responses,
400
+ handler: ({ request }) => handler(request),
401
+ },
402
+ {
403
+ method: "GET",
404
+ path,
405
+ operationId: "mcpGet",
406
+ summary: "MCP Streamable HTTP discovery hint",
407
+ responses,
408
+ handler: ({ request }) => handler(request),
409
+ },
410
+ {
411
+ method: "OPTIONS",
412
+ path,
413
+ operationId: "mcpOptions",
414
+ summary: "MCP Streamable HTTP preflight",
415
+ responses,
416
+ handler: ({ request }) => handler(request),
417
+ },
418
+ ];
419
+ }
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:448a4447-0b88-5512-8ce5-485b6688ff4c",
4
+ "serialNumber": "urn:uuid:a88e3a69-b278-5771-a67e-40fb774e004e",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-07-01T13:38:31.674Z",
7
+ "timestamp": "2026-07-01T22:47:12.881Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "1.0.0-beta.5"
12
+ "version": "1.0.0-beta.6"
13
13
  }
14
14
  ],
15
15
  "authors": [
@@ -19,11 +19,11 @@
19
19
  ],
20
20
  "component": {
21
21
  "type": "library",
22
- "bom-ref": "pkg:npm/@daloyjs/core@1.0.0-beta.5",
22
+ "bom-ref": "pkg:npm/@daloyjs/core@1.0.0-beta.6",
23
23
  "name": "@daloyjs/core",
24
- "version": "1.0.0-beta.5",
24
+ "version": "1.0.0-beta.6",
25
25
  "description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
26
- "purl": "pkg:npm/@daloyjs/core@1.0.0-beta.5",
26
+ "purl": "pkg:npm/@daloyjs/core@1.0.0-beta.6",
27
27
  "licenses": [
28
28
  {
29
29
  "license": {
@@ -46,9 +46,9 @@
46
46
  }
47
47
  ],
48
48
  "swid": {
49
- "tagId": "swidtag--daloyjs-core-1.0.0-beta.5",
49
+ "tagId": "swidtag--daloyjs-core-1.0.0-beta.6",
50
50
  "name": "@daloyjs/core",
51
- "version": "1.0.0-beta.5",
51
+ "version": "1.0.0-beta.6",
52
52
  "tagVersion": 0,
53
53
  "patch": false
54
54
  }
@@ -57,7 +57,7 @@
57
57
  "components": [],
58
58
  "dependencies": [
59
59
  {
60
- "ref": "pkg:npm/@daloyjs/core@1.0.0-beta.5",
60
+ "ref": "pkg:npm/@daloyjs/core@1.0.0-beta.6",
61
61
  "dependsOn": []
62
62
  }
63
63
  ]
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@daloyjs/core-1.0.0-beta.5",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.5-448a4447-0b88-5512-8ce5-485b6688ff4c",
5
+ "name": "@daloyjs/core-1.0.0-beta.6",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.6-a88e3a69-b278-5771-a67e-40fb774e004e",
7
7
  "creationInfo": {
8
- "created": "2026-07-01T13:38:31.674Z",
8
+ "created": "2026-07-01T22:47:12.881Z",
9
9
  "creators": [
10
10
  "Tool: daloy-generate-sbom",
11
11
  "Organization: DaloyJS"
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "SPDXID": "SPDXRef-Package--daloyjs-core",
18
18
  "name": "@daloyjs/core",
19
- "versionInfo": "1.0.0-beta.5",
19
+ "versionInfo": "1.0.0-beta.6",
20
20
  "downloadLocation": "https://github.com/daloyjs/daloy",
21
21
  "filesAnalyzed": false,
22
22
  "licenseConcluded": "MIT",
@@ -27,7 +27,7 @@
27
27
  {
28
28
  "referenceCategory": "PACKAGE-MANAGER",
29
29
  "referenceType": "purl",
30
- "referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-beta.5"
30
+ "referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-beta.6"
31
31
  }
32
32
  ]
33
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daloyjs/core",
3
- "version": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -20,6 +20,7 @@
20
20
  "framework",
21
21
  "http",
22
22
  "openapi",
23
+ "mcp",
23
24
  "hey-api",
24
25
  "contract-first",
25
26
  "typescript",
@@ -85,6 +86,10 @@
85
86
  "types": "./dist/openapi-diff.d.ts",
86
87
  "import": "./dist/openapi-diff.js"
87
88
  },
89
+ "./mcp": {
90
+ "types": "./dist/mcp.d.ts",
91
+ "import": "./dist/mcp.js"
92
+ },
88
93
  "./asyncapi": {
89
94
  "types": "./dist/asyncapi.d.ts",
90
95
  "import": "./dist/asyncapi.js"