alepha 0.13.8 → 0.14.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/dist/api/audits/index.d.ts +2 -1
- package/dist/api/audits/index.d.ts.map +1 -0
- package/dist/api/files/index.d.ts +2 -1
- package/dist/api/files/index.d.ts.map +1 -0
- package/dist/api/jobs/index.d.ts +158 -157
- package/dist/api/jobs/index.d.ts.map +1 -0
- package/dist/api/notifications/index.d.ts.map +1 -0
- package/dist/api/parameters/index.d.ts +4 -4
- package/dist/api/parameters/index.d.ts.map +1 -0
- package/dist/api/users/index.d.ts +132 -131
- package/dist/api/users/index.d.ts.map +1 -0
- package/dist/api/verifications/index.d.ts.map +1 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/bucket/index.d.ts.map +1 -0
- package/dist/cache/core/index.d.ts.map +1 -0
- package/dist/cache/redis/index.d.ts.map +1 -0
- package/dist/cli/index.d.ts +44 -32
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +380 -109
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +11 -1
- package/dist/command/index.d.ts.map +1 -0
- package/dist/command/index.js +45 -6
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +1334 -1318
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +75 -71
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +1337 -1321
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +1337 -1321
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts.map +1 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/fake/index.d.ts.map +1 -0
- package/dist/file/index.d.ts.map +1 -0
- package/dist/lock/core/index.d.ts.map +1 -0
- package/dist/lock/redis/index.d.ts.map +1 -0
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +820 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +978 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/orm/index.d.ts +180 -107
- package/dist/orm/index.d.ts.map +1 -0
- package/dist/orm/index.js +260 -174
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +4 -4
- package/dist/queue/core/index.d.ts.map +1 -0
- package/dist/queue/redis/index.d.ts.map +1 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/server/auth/index.d.ts +155 -155
- package/dist/server/auth/index.d.ts.map +1 -0
- package/dist/server/cache/index.d.ts.map +1 -0
- package/dist/server/compress/index.d.ts.map +1 -0
- package/dist/server/cookies/index.d.ts.map +1 -0
- package/dist/server/core/index.d.ts.map +1 -0
- package/dist/server/cors/index.d.ts.map +1 -0
- package/dist/server/health/index.d.ts.map +1 -0
- package/dist/server/helmet/index.d.ts.map +1 -0
- package/dist/server/links/index.d.ts +33 -33
- package/dist/server/links/index.d.ts.map +1 -0
- package/dist/server/metrics/index.d.ts.map +1 -0
- package/dist/server/multipart/index.d.ts.map +1 -0
- package/dist/server/proxy/index.d.ts.map +1 -0
- package/dist/server/rate-limit/index.d.ts.map +1 -0
- package/dist/server/security/index.d.ts +9 -9
- package/dist/server/security/index.d.ts.map +1 -0
- package/dist/server/static/index.d.ts.map +1 -0
- package/dist/server/swagger/index.d.ts.map +1 -0
- package/dist/sms/index.d.ts.map +1 -0
- package/dist/thread/index.d.ts.map +1 -0
- package/dist/topic/core/index.d.ts.map +1 -0
- package/dist/topic/redis/index.d.ts.map +1 -0
- package/dist/vite/index.d.ts +10 -2
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +36 -14
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts.map +1 -0
- package/package.json +9 -4
- package/src/cli/apps/AlephaCli.ts +2 -0
- package/src/cli/apps/AlephaPackageBuilderCli.ts +12 -8
- package/src/cli/assets/mainTs.ts +9 -10
- package/src/cli/commands/ChangelogCommands.ts +389 -0
- package/src/cli/commands/DrizzleCommands.ts +204 -4
- package/src/cli/commands/ViteCommands.ts +26 -16
- package/src/cli/services/AlephaCliUtils.ts +23 -150
- package/src/command/providers/CliProvider.ts +76 -5
- package/src/core/providers/SchemaValidator.ts +23 -1
- package/src/mcp/errors/McpError.ts +72 -0
- package/src/mcp/helpers/jsonrpc.ts +163 -0
- package/src/mcp/index.ts +132 -0
- package/src/mcp/interfaces/McpTypes.ts +248 -0
- package/src/mcp/primitives/$prompt.ts +188 -0
- package/src/mcp/primitives/$resource.ts +171 -0
- package/src/mcp/primitives/$tool.ts +285 -0
- package/src/mcp/providers/McpServerProvider.ts +382 -0
- package/src/mcp/transports/SseMcpTransport.ts +172 -0
- package/src/mcp/transports/StdioMcpTransport.ts +126 -0
- package/src/orm/index.ts +12 -0
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +164 -0
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +3 -1
- package/src/vite/plugins/viteAlephaBuild.ts +8 -2
- package/src/vite/plugins/viteAlephaDev.ts +6 -2
- package/src/vite/tasks/buildServer.ts +1 -1
- package/src/vite/tasks/generateCloudflare.ts +43 -15
- package/src/vite/tasks/runAlepha.ts +1 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { $inject, Alepha } from "alepha";
|
|
2
|
+
import { $logger } from "alepha/logger";
|
|
3
|
+
import {
|
|
4
|
+
McpError,
|
|
5
|
+
McpMethodNotFoundError,
|
|
6
|
+
McpPromptNotFoundError,
|
|
7
|
+
McpResourceNotFoundError,
|
|
8
|
+
McpToolNotFoundError,
|
|
9
|
+
} from "../errors/McpError.ts";
|
|
10
|
+
import {
|
|
11
|
+
createErrorResponse,
|
|
12
|
+
createInternalError,
|
|
13
|
+
createResponse,
|
|
14
|
+
MCP_PROTOCOL_VERSION,
|
|
15
|
+
} from "../helpers/jsonrpc.ts";
|
|
16
|
+
import type {
|
|
17
|
+
JsonRpcRequest,
|
|
18
|
+
JsonRpcResponse,
|
|
19
|
+
McpCapabilities,
|
|
20
|
+
McpContent,
|
|
21
|
+
McpContext,
|
|
22
|
+
McpInitializeResult,
|
|
23
|
+
McpPromptDescriptor,
|
|
24
|
+
McpPromptGetResult,
|
|
25
|
+
McpPromptMessage,
|
|
26
|
+
McpResourceContent,
|
|
27
|
+
McpResourceDescriptor,
|
|
28
|
+
McpResourceReadResult,
|
|
29
|
+
McpServerInfo,
|
|
30
|
+
McpToolCallResult,
|
|
31
|
+
McpToolDescriptor,
|
|
32
|
+
} from "../interfaces/McpTypes.ts";
|
|
33
|
+
import type { PromptPrimitive } from "../primitives/$prompt.ts";
|
|
34
|
+
import type { ResourcePrimitive } from "../primitives/$resource.ts";
|
|
35
|
+
import type { ToolPrimitive } from "../primitives/$tool.ts";
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Core MCP server provider that handles protocol messages.
|
|
41
|
+
*
|
|
42
|
+
* This provider maintains registries of tools, resources, and prompts,
|
|
43
|
+
* and routes incoming JSON-RPC requests to the appropriate handlers.
|
|
44
|
+
*
|
|
45
|
+
* It is transport-agnostic - actual communication is handled by
|
|
46
|
+
* transport providers like StdioMcpTransport or SseMcpTransport.
|
|
47
|
+
*/
|
|
48
|
+
export class McpServerProvider {
|
|
49
|
+
protected readonly log = $logger();
|
|
50
|
+
protected readonly alepha = $inject(Alepha);
|
|
51
|
+
|
|
52
|
+
protected readonly tools = new Map<string, ToolPrimitive<any>>();
|
|
53
|
+
protected readonly resources = new Map<string, ResourcePrimitive>();
|
|
54
|
+
protected readonly prompts = new Map<string, PromptPrimitive<any>>();
|
|
55
|
+
|
|
56
|
+
protected initialized = false;
|
|
57
|
+
|
|
58
|
+
protected serverInfo: McpServerInfo = {
|
|
59
|
+
name: "alepha-mcp",
|
|
60
|
+
version: "1.0.0",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
64
|
+
// Registration Methods
|
|
65
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Register a tool with the MCP server.
|
|
69
|
+
*/
|
|
70
|
+
public registerTool(tool: ToolPrimitive<any>): void {
|
|
71
|
+
this.log.trace(`Registering MCP tool: ${tool.name}`);
|
|
72
|
+
this.tools.set(tool.name, tool);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Register a resource with the MCP server.
|
|
77
|
+
*/
|
|
78
|
+
public registerResource(resource: ResourcePrimitive): void {
|
|
79
|
+
this.log.trace(`Registering MCP resource: ${resource.uri}`);
|
|
80
|
+
this.resources.set(resource.uri, resource);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Register a prompt with the MCP server.
|
|
85
|
+
*/
|
|
86
|
+
public registerPrompt(prompt: PromptPrimitive<any>): void {
|
|
87
|
+
this.log.trace(`Registering MCP prompt: ${prompt.name}`);
|
|
88
|
+
this.prompts.set(prompt.name, prompt);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
92
|
+
// Getters
|
|
93
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get the server capabilities based on registered primitives.
|
|
97
|
+
*/
|
|
98
|
+
public getCapabilities(): McpCapabilities {
|
|
99
|
+
return {
|
|
100
|
+
tools: this.tools.size > 0 ? {} : undefined,
|
|
101
|
+
resources: this.resources.size > 0 ? {} : undefined,
|
|
102
|
+
prompts: this.prompts.size > 0 ? {} : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get all registered tools.
|
|
108
|
+
*/
|
|
109
|
+
public getTools(): ToolPrimitive<any>[] {
|
|
110
|
+
return Array.from(this.tools.values());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get all registered resources.
|
|
115
|
+
*/
|
|
116
|
+
public getResources(): ResourcePrimitive[] {
|
|
117
|
+
return Array.from(this.resources.values());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get all registered prompts.
|
|
122
|
+
*/
|
|
123
|
+
public getPrompts(): PromptPrimitive<any>[] {
|
|
124
|
+
return Array.from(this.prompts.values());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get a tool by name.
|
|
129
|
+
*/
|
|
130
|
+
public getTool(name: string): ToolPrimitive<any> | undefined {
|
|
131
|
+
return this.tools.get(name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get a resource by URI.
|
|
136
|
+
*/
|
|
137
|
+
public getResource(uri: string): ResourcePrimitive | undefined {
|
|
138
|
+
return this.resources.get(uri);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get a prompt by name.
|
|
143
|
+
*/
|
|
144
|
+
public getPrompt(name: string): PromptPrimitive<any> | undefined {
|
|
145
|
+
return this.prompts.get(name);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
149
|
+
// Message Handling
|
|
150
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Handle an incoming JSON-RPC request.
|
|
154
|
+
*
|
|
155
|
+
* @param request - The parsed JSON-RPC request
|
|
156
|
+
* @param context - Optional context from the transport layer (headers, auth, etc.)
|
|
157
|
+
* @returns The JSON-RPC response, or null for notifications
|
|
158
|
+
*/
|
|
159
|
+
public async handleMessage(
|
|
160
|
+
request: JsonRpcRequest,
|
|
161
|
+
context?: McpContext,
|
|
162
|
+
): Promise<JsonRpcResponse | null> {
|
|
163
|
+
const id = request.id;
|
|
164
|
+
|
|
165
|
+
// Notifications have no id and expect no response
|
|
166
|
+
if (id === undefined) {
|
|
167
|
+
await this.handleNotification(request);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const result = await this.handleRequest(request, context);
|
|
173
|
+
return createResponse(id, result);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
this.log.error("MCP request failed", error);
|
|
176
|
+
// Preserve error code from McpError instances
|
|
177
|
+
if (error instanceof McpError) {
|
|
178
|
+
return createErrorResponse(id, {
|
|
179
|
+
code: error.code,
|
|
180
|
+
message: error.message,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return createErrorResponse(
|
|
184
|
+
id,
|
|
185
|
+
createInternalError((error as Error).message),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Handle a JSON-RPC request that expects a response.
|
|
192
|
+
*/
|
|
193
|
+
protected async handleRequest(
|
|
194
|
+
request: JsonRpcRequest,
|
|
195
|
+
context?: McpContext,
|
|
196
|
+
): Promise<unknown> {
|
|
197
|
+
const { method, params = {} } = request;
|
|
198
|
+
|
|
199
|
+
switch (method) {
|
|
200
|
+
case "initialize":
|
|
201
|
+
return this.handleInitialize(params);
|
|
202
|
+
case "ping":
|
|
203
|
+
return this.handlePing();
|
|
204
|
+
case "tools/list":
|
|
205
|
+
return this.handleToolsList();
|
|
206
|
+
case "tools/call":
|
|
207
|
+
return this.handleToolsCall(params, context);
|
|
208
|
+
case "resources/list":
|
|
209
|
+
return this.handleResourcesList();
|
|
210
|
+
case "resources/read":
|
|
211
|
+
return this.handleResourcesRead(params, context);
|
|
212
|
+
case "prompts/list":
|
|
213
|
+
return this.handlePromptsList();
|
|
214
|
+
case "prompts/get":
|
|
215
|
+
return this.handlePromptsGet(params, context);
|
|
216
|
+
default:
|
|
217
|
+
throw new McpMethodNotFoundError(method);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Handle a notification (no response expected).
|
|
223
|
+
*/
|
|
224
|
+
protected async handleNotification(request: JsonRpcRequest): Promise<void> {
|
|
225
|
+
const { method } = request;
|
|
226
|
+
|
|
227
|
+
switch (method) {
|
|
228
|
+
case "notifications/initialized":
|
|
229
|
+
this.log.debug("MCP client initialized");
|
|
230
|
+
break;
|
|
231
|
+
case "notifications/cancelled":
|
|
232
|
+
this.log.debug("MCP request cancelled", request.params);
|
|
233
|
+
break;
|
|
234
|
+
default:
|
|
235
|
+
this.log.debug(`Unknown MCP notification: ${method}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
240
|
+
// Protocol Handlers
|
|
241
|
+
// -----------------------------------------------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
protected handleInitialize(
|
|
244
|
+
params: Record<string, unknown>,
|
|
245
|
+
): McpInitializeResult {
|
|
246
|
+
this.log.info("MCP client initializing", {
|
|
247
|
+
clientInfo: params.clientInfo,
|
|
248
|
+
protocolVersion: params.protocolVersion,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
this.initialized = true;
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
255
|
+
capabilities: this.getCapabilities(),
|
|
256
|
+
serverInfo: this.serverInfo,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
protected handlePing(): Record<string, never> {
|
|
261
|
+
return {};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
protected handleToolsList(): { tools: McpToolDescriptor[] } {
|
|
265
|
+
return {
|
|
266
|
+
tools: Array.from(this.tools.values()).map((t) => t.toDescriptor()),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
protected async handleToolsCall(
|
|
271
|
+
params: Record<string, unknown>,
|
|
272
|
+
context?: McpContext,
|
|
273
|
+
): Promise<McpToolCallResult> {
|
|
274
|
+
const name = params.name as string;
|
|
275
|
+
const args = (params.arguments ?? {}) as Record<string, unknown>;
|
|
276
|
+
|
|
277
|
+
const tool = this.tools.get(name);
|
|
278
|
+
if (!tool) {
|
|
279
|
+
throw new McpToolNotFoundError(name);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
const result = await tool.execute(args, context);
|
|
284
|
+
|
|
285
|
+
const content: McpContent[] = [
|
|
286
|
+
{
|
|
287
|
+
type: "text",
|
|
288
|
+
text:
|
|
289
|
+
typeof result === "string"
|
|
290
|
+
? result
|
|
291
|
+
: JSON.stringify(result ?? null),
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
return { content };
|
|
296
|
+
} catch (error) {
|
|
297
|
+
return {
|
|
298
|
+
content: [
|
|
299
|
+
{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: `Error: ${(error as Error).message}`,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
isError: true,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
protected handleResourcesList(): { resources: McpResourceDescriptor[] } {
|
|
310
|
+
return {
|
|
311
|
+
resources: Array.from(this.resources.values()).map((r) =>
|
|
312
|
+
r.toDescriptor(),
|
|
313
|
+
),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
protected async handleResourcesRead(
|
|
318
|
+
params: Record<string, unknown>,
|
|
319
|
+
context?: McpContext,
|
|
320
|
+
): Promise<McpResourceReadResult> {
|
|
321
|
+
const uri = params.uri as string;
|
|
322
|
+
|
|
323
|
+
const resource = this.resources.get(uri);
|
|
324
|
+
if (!resource) {
|
|
325
|
+
throw new McpResourceNotFoundError(uri);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const content = await resource.read(context);
|
|
329
|
+
|
|
330
|
+
const resourceContent: McpResourceContent = {
|
|
331
|
+
uri,
|
|
332
|
+
mimeType: resource.mimeType,
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
if (content.text !== undefined) {
|
|
336
|
+
resourceContent.text = content.text;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (content.blob !== undefined) {
|
|
340
|
+
// Convert binary to base64 for transport
|
|
341
|
+
resourceContent.blob = Buffer.from(content.blob).toString("base64");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
contents: [resourceContent],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
protected handlePromptsList(): { prompts: McpPromptDescriptor[] } {
|
|
350
|
+
return {
|
|
351
|
+
prompts: Array.from(this.prompts.values()).map((p) => p.toDescriptor()),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
protected async handlePromptsGet(
|
|
356
|
+
params: Record<string, unknown>,
|
|
357
|
+
context?: McpContext,
|
|
358
|
+
): Promise<McpPromptGetResult> {
|
|
359
|
+
const name = params.name as string;
|
|
360
|
+
const args = (params.arguments ?? {}) as Record<string, string>;
|
|
361
|
+
|
|
362
|
+
const prompt = this.prompts.get(name);
|
|
363
|
+
if (!prompt) {
|
|
364
|
+
throw new McpPromptNotFoundError(name);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const messages = await prompt.get(args, context);
|
|
368
|
+
|
|
369
|
+
const mcpMessages: McpPromptMessage[] = messages.map((msg) => ({
|
|
370
|
+
role: msg.role,
|
|
371
|
+
content: {
|
|
372
|
+
type: "text" as const,
|
|
373
|
+
text: msg.content,
|
|
374
|
+
},
|
|
375
|
+
}));
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
description: prompt.description,
|
|
379
|
+
messages: mcpMessages,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { $env, $inject, t } from "alepha";
|
|
2
|
+
import { $logger } from "alepha/logger";
|
|
3
|
+
import { $route } from "alepha/server";
|
|
4
|
+
import {
|
|
5
|
+
createErrorResponse,
|
|
6
|
+
createNotification,
|
|
7
|
+
createParseError,
|
|
8
|
+
JsonRpcParseError,
|
|
9
|
+
parseMessage,
|
|
10
|
+
} from "../helpers/jsonrpc.ts";
|
|
11
|
+
import type { McpContext } from "../interfaces/McpTypes.ts";
|
|
12
|
+
import { McpServerProvider } from "../providers/McpServerProvider.ts";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
const envSchema = t.object({
|
|
17
|
+
MCP_SSE_PATH: t.text({
|
|
18
|
+
description: "Path for MCP SSE endpoint",
|
|
19
|
+
default: "/mcp",
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* SSE (Server-Sent Events) transport for MCP communication.
|
|
27
|
+
*
|
|
28
|
+
* This transport uses HTTP with SSE for server-to-client messages
|
|
29
|
+
* and POST requests for client-to-server messages.
|
|
30
|
+
*
|
|
31
|
+
* Endpoints:
|
|
32
|
+
* - GET /mcp - SSE stream for server events
|
|
33
|
+
* - POST /mcp - JSON-RPC request endpoint
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { Alepha, run } from "alepha";
|
|
38
|
+
* import { AlephaServer } from "alepha/server";
|
|
39
|
+
* import { AlephaMcp, AlephaMcpSse } from "alepha/mcp";
|
|
40
|
+
*
|
|
41
|
+
* class MyTools {
|
|
42
|
+
* // ... tool definitions
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* run(
|
|
46
|
+
* Alepha.create()
|
|
47
|
+
* .with(AlephaServer)
|
|
48
|
+
* .with(AlephaMcp)
|
|
49
|
+
* .with(AlephaMcpSse)
|
|
50
|
+
* .with(MyTools)
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export class SseMcpTransport {
|
|
55
|
+
protected readonly log = $logger();
|
|
56
|
+
protected readonly env = $env(envSchema);
|
|
57
|
+
protected readonly mcpServer = $inject(McpServerProvider);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* SSE endpoint for server-to-client messages.
|
|
61
|
+
*
|
|
62
|
+
* Returns a text/event-stream response with server capabilities
|
|
63
|
+
* and keeps the connection open for notifications.
|
|
64
|
+
*/
|
|
65
|
+
sse = $route({
|
|
66
|
+
method: "GET",
|
|
67
|
+
path: this.env.MCP_SSE_PATH,
|
|
68
|
+
handler: async (request) => {
|
|
69
|
+
this.log.debug("MCP SSE connection established");
|
|
70
|
+
|
|
71
|
+
const encoder = new TextEncoder();
|
|
72
|
+
|
|
73
|
+
// Create SSE stream
|
|
74
|
+
const stream = new ReadableStream({
|
|
75
|
+
start: (controller) => {
|
|
76
|
+
// Send initial endpoint info
|
|
77
|
+
const endpointEvent = this.formatSseEvent(
|
|
78
|
+
"endpoint",
|
|
79
|
+
`${this.env.MCP_SSE_PATH}`,
|
|
80
|
+
);
|
|
81
|
+
controller.enqueue(encoder.encode(endpointEvent));
|
|
82
|
+
|
|
83
|
+
// Send capabilities notification
|
|
84
|
+
const capabilitiesNotification = createNotification(
|
|
85
|
+
"notifications/capabilities",
|
|
86
|
+
{ capabilities: this.mcpServer.getCapabilities() },
|
|
87
|
+
);
|
|
88
|
+
const capabilitiesEvent = this.formatSseEvent(
|
|
89
|
+
"message",
|
|
90
|
+
JSON.stringify(capabilitiesNotification),
|
|
91
|
+
);
|
|
92
|
+
controller.enqueue(encoder.encode(capabilitiesEvent));
|
|
93
|
+
},
|
|
94
|
+
cancel: () => {
|
|
95
|
+
this.log.debug("MCP SSE connection closed");
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
request.reply.status = 200;
|
|
100
|
+
request.reply.headers = {
|
|
101
|
+
"content-type": "text/event-stream",
|
|
102
|
+
"cache-control": "no-cache",
|
|
103
|
+
connection: "keep-alive",
|
|
104
|
+
};
|
|
105
|
+
request.reply.body = stream;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* POST endpoint for client-to-server JSON-RPC messages.
|
|
111
|
+
*/
|
|
112
|
+
message = $route({
|
|
113
|
+
method: "POST",
|
|
114
|
+
path: this.env.MCP_SSE_PATH,
|
|
115
|
+
secure: false,
|
|
116
|
+
schema: {
|
|
117
|
+
body: t.json(),
|
|
118
|
+
},
|
|
119
|
+
handler: async (request) => {
|
|
120
|
+
try {
|
|
121
|
+
const body =
|
|
122
|
+
typeof request.body === "string"
|
|
123
|
+
? request.body
|
|
124
|
+
: JSON.stringify(request.body);
|
|
125
|
+
|
|
126
|
+
this.log.debug("MCP request body", {
|
|
127
|
+
body,
|
|
128
|
+
bodyType: typeof request.body,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const rpcRequest = parseMessage(body);
|
|
132
|
+
|
|
133
|
+
// Build context from request headers
|
|
134
|
+
const context: McpContext = {
|
|
135
|
+
headers: request.headers as Record<
|
|
136
|
+
string,
|
|
137
|
+
string | string[] | undefined
|
|
138
|
+
>,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const response = await this.mcpServer.handleMessage(
|
|
142
|
+
rpcRequest,
|
|
143
|
+
context,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
request.reply.headers["content-type"] = "application/json";
|
|
147
|
+
request.reply.body = response ? JSON.stringify(response) : "";
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error instanceof JsonRpcParseError) {
|
|
150
|
+
request.reply.status = 400;
|
|
151
|
+
request.reply.headers["content-type"] = "application/json";
|
|
152
|
+
request.reply.body = JSON.stringify(
|
|
153
|
+
createErrorResponse(0, createParseError(error.message)),
|
|
154
|
+
);
|
|
155
|
+
} else {
|
|
156
|
+
this.log.error("Failed to process MCP message", error);
|
|
157
|
+
request.reply.status = 500;
|
|
158
|
+
request.reply.body = JSON.stringify({
|
|
159
|
+
error: (error as Error).message,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Format a message as an SSE event.
|
|
168
|
+
*/
|
|
169
|
+
protected formatSseEvent(event: string, data: string): string {
|
|
170
|
+
return `event: ${event}\ndata: ${data}\n\n`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
import { $hook, $inject } from "alepha";
|
|
3
|
+
import { $logger } from "alepha/logger";
|
|
4
|
+
import {
|
|
5
|
+
createErrorResponse,
|
|
6
|
+
createParseError,
|
|
7
|
+
JsonRpcParseError,
|
|
8
|
+
parseMessage,
|
|
9
|
+
} from "../helpers/jsonrpc.ts";
|
|
10
|
+
import { McpServerProvider } from "../providers/McpServerProvider.ts";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Stdio transport for MCP communication.
|
|
16
|
+
*
|
|
17
|
+
* This transport uses stdin/stdout for JSON-RPC message exchange,
|
|
18
|
+
* which is the standard transport for local MCP servers.
|
|
19
|
+
*
|
|
20
|
+
* Messages are newline-delimited JSON objects.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { Alepha, run } from "alepha";
|
|
25
|
+
* import { AlephaMcp, AlephaMcpStdio } from "alepha/mcp";
|
|
26
|
+
*
|
|
27
|
+
* class MyTools {
|
|
28
|
+
* // ... tool definitions
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* run(
|
|
32
|
+
* Alepha.create()
|
|
33
|
+
* .with(AlephaMcp)
|
|
34
|
+
* .with(AlephaMcpStdio)
|
|
35
|
+
* .with(MyTools)
|
|
36
|
+
* );
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class StdioMcpTransport {
|
|
40
|
+
protected readonly log = $logger();
|
|
41
|
+
protected readonly mcpServer = $inject(McpServerProvider);
|
|
42
|
+
|
|
43
|
+
protected rl?: readline.Interface;
|
|
44
|
+
protected started = false;
|
|
45
|
+
|
|
46
|
+
onStart = $hook({
|
|
47
|
+
on: "start",
|
|
48
|
+
handler: () => this.start(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
onStop = $hook({
|
|
52
|
+
on: "stop",
|
|
53
|
+
handler: () => this.stop(),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Start the stdio transport.
|
|
58
|
+
*/
|
|
59
|
+
protected start(): void {
|
|
60
|
+
if (this.started) return;
|
|
61
|
+
this.started = true;
|
|
62
|
+
|
|
63
|
+
this.rl = readline.createInterface({
|
|
64
|
+
input: process.stdin,
|
|
65
|
+
output: process.stdout,
|
|
66
|
+
terminal: false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.rl.on("line", (line) => this.handleLine(line));
|
|
70
|
+
this.rl.on("close", () => this.handleClose());
|
|
71
|
+
|
|
72
|
+
this.log.info("MCP stdio transport started");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Stop the stdio transport.
|
|
77
|
+
*/
|
|
78
|
+
protected stop(): void {
|
|
79
|
+
if (!this.started) return;
|
|
80
|
+
this.started = false;
|
|
81
|
+
|
|
82
|
+
this.rl?.close();
|
|
83
|
+
this.rl = undefined;
|
|
84
|
+
|
|
85
|
+
this.log.info("MCP stdio transport stopped");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handle an incoming line from stdin.
|
|
90
|
+
*/
|
|
91
|
+
protected async handleLine(line: string): Promise<void> {
|
|
92
|
+
// Skip empty lines
|
|
93
|
+
if (!line.trim()) return;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const request = parseMessage(line);
|
|
97
|
+
const response = await this.mcpServer.handleMessage(request);
|
|
98
|
+
|
|
99
|
+
if (response) {
|
|
100
|
+
this.send(response);
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof JsonRpcParseError) {
|
|
104
|
+
// Send parse error response
|
|
105
|
+
this.send(createErrorResponse(0, createParseError(error.message)));
|
|
106
|
+
} else {
|
|
107
|
+
this.log.error("Failed to process MCP message", error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handle stdin close event.
|
|
114
|
+
*/
|
|
115
|
+
protected handleClose(): void {
|
|
116
|
+
this.log.debug("MCP stdio input closed");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Send a message to stdout.
|
|
121
|
+
*/
|
|
122
|
+
protected send(message: object): void {
|
|
123
|
+
const json = JSON.stringify(message);
|
|
124
|
+
process.stdout.write(json + "\n");
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/orm/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { $entity } from "./primitives/$entity.ts";
|
|
|
5
5
|
import { $repository } from "./primitives/$repository.ts";
|
|
6
6
|
import { $sequence } from "./primitives/$sequence.ts";
|
|
7
7
|
import { DrizzleKitProvider } from "./providers/DrizzleKitProvider.ts";
|
|
8
|
+
import { CloudflareD1Provider } from "./providers/drivers/CloudflareD1Provider.ts";
|
|
8
9
|
import { DatabaseProvider } from "./providers/drivers/DatabaseProvider.ts";
|
|
9
10
|
import { NodePostgresProvider } from "./providers/drivers/NodePostgresProvider.ts";
|
|
10
11
|
import { NodeSqliteProvider } from "./providers/drivers/NodeSqliteProvider.ts";
|
|
@@ -113,6 +114,7 @@ export * from "./primitives/$repository.ts";
|
|
|
113
114
|
export * from "./primitives/$sequence.ts";
|
|
114
115
|
export * from "./primitives/$transaction.ts";
|
|
115
116
|
export * from "./providers/DrizzleKitProvider.ts";
|
|
117
|
+
export * from "./providers/drivers/CloudflareD1Provider.ts";
|
|
116
118
|
export * from "./providers/drivers/DatabaseProvider.ts";
|
|
117
119
|
export * from "./providers/drivers/NodePostgresProvider.ts";
|
|
118
120
|
export * from "./providers/drivers/NodeSqliteProvider.ts";
|
|
@@ -174,6 +176,7 @@ export const AlephaPostgres = $module({
|
|
|
174
176
|
NodePostgresProvider,
|
|
175
177
|
PglitePostgresProvider,
|
|
176
178
|
NodeSqliteProvider,
|
|
179
|
+
CloudflareD1Provider,
|
|
177
180
|
SqliteModelBuilder,
|
|
178
181
|
PostgresModelBuilder,
|
|
179
182
|
DrizzleKitProvider,
|
|
@@ -200,6 +203,15 @@ export const AlephaPostgres = $module({
|
|
|
200
203
|
const isMemory = url?.includes(":memory:");
|
|
201
204
|
const isFile = !!url && !isPostgres && !isMemory;
|
|
202
205
|
|
|
206
|
+
if (url?.startsWith("cloudflare-d1:")) {
|
|
207
|
+
alepha.with({
|
|
208
|
+
optional: true,
|
|
209
|
+
provide: DatabaseProvider,
|
|
210
|
+
use: CloudflareD1Provider,
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
203
215
|
if (hasPGlite && (isMemory || isFile || !url) && !isSqlite) {
|
|
204
216
|
alepha.with({
|
|
205
217
|
optional: true,
|