alepha 0.20.3 → 0.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/jobs/index.d.ts +14 -14
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/keys/index.d.ts +4 -4
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +8 -3
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +20 -4
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/users/index.browser.js +6 -0
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +5037 -139
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +58 -10
- package/dist/api/users/index.js.map +1 -1
- package/dist/bucket/index.d.ts +77 -107
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +148 -4
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +7 -1
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +26 -0
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +11 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +11 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cli/config/index.d.ts +7 -5
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +2 -3
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +420 -13
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +22 -511
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +4 -8
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +13 -15
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +10 -13
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +18 -15
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +10 -13
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +16 -13
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/core/index.browser.js +27 -3
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +6 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +27 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +27 -3
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +27 -3
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/datetime/index.d.ts +69 -10
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +135 -13
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/smtp/index.js +10636 -2
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +8085 -4
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +33554 -3
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +30 -2
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +35 -12
- package/dist/lock/core/index.js.map +1 -1
- package/dist/mcp/index.d.ts +238 -31
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +198 -71
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +1 -1
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +4 -3
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +4877 -9
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +4 -3
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +608 -1
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/react/core/index.d.ts +102 -1
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +65 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +6 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +7 -7
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +7 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +6 -0
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/router/index.browser.js +20 -2
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +36 -4
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +20 -2
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/chunk-6Ep1yQYe.js +16 -0
- package/dist/react/testing/index.d.ts +411 -1
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +12293 -13
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +195 -1
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js +61 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +84 -3
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +390 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +390 -1
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.d.ts +325 -2
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1361 -2
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1054 -1
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1223 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/core/index.browser.js +10 -3
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +28 -5
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +514 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4374 -4
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +3 -4
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/websocket/index.browser.js +11 -5
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +21 -6
- package/dist/websocket/index.js.map +1 -1
- package/package.json +671 -263
- package/src/api/parameters/services/ParameterProvider.ts +21 -4
- package/src/api/users/__tests__/SessionService.spec.ts +99 -0
- package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
- package/src/api/users/entities/sessions.ts +6 -0
- package/src/api/users/jobs/UserJobs.ts +44 -17
- package/src/api/users/providers/RealmProvider.ts +4 -0
- package/src/api/users/services/SessionService.ts +27 -0
- package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
- package/src/bucket/index.ts +19 -2
- package/src/bucket/primitives/$bucket.ts +9 -1
- package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
- package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
- package/src/cache/core/index.ts +29 -0
- package/src/cache/core/primitives/$cache.ts +14 -1
- package/src/cli/config/defineConfig.ts +13 -15
- package/src/cli/core/__tests__/init.spec.ts +6 -7
- package/src/cli/core/services/ProjectScaffolder.ts +18 -14
- package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
- package/src/cli/core/templates/agentMd.ts +2 -10
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +3 -3
- package/src/cli/devtools/index.ts +12 -26
- package/src/cli/platform/index.ts +15 -24
- package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
- package/src/cli/vendor/index.ts +14 -23
- package/src/core/Alepha.ts +11 -1
- package/src/core/helpers/ref.ts +18 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/providers/SchemaValidator.ts +9 -1
- package/src/core/providers/TypeProvider.ts +1 -2
- package/src/datetime/REFACTORING.md +118 -0
- package/src/datetime/providers/DateTimeProvider.ts +203 -24
- package/src/lock/core/index.ts +31 -0
- package/src/lock/core/primitives/$lock.ts +14 -1
- package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
- package/src/mcp/helpers/jsonrpc.ts +26 -1
- package/src/mcp/index.ts +10 -5
- package/src/mcp/interfaces/McpTypes.ts +83 -6
- package/src/mcp/primitives/$prompt.ts +18 -1
- package/src/mcp/primitives/$resource.ts +18 -1
- package/src/mcp/primitives/$tool.ts +83 -7
- package/src/mcp/providers/McpServerProvider.ts +74 -16
- package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
- package/src/orm/REFACTORING.md +330 -0
- package/src/orm/core/primitives/$transactional.ts +11 -0
- package/src/orm/core/schemas/updateSchema.ts +1 -1
- package/src/orm/core/services/PgRelationManager.ts +4 -2
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
- package/src/react/core/hooks/useQuery.ts +153 -0
- package/src/react/core/index.ts +1 -0
- package/src/react/form/services/FormModel.ts +15 -6
- package/src/react/form/services/parseField.ts +8 -0
- package/src/react/i18n/providers/I18nProvider.ts +8 -2
- package/src/react/router/__tests__/$page.spec.tsx +0 -16
- package/src/react/router/__tests__/ssr.spec.tsx +339 -0
- package/src/react/router/primitives/$page.ts +28 -4
- package/src/react/router/providers/ReactPageProvider.ts +27 -9
- package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
- package/src/react/ui/index.ts +6 -0
- package/src/react/ui/services/SchemaControl.ts +209 -0
- package/src/security/primitives/$issuer.ts +6 -3
- package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
- package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
- package/src/server/core/errors/ValidationError.ts +13 -1
- package/src/server/core/primitives/$action.ts +16 -5
- package/src/server/core/providers/ServerRouterProvider.ts +26 -4
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -7
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
- package/src/websocket/services/WebSocketClient.ts +11 -5
- package/src/mcp/transports/SseMcpTransport.ts +0 -182
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { $inject, createPrimitive, KIND, Primitive } from "alepha";
|
|
2
2
|
import type {
|
|
3
3
|
McpContext,
|
|
4
|
+
McpIcon,
|
|
4
5
|
McpResourceDescriptor,
|
|
5
6
|
ResourceContent,
|
|
6
7
|
ResourceHandler,
|
|
@@ -78,6 +79,17 @@ export interface ResourcePrimitiveOptions {
|
|
|
78
79
|
*/
|
|
79
80
|
name?: string;
|
|
80
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Human-friendly display title (spec 2025-11-25). Distinct from `name`,
|
|
84
|
+
* which remains the programmatic identifier.
|
|
85
|
+
*/
|
|
86
|
+
title?: string;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Optional icons surfaced in client UIs (spec 2025-11-25 / SEP-973).
|
|
90
|
+
*/
|
|
91
|
+
icons?: McpIcon[];
|
|
92
|
+
|
|
81
93
|
/**
|
|
82
94
|
* Description of what this resource contains.
|
|
83
95
|
*
|
|
@@ -159,12 +171,17 @@ export class ResourcePrimitive extends Primitive<ResourcePrimitiveOptions> {
|
|
|
159
171
|
* Convert the resource to an MCP resource descriptor for protocol messages.
|
|
160
172
|
*/
|
|
161
173
|
public toDescriptor(): McpResourceDescriptor {
|
|
162
|
-
|
|
174
|
+
const descriptor: McpResourceDescriptor = {
|
|
163
175
|
uri: this.uri,
|
|
164
176
|
name: this.name,
|
|
165
177
|
description: this.description,
|
|
166
178
|
mimeType: this.mimeType,
|
|
167
179
|
};
|
|
180
|
+
if (this.options.title) descriptor.title = this.options.title;
|
|
181
|
+
if (this.options.icons && this.options.icons.length > 0) {
|
|
182
|
+
descriptor.icons = this.options.icons;
|
|
183
|
+
}
|
|
184
|
+
return descriptor;
|
|
168
185
|
}
|
|
169
186
|
}
|
|
170
187
|
|
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
} from "alepha";
|
|
11
11
|
import type {
|
|
12
12
|
McpContext,
|
|
13
|
+
McpIcon,
|
|
13
14
|
McpJsonSchema,
|
|
15
|
+
McpToolAnnotations,
|
|
14
16
|
McpToolDescriptor,
|
|
15
17
|
ToolHandlerArgs,
|
|
16
18
|
ToolHandlerResult,
|
|
@@ -84,6 +86,15 @@ export interface ToolPrimitiveOptions<T extends ToolPrimitiveSchema> {
|
|
|
84
86
|
*/
|
|
85
87
|
name?: string;
|
|
86
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Human-friendly display title (spec 2025-11-25). Distinct from `name`,
|
|
91
|
+
* which remains the programmatic identifier. Clients use `title` in
|
|
92
|
+
* tool palettes / picker UIs.
|
|
93
|
+
*
|
|
94
|
+
* @example "Search Lore"
|
|
95
|
+
*/
|
|
96
|
+
title?: string;
|
|
97
|
+
|
|
87
98
|
/**
|
|
88
99
|
* A human-readable description of what the tool does.
|
|
89
100
|
*
|
|
@@ -95,6 +106,18 @@ export interface ToolPrimitiveOptions<T extends ToolPrimitiveSchema> {
|
|
|
95
106
|
*/
|
|
96
107
|
description: string;
|
|
97
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Behavior hints (spec 2025-03-26+). Clients use these to gate UI prompts
|
|
111
|
+
* (e.g. require confirmation before a tool with `destructiveHint: true`).
|
|
112
|
+
* None are guarantees — they are heuristics for the client, not the model.
|
|
113
|
+
*/
|
|
114
|
+
annotations?: McpToolAnnotations;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Icons surfaced in client tool palettes / picker UIs (spec 2025-11-25).
|
|
118
|
+
*/
|
|
119
|
+
icons?: McpIcon[];
|
|
120
|
+
|
|
98
121
|
/**
|
|
99
122
|
* TypeBox schema defining the tool's parameters and result type.
|
|
100
123
|
*
|
|
@@ -141,6 +164,15 @@ export class ToolPrimitive<T extends ToolPrimitiveSchema> extends Primitive<
|
|
|
141
164
|
return this.options.description;
|
|
142
165
|
}
|
|
143
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Whether the tool declared a result schema. When true, `tools/call`
|
|
169
|
+
* responses include `structuredContent` populated with the validated
|
|
170
|
+
* result (spec 2025-06-18).
|
|
171
|
+
*/
|
|
172
|
+
public hasOutputSchema(): boolean {
|
|
173
|
+
return !!this.options.schema?.result;
|
|
174
|
+
}
|
|
175
|
+
|
|
144
176
|
protected onInit(): void {
|
|
145
177
|
this.mcpServer.registerTool(this);
|
|
146
178
|
}
|
|
@@ -184,21 +216,57 @@ export class ToolPrimitive<T extends ToolPrimitiveSchema> extends Primitive<
|
|
|
184
216
|
|
|
185
217
|
/**
|
|
186
218
|
* Convert the tool to an MCP tool descriptor for protocol messages.
|
|
219
|
+
*
|
|
220
|
+
* Emits the spec 2025-11-25 surface: `title`, `annotations`, `icons`,
|
|
221
|
+
* and (when `schema.result` is defined) `outputSchema` so the server
|
|
222
|
+
* can populate `structuredContent` on call results.
|
|
187
223
|
*/
|
|
188
224
|
public toDescriptor(): McpToolDescriptor {
|
|
189
|
-
|
|
225
|
+
const inputSchema: McpJsonSchema = this.options.schema?.params
|
|
226
|
+
? this.schemaToJsonSchema(this.options.schema.params)
|
|
227
|
+
: { type: "object", properties: {}, required: [] };
|
|
228
|
+
|
|
229
|
+
const descriptor: McpToolDescriptor = {
|
|
190
230
|
name: this.name,
|
|
191
231
|
description: this.description,
|
|
192
|
-
inputSchema
|
|
193
|
-
? this.schemaToJsonSchema(this.options.schema.params)
|
|
194
|
-
: { type: "object", properties: {}, required: [] },
|
|
232
|
+
inputSchema,
|
|
195
233
|
};
|
|
234
|
+
|
|
235
|
+
if (this.options.title) descriptor.title = this.options.title;
|
|
236
|
+
if (this.options.annotations)
|
|
237
|
+
descriptor.annotations = this.options.annotations;
|
|
238
|
+
if (this.options.icons && this.options.icons.length > 0) {
|
|
239
|
+
descriptor.icons = this.options.icons;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Output schema is emitted when the tool declares `schema.result`,
|
|
243
|
+
// unlocking structured content on tools/call responses.
|
|
244
|
+
if (this.options.schema?.result) {
|
|
245
|
+
const out = this.propertyToJsonSchema(this.options.schema.result);
|
|
246
|
+
// The result schema may be a primitive — wrap so the descriptor
|
|
247
|
+
// value is always a JSON Schema object with `type`.
|
|
248
|
+
descriptor.outputSchema = (
|
|
249
|
+
typeof out === "object" && out !== null && "type" in out
|
|
250
|
+
? out
|
|
251
|
+
: { type: "object", properties: {}, required: [] }
|
|
252
|
+
) as McpJsonSchema;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return descriptor;
|
|
196
256
|
}
|
|
197
257
|
|
|
198
258
|
/**
|
|
199
259
|
* Convert a TypeBox schema to JSON Schema format.
|
|
260
|
+
*
|
|
261
|
+
* Emits the 2020-12 dialect annotation at the root (spec 2025-11-25 /
|
|
262
|
+
* SEP-1613 — JSON Schema 2020-12 is the default dialect for MCP).
|
|
263
|
+
* The TypeBox shapes Alepha emits today are already 2020-12-compatible;
|
|
264
|
+
* this is just the dialect declaration.
|
|
200
265
|
*/
|
|
201
|
-
protected schemaToJsonSchema(
|
|
266
|
+
protected schemaToJsonSchema(
|
|
267
|
+
schema: TObject,
|
|
268
|
+
options?: { root?: boolean },
|
|
269
|
+
): McpJsonSchema {
|
|
202
270
|
const properties: Record<string, unknown> = {};
|
|
203
271
|
const required: string[] = [];
|
|
204
272
|
|
|
@@ -211,11 +279,19 @@ export class ToolPrimitive<T extends ToolPrimitiveSchema> extends Primitive<
|
|
|
211
279
|
}
|
|
212
280
|
}
|
|
213
281
|
|
|
214
|
-
|
|
282
|
+
const result: McpJsonSchema = {
|
|
215
283
|
type: "object",
|
|
216
284
|
properties,
|
|
217
285
|
required,
|
|
218
286
|
};
|
|
287
|
+
|
|
288
|
+
// Annotate the dialect on the root schema only (avoid noise on nested
|
|
289
|
+
// sub-schemas where MCP doesn't expect $schema).
|
|
290
|
+
if (options?.root !== false) {
|
|
291
|
+
result.$schema = "https://json-schema.org/draft/2020-12/schema";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return result;
|
|
219
295
|
}
|
|
220
296
|
|
|
221
297
|
/**
|
|
@@ -251,7 +327,7 @@ export class ToolPrimitive<T extends ToolPrimitiveSchema> extends Primitive<
|
|
|
251
327
|
result.items = this.propertyToJsonSchema(schema.items as TSchema);
|
|
252
328
|
}
|
|
253
329
|
} else if (t.schema.isObject(schema)) {
|
|
254
|
-
Object.assign(result, this.schemaToJsonSchema(schema));
|
|
330
|
+
Object.assign(result, this.schemaToJsonSchema(schema, { root: false }));
|
|
255
331
|
} else if (t.schema.isUnsafe(schema) || t.schema.isOptional(schema)) {
|
|
256
332
|
// Handle Unsafe types (like t.enum) and optional wrappers by checking the underlying type property
|
|
257
333
|
const schemaAny = schema as { type?: string; enum?: unknown[] };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $inject, Alepha } from "alepha";
|
|
1
|
+
import { $inject, Alepha, TypeBoxError } from "alepha";
|
|
2
2
|
import { $logger } from "alepha/logger";
|
|
3
3
|
import {
|
|
4
4
|
McpError,
|
|
@@ -11,13 +11,14 @@ import {
|
|
|
11
11
|
createErrorResponse,
|
|
12
12
|
createInternalError,
|
|
13
13
|
createResponse,
|
|
14
|
+
isSupportedProtocolVersion,
|
|
14
15
|
MCP_PROTOCOL_VERSION,
|
|
16
|
+
SUPPORTED_PROTOCOL_VERSIONS,
|
|
15
17
|
} from "../helpers/jsonrpc.ts";
|
|
16
18
|
import type {
|
|
17
19
|
JsonRpcRequest,
|
|
18
20
|
JsonRpcResponse,
|
|
19
21
|
McpCapabilities,
|
|
20
|
-
McpContent,
|
|
21
22
|
McpContext,
|
|
22
23
|
McpInitializeResult,
|
|
23
24
|
McpPromptDescriptor,
|
|
@@ -55,7 +56,20 @@ export class McpServerProvider {
|
|
|
55
56
|
|
|
56
57
|
protected initialized = false;
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Protocol version negotiated with the client during `initialize`.
|
|
61
|
+
* Used by transports to validate the `MCP-Protocol-Version` header on
|
|
62
|
+
* subsequent HTTP requests (per spec 2025-06-18+).
|
|
63
|
+
*/
|
|
64
|
+
public negotiatedVersion: string = MCP_PROTOCOL_VERSION;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Server identity returned during `initialize`. Consumers may override
|
|
68
|
+
* fields directly (e.g. `mcpServer.serverInfo = { name: "roadmap-mcp",
|
|
69
|
+
* version: "0.20.3", description: "..." }`) — the `description` field
|
|
70
|
+
* is supported per spec 2025-11-25 (minor change #2).
|
|
71
|
+
*/
|
|
72
|
+
public serverInfo: McpServerInfo = {
|
|
59
73
|
name: "alepha-mcp",
|
|
60
74
|
version: "1.0.0",
|
|
61
75
|
};
|
|
@@ -243,15 +257,25 @@ export class McpServerProvider {
|
|
|
243
257
|
protected handleInitialize(
|
|
244
258
|
params: Record<string, unknown>,
|
|
245
259
|
): McpInitializeResult {
|
|
260
|
+
const requested = params.protocolVersion;
|
|
261
|
+
// Echo the client's version when supported, otherwise reply with our
|
|
262
|
+
// preferred version (highest entry in SUPPORTED_PROTOCOL_VERSIONS).
|
|
263
|
+
// The client can then decide to retry, downgrade, or disconnect.
|
|
264
|
+
const negotiated = isSupportedProtocolVersion(requested)
|
|
265
|
+
? requested
|
|
266
|
+
: SUPPORTED_PROTOCOL_VERSIONS[0];
|
|
267
|
+
|
|
246
268
|
this.log.info("MCP client initializing", {
|
|
247
269
|
clientInfo: params.clientInfo,
|
|
248
|
-
|
|
270
|
+
requestedProtocolVersion: requested,
|
|
271
|
+
negotiatedProtocolVersion: negotiated,
|
|
249
272
|
});
|
|
250
273
|
|
|
251
274
|
this.initialized = true;
|
|
275
|
+
this.negotiatedVersion = negotiated;
|
|
252
276
|
|
|
253
277
|
return {
|
|
254
|
-
protocolVersion:
|
|
278
|
+
protocolVersion: negotiated,
|
|
255
279
|
capabilities: this.getCapabilities(),
|
|
256
280
|
serverInfo: this.serverInfo,
|
|
257
281
|
};
|
|
@@ -276,24 +300,58 @@ export class McpServerProvider {
|
|
|
276
300
|
|
|
277
301
|
const tool = this.tools.get(name);
|
|
278
302
|
if (!tool) {
|
|
303
|
+
// McpToolNotFoundError is intentionally a JSON-RPC protocol error,
|
|
304
|
+
// not a tool execution error — see SEP-1303 (only validation/runtime
|
|
305
|
+
// failures of an existing tool are reported via isError: true).
|
|
279
306
|
throw new McpToolNotFoundError(name);
|
|
280
307
|
}
|
|
281
308
|
|
|
282
309
|
try {
|
|
283
310
|
const result = await tool.execute(args, context);
|
|
284
311
|
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
312
|
+
const callResult: McpToolCallResult = {
|
|
313
|
+
content: [
|
|
314
|
+
{
|
|
315
|
+
type: "text",
|
|
316
|
+
text:
|
|
317
|
+
typeof result === "string"
|
|
318
|
+
? result
|
|
319
|
+
: JSON.stringify(result ?? null),
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// Spec 2025-06-18: when the tool declares an outputSchema, the server
|
|
325
|
+
// MUST populate `structuredContent` with the validated result. The
|
|
326
|
+
// text-stringified `content` block remains as a back-compat fallback.
|
|
327
|
+
if (tool.hasOutputSchema() && result !== undefined) {
|
|
328
|
+
callResult.structuredContent = result;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return callResult;
|
|
296
332
|
} catch (error) {
|
|
333
|
+
// Spec 2025-11-25 / SEP-1303: input-validation failures (and other
|
|
334
|
+
// tool-runtime errors) are returned as Tool Execution Errors, not
|
|
335
|
+
// JSON-RPC protocol errors, so the model can self-correct.
|
|
336
|
+
// For TypeBox validation errors we surface the failing path so the
|
|
337
|
+
// model knows which argument was malformed.
|
|
338
|
+
if (error instanceof TypeBoxError) {
|
|
339
|
+
const path = error.value?.path || "/";
|
|
340
|
+
const message = error.value?.message || error.message;
|
|
341
|
+
return {
|
|
342
|
+
content: [
|
|
343
|
+
{
|
|
344
|
+
type: "text",
|
|
345
|
+
text: `Validation error at ${path}: ${message}`,
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
structuredContent: {
|
|
349
|
+
errors: [{ path, message }],
|
|
350
|
+
},
|
|
351
|
+
isError: true,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
297
355
|
return {
|
|
298
356
|
content: [
|
|
299
357
|
{
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { $atom, $inject, $state, t } from "alepha";
|
|
2
|
+
import { $logger } from "alepha/logger";
|
|
3
|
+
import { $route } from "alepha/server";
|
|
4
|
+
import {
|
|
5
|
+
createErrorResponse,
|
|
6
|
+
createParseError,
|
|
7
|
+
JsonRpcParseError,
|
|
8
|
+
parseMessage,
|
|
9
|
+
} from "../helpers/jsonrpc.ts";
|
|
10
|
+
import type { McpContext } from "../interfaces/McpTypes.ts";
|
|
11
|
+
import { McpServerProvider } from "../providers/McpServerProvider.ts";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export const mcpStreamableHttpOptions = $atom({
|
|
16
|
+
name: "alepha.mcp.streamableHttp.options",
|
|
17
|
+
description: "Configuration options for the MCP Streamable HTTP transport.",
|
|
18
|
+
schema: t.object({
|
|
19
|
+
/**
|
|
20
|
+
* Path for the MCP endpoint. Single endpoint for both requests and
|
|
21
|
+
* (optional) server-streamed responses, per spec 2025-03-26+.
|
|
22
|
+
*/
|
|
23
|
+
path: t.text({ default: "/mcp" }),
|
|
24
|
+
/**
|
|
25
|
+
* Allow-list of `Origin` header values accepted on incoming requests.
|
|
26
|
+
* Empty array (default) means "allow any". When set, browser-originated
|
|
27
|
+
* requests with a non-matching `Origin` are rejected with 403 Forbidden,
|
|
28
|
+
* blocking DNS-rebinding attacks against localhost MCP servers.
|
|
29
|
+
*
|
|
30
|
+
* Server-to-server callers (no `Origin` header) are always allowed.
|
|
31
|
+
*
|
|
32
|
+
* Spec 2025-11-25, PR #1439.
|
|
33
|
+
*/
|
|
34
|
+
allowedOrigins: t.array(t.text(), { default: [] }),
|
|
35
|
+
}),
|
|
36
|
+
default: {
|
|
37
|
+
path: "/mcp",
|
|
38
|
+
allowedOrigins: [],
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Backward-compat alias for the legacy atom name. Prefer
|
|
43
|
+
// `mcpStreamableHttpOptions` going forward; this re-export keeps existing
|
|
44
|
+
// consumer imports compiling and will be removed once they migrate.
|
|
45
|
+
export const mcpSseOptions = mcpStreamableHttpOptions;
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Streamable HTTP transport for MCP communication.
|
|
51
|
+
*
|
|
52
|
+
* Implements the 2025-03-26+ Streamable HTTP transport: a single `/mcp`
|
|
53
|
+
* endpoint that accepts JSON-RPC over POST and returns either
|
|
54
|
+
* `application/json` (single response, the default) or
|
|
55
|
+
* `text/event-stream` (when the server wants to stream multiple messages).
|
|
56
|
+
*
|
|
57
|
+
* Designed for serverless deployment (Cloudflare Workers, etc.) — there is
|
|
58
|
+
* no long-lived GET stream. GET on the endpoint returns 405 Method Not
|
|
59
|
+
* Allowed; clients that want server-initiated push must rely on the POST
|
|
60
|
+
* response stream when the server upgrades to SSE for that particular call.
|
|
61
|
+
*
|
|
62
|
+
* Spec compliance:
|
|
63
|
+
* - 2025-06-18: validates `MCP-Protocol-Version` header on every request
|
|
64
|
+
* after `initialize` against the version negotiated and stored on
|
|
65
|
+
* `McpServerProvider`.
|
|
66
|
+
* - 2025-11-25: rejects requests with a non-allow-listed `Origin` header
|
|
67
|
+
* (PR #1439). See {@link mcpStreamableHttpOptions.allowedOrigins}.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { Alepha, run } from "alepha";
|
|
72
|
+
* import { AlephaServer } from "alepha/server";
|
|
73
|
+
* import { AlephaMcp, StreamableHttpMcpTransport } from "alepha/mcp";
|
|
74
|
+
*
|
|
75
|
+
* class MyTools {
|
|
76
|
+
* // ... tool definitions
|
|
77
|
+
* }
|
|
78
|
+
*
|
|
79
|
+
* run(
|
|
80
|
+
* Alepha.create()
|
|
81
|
+
* .with(AlephaServer)
|
|
82
|
+
* .with(AlephaMcp)
|
|
83
|
+
* .with(StreamableHttpMcpTransport)
|
|
84
|
+
* .with(MyTools)
|
|
85
|
+
* );
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export class StreamableHttpMcpTransport {
|
|
89
|
+
protected readonly log = $logger();
|
|
90
|
+
protected readonly options = $state(mcpStreamableHttpOptions);
|
|
91
|
+
protected readonly mcpServer = $inject(McpServerProvider);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* GET on the MCP endpoint is not supported in this transport. Returning
|
|
95
|
+
* 405 (rather than serving the legacy two-endpoint SSE pattern) is the
|
|
96
|
+
* spec-allowed response for servers that don't offer server-initiated
|
|
97
|
+
* push outside of an active POST.
|
|
98
|
+
*/
|
|
99
|
+
notAllowed = $route({
|
|
100
|
+
method: "GET",
|
|
101
|
+
path: this.options.path,
|
|
102
|
+
handler: (request) => {
|
|
103
|
+
request.reply.status = 405;
|
|
104
|
+
request.reply.headers.allow = "POST";
|
|
105
|
+
request.reply.headers["content-type"] = "application/json";
|
|
106
|
+
request.reply.body = JSON.stringify({
|
|
107
|
+
error: "Method Not Allowed. Use POST for MCP messages.",
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* POST endpoint for client-to-server JSON-RPC messages.
|
|
114
|
+
* Returns `application/json` for single responses; tools that need to
|
|
115
|
+
* stream progress would upgrade to `text/event-stream` (deferred until a
|
|
116
|
+
* concrete need exists).
|
|
117
|
+
*/
|
|
118
|
+
message = $route({
|
|
119
|
+
method: "POST",
|
|
120
|
+
path: this.options.path,
|
|
121
|
+
schema: {
|
|
122
|
+
body: t.json(),
|
|
123
|
+
},
|
|
124
|
+
handler: async (request) => {
|
|
125
|
+
try {
|
|
126
|
+
// Origin allow-list check (spec 2025-11-25 / PR #1439).
|
|
127
|
+
const originRaw = request.headers.origin;
|
|
128
|
+
const origin = Array.isArray(originRaw) ? originRaw[0] : originRaw;
|
|
129
|
+
if (
|
|
130
|
+
origin &&
|
|
131
|
+
this.options.allowedOrigins.length > 0 &&
|
|
132
|
+
!this.options.allowedOrigins.includes(origin)
|
|
133
|
+
) {
|
|
134
|
+
this.log.warn("Rejected MCP request with non-allowed Origin", {
|
|
135
|
+
origin,
|
|
136
|
+
allowed: this.options.allowedOrigins,
|
|
137
|
+
});
|
|
138
|
+
request.reply.status = 403;
|
|
139
|
+
request.reply.headers["content-type"] = "application/json";
|
|
140
|
+
request.reply.body = JSON.stringify({
|
|
141
|
+
error: "Forbidden: Origin not allowed",
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const body =
|
|
147
|
+
typeof request.body === "string"
|
|
148
|
+
? request.body
|
|
149
|
+
: JSON.stringify(request.body);
|
|
150
|
+
|
|
151
|
+
this.log.debug("MCP request body", {
|
|
152
|
+
body,
|
|
153
|
+
bodyType: typeof request.body,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const rpcRequest = parseMessage(body);
|
|
157
|
+
|
|
158
|
+
// Build context from request headers
|
|
159
|
+
const headers = { ...request.headers } as Record<
|
|
160
|
+
string,
|
|
161
|
+
string | string[] | undefined
|
|
162
|
+
>;
|
|
163
|
+
|
|
164
|
+
// Spec 2025-06-18+: every HTTP request after `initialize` MUST carry
|
|
165
|
+
// an `MCP-Protocol-Version` header matching the negotiated version.
|
|
166
|
+
// Reject mismatches with 400 so the client doesn't silently drift.
|
|
167
|
+
if (rpcRequest.method !== "initialize") {
|
|
168
|
+
const headerRaw = headers["mcp-protocol-version"];
|
|
169
|
+
const headerVersion = Array.isArray(headerRaw)
|
|
170
|
+
? headerRaw[0]
|
|
171
|
+
: headerRaw;
|
|
172
|
+
if (
|
|
173
|
+
headerVersion &&
|
|
174
|
+
headerVersion !== this.mcpServer.negotiatedVersion
|
|
175
|
+
) {
|
|
176
|
+
this.log.warn("MCP-Protocol-Version header mismatch", {
|
|
177
|
+
header: headerVersion,
|
|
178
|
+
negotiated: this.mcpServer.negotiatedVersion,
|
|
179
|
+
});
|
|
180
|
+
request.reply.status = 400;
|
|
181
|
+
request.reply.headers["content-type"] = "application/json";
|
|
182
|
+
request.reply.body = JSON.stringify({
|
|
183
|
+
error: `MCP-Protocol-Version mismatch: expected ${this.mcpServer.negotiatedVersion}, got ${headerVersion}`,
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const context: McpContext = { headers };
|
|
190
|
+
|
|
191
|
+
const response = await this.mcpServer.handleMessage(
|
|
192
|
+
rpcRequest,
|
|
193
|
+
context,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (response) {
|
|
197
|
+
request.reply.headers["content-type"] = "application/json";
|
|
198
|
+
request.reply.body = JSON.stringify(response);
|
|
199
|
+
} else {
|
|
200
|
+
request.reply.status = 204;
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error instanceof JsonRpcParseError) {
|
|
204
|
+
request.reply.status = 400;
|
|
205
|
+
request.reply.headers["content-type"] = "application/json";
|
|
206
|
+
request.reply.body = JSON.stringify(
|
|
207
|
+
createErrorResponse(0, createParseError(error.message)),
|
|
208
|
+
);
|
|
209
|
+
} else {
|
|
210
|
+
this.log.error("Failed to process MCP message", error);
|
|
211
|
+
request.reply.status = 500;
|
|
212
|
+
request.reply.body = JSON.stringify({
|
|
213
|
+
error: (error as Error).message,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @deprecated Use {@link StreamableHttpMcpTransport}. The 2024-11-05
|
|
223
|
+
* two-endpoint HTTP+SSE pattern was replaced by Streamable HTTP in spec
|
|
224
|
+
* 2025-03-26. This alias is preserved for one release to ease migration.
|
|
225
|
+
*/
|
|
226
|
+
export const SseMcpTransport = StreamableHttpMcpTransport;
|