@dangao/bun-server 1.12.0 → 2.0.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.
Files changed (223) hide show
  1. package/README.md +32 -0
  2. package/dist/ai/ai-module.d.ts +24 -0
  3. package/dist/ai/ai-module.d.ts.map +1 -0
  4. package/dist/ai/decorators.d.ts +25 -0
  5. package/dist/ai/decorators.d.ts.map +1 -0
  6. package/dist/ai/errors.d.ts +39 -0
  7. package/dist/ai/errors.d.ts.map +1 -0
  8. package/dist/ai/index.d.ts +12 -0
  9. package/dist/ai/index.d.ts.map +1 -0
  10. package/dist/ai/providers/anthropic-provider.d.ts +23 -0
  11. package/dist/ai/providers/anthropic-provider.d.ts.map +1 -0
  12. package/dist/ai/providers/google-provider.d.ts +20 -0
  13. package/dist/ai/providers/google-provider.d.ts.map +1 -0
  14. package/dist/ai/providers/ollama-provider.d.ts +17 -0
  15. package/dist/ai/providers/ollama-provider.d.ts.map +1 -0
  16. package/dist/ai/providers/openai-provider.d.ts +28 -0
  17. package/dist/ai/providers/openai-provider.d.ts.map +1 -0
  18. package/dist/ai/service.d.ts +40 -0
  19. package/dist/ai/service.d.ts.map +1 -0
  20. package/dist/ai/tools/tool-executor.d.ts +15 -0
  21. package/dist/ai/tools/tool-executor.d.ts.map +1 -0
  22. package/dist/ai/tools/tool-registry.d.ts +39 -0
  23. package/dist/ai/tools/tool-registry.d.ts.map +1 -0
  24. package/dist/ai/types.d.ts +134 -0
  25. package/dist/ai/types.d.ts.map +1 -0
  26. package/dist/ai-guard/ai-guard-module.d.ts +18 -0
  27. package/dist/ai-guard/ai-guard-module.d.ts.map +1 -0
  28. package/dist/ai-guard/decorators.d.ts +16 -0
  29. package/dist/ai-guard/decorators.d.ts.map +1 -0
  30. package/dist/ai-guard/detectors/content-moderator.d.ts +26 -0
  31. package/dist/ai-guard/detectors/content-moderator.d.ts.map +1 -0
  32. package/dist/ai-guard/detectors/injection-detector.d.ts +13 -0
  33. package/dist/ai-guard/detectors/injection-detector.d.ts.map +1 -0
  34. package/dist/ai-guard/detectors/pii-detector.d.ts +11 -0
  35. package/dist/ai-guard/detectors/pii-detector.d.ts.map +1 -0
  36. package/dist/ai-guard/index.d.ts +8 -0
  37. package/dist/ai-guard/index.d.ts.map +1 -0
  38. package/dist/ai-guard/service.d.ts +21 -0
  39. package/dist/ai-guard/service.d.ts.map +1 -0
  40. package/dist/ai-guard/types.d.ts +59 -0
  41. package/dist/ai-guard/types.d.ts.map +1 -0
  42. package/dist/conversation/conversation-module.d.ts +25 -0
  43. package/dist/conversation/conversation-module.d.ts.map +1 -0
  44. package/dist/conversation/decorators.d.ts +28 -0
  45. package/dist/conversation/decorators.d.ts.map +1 -0
  46. package/dist/conversation/index.d.ts +8 -0
  47. package/dist/conversation/index.d.ts.map +1 -0
  48. package/dist/conversation/service.d.ts +43 -0
  49. package/dist/conversation/service.d.ts.map +1 -0
  50. package/dist/conversation/stores/database-store.d.ts +46 -0
  51. package/dist/conversation/stores/database-store.d.ts.map +1 -0
  52. package/dist/conversation/stores/memory-store.d.ts +17 -0
  53. package/dist/conversation/stores/memory-store.d.ts.map +1 -0
  54. package/dist/conversation/stores/redis-store.d.ts +39 -0
  55. package/dist/conversation/stores/redis-store.d.ts.map +1 -0
  56. package/dist/conversation/types.d.ts +64 -0
  57. package/dist/conversation/types.d.ts.map +1 -0
  58. package/dist/core/cluster.d.ts +42 -3
  59. package/dist/core/cluster.d.ts.map +1 -1
  60. package/dist/core/index.d.ts +1 -1
  61. package/dist/core/index.d.ts.map +1 -1
  62. package/dist/core/server.d.ts.map +1 -1
  63. package/dist/embedding/embedding-module.d.ts +20 -0
  64. package/dist/embedding/embedding-module.d.ts.map +1 -0
  65. package/dist/embedding/index.d.ts +6 -0
  66. package/dist/embedding/index.d.ts.map +1 -0
  67. package/dist/embedding/providers/ollama-embedding-provider.d.ts +18 -0
  68. package/dist/embedding/providers/ollama-embedding-provider.d.ts.map +1 -0
  69. package/dist/embedding/providers/openai-embedding-provider.d.ts +18 -0
  70. package/dist/embedding/providers/openai-embedding-provider.d.ts.map +1 -0
  71. package/dist/embedding/service.d.ts +27 -0
  72. package/dist/embedding/service.d.ts.map +1 -0
  73. package/dist/embedding/types.d.ts +25 -0
  74. package/dist/embedding/types.d.ts.map +1 -0
  75. package/dist/index.d.ts +9 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +2870 -88
  78. package/dist/mcp/decorators.d.ts +42 -0
  79. package/dist/mcp/decorators.d.ts.map +1 -0
  80. package/dist/mcp/index.d.ts +6 -0
  81. package/dist/mcp/index.d.ts.map +1 -0
  82. package/dist/mcp/mcp-module.d.ts +22 -0
  83. package/dist/mcp/mcp-module.d.ts.map +1 -0
  84. package/dist/mcp/registry.d.ts +23 -0
  85. package/dist/mcp/registry.d.ts.map +1 -0
  86. package/dist/mcp/server.d.ts +29 -0
  87. package/dist/mcp/server.d.ts.map +1 -0
  88. package/dist/mcp/types.d.ts +60 -0
  89. package/dist/mcp/types.d.ts.map +1 -0
  90. package/dist/prompt/index.d.ts +6 -0
  91. package/dist/prompt/index.d.ts.map +1 -0
  92. package/dist/prompt/prompt-module.d.ts +23 -0
  93. package/dist/prompt/prompt-module.d.ts.map +1 -0
  94. package/dist/prompt/service.d.ts +47 -0
  95. package/dist/prompt/service.d.ts.map +1 -0
  96. package/dist/prompt/stores/file-store.d.ts +36 -0
  97. package/dist/prompt/stores/file-store.d.ts.map +1 -0
  98. package/dist/prompt/stores/memory-store.d.ts +17 -0
  99. package/dist/prompt/stores/memory-store.d.ts.map +1 -0
  100. package/dist/prompt/types.d.ts +68 -0
  101. package/dist/prompt/types.d.ts.map +1 -0
  102. package/dist/rag/chunkers/markdown-chunker.d.ts +11 -0
  103. package/dist/rag/chunkers/markdown-chunker.d.ts.map +1 -0
  104. package/dist/rag/chunkers/text-chunker.d.ts +11 -0
  105. package/dist/rag/chunkers/text-chunker.d.ts.map +1 -0
  106. package/dist/rag/decorators.d.ts +24 -0
  107. package/dist/rag/decorators.d.ts.map +1 -0
  108. package/dist/rag/index.d.ts +7 -0
  109. package/dist/rag/index.d.ts.map +1 -0
  110. package/dist/rag/rag-module.d.ts +23 -0
  111. package/dist/rag/rag-module.d.ts.map +1 -0
  112. package/dist/rag/service.d.ts +36 -0
  113. package/dist/rag/service.d.ts.map +1 -0
  114. package/dist/rag/types.d.ts +56 -0
  115. package/dist/rag/types.d.ts.map +1 -0
  116. package/dist/vector-store/index.d.ts +6 -0
  117. package/dist/vector-store/index.d.ts.map +1 -0
  118. package/dist/vector-store/stores/memory-store.d.ts +17 -0
  119. package/dist/vector-store/stores/memory-store.d.ts.map +1 -0
  120. package/dist/vector-store/stores/pinecone-store.d.ts +27 -0
  121. package/dist/vector-store/stores/pinecone-store.d.ts.map +1 -0
  122. package/dist/vector-store/stores/qdrant-store.d.ts +29 -0
  123. package/dist/vector-store/stores/qdrant-store.d.ts.map +1 -0
  124. package/dist/vector-store/types.d.ts +60 -0
  125. package/dist/vector-store/types.d.ts.map +1 -0
  126. package/dist/vector-store/vector-store-module.d.ts +20 -0
  127. package/dist/vector-store/vector-store-module.d.ts.map +1 -0
  128. package/docs/ai.md +500 -0
  129. package/docs/best-practices.md +83 -8
  130. package/docs/database.md +23 -0
  131. package/docs/guide.md +90 -27
  132. package/docs/migration.md +81 -7
  133. package/docs/security.md +23 -0
  134. package/docs/zh/ai.md +441 -0
  135. package/docs/zh/best-practices.md +43 -0
  136. package/docs/zh/database.md +23 -0
  137. package/docs/zh/guide.md +40 -1
  138. package/docs/zh/migration.md +39 -0
  139. package/docs/zh/security.md +23 -0
  140. package/package.json +2 -2
  141. package/src/ai/ai-module.ts +62 -0
  142. package/src/ai/decorators.ts +30 -0
  143. package/src/ai/errors.ts +71 -0
  144. package/src/ai/index.ts +11 -0
  145. package/src/ai/providers/anthropic-provider.ts +190 -0
  146. package/src/ai/providers/google-provider.ts +179 -0
  147. package/src/ai/providers/ollama-provider.ts +126 -0
  148. package/src/ai/providers/openai-provider.ts +242 -0
  149. package/src/ai/service.ts +155 -0
  150. package/src/ai/tools/tool-executor.ts +38 -0
  151. package/src/ai/tools/tool-registry.ts +91 -0
  152. package/src/ai/types.ts +145 -0
  153. package/src/ai-guard/ai-guard-module.ts +50 -0
  154. package/src/ai-guard/decorators.ts +21 -0
  155. package/src/ai-guard/detectors/content-moderator.ts +80 -0
  156. package/src/ai-guard/detectors/injection-detector.ts +48 -0
  157. package/src/ai-guard/detectors/pii-detector.ts +64 -0
  158. package/src/ai-guard/index.ts +7 -0
  159. package/src/ai-guard/service.ts +100 -0
  160. package/src/ai-guard/types.ts +61 -0
  161. package/src/conversation/conversation-module.ts +63 -0
  162. package/src/conversation/decorators.ts +47 -0
  163. package/src/conversation/index.ts +7 -0
  164. package/src/conversation/service.ts +133 -0
  165. package/src/conversation/stores/database-store.ts +125 -0
  166. package/src/conversation/stores/memory-store.ts +57 -0
  167. package/src/conversation/stores/redis-store.ts +101 -0
  168. package/src/conversation/types.ts +68 -0
  169. package/src/core/cluster.ts +239 -46
  170. package/src/core/index.ts +1 -1
  171. package/src/core/server.ts +91 -78
  172. package/src/embedding/embedding-module.ts +52 -0
  173. package/src/embedding/index.ts +5 -0
  174. package/src/embedding/providers/ollama-embedding-provider.ts +39 -0
  175. package/src/embedding/providers/openai-embedding-provider.ts +47 -0
  176. package/src/embedding/service.ts +55 -0
  177. package/src/embedding/types.ts +27 -0
  178. package/src/index.ts +11 -1
  179. package/src/mcp/decorators.ts +60 -0
  180. package/src/mcp/index.ts +5 -0
  181. package/src/mcp/mcp-module.ts +58 -0
  182. package/src/mcp/registry.ts +72 -0
  183. package/src/mcp/server.ts +164 -0
  184. package/src/mcp/types.ts +63 -0
  185. package/src/prompt/index.ts +5 -0
  186. package/src/prompt/prompt-module.ts +61 -0
  187. package/src/prompt/service.ts +93 -0
  188. package/src/prompt/stores/file-store.ts +135 -0
  189. package/src/prompt/stores/memory-store.ts +82 -0
  190. package/src/prompt/types.ts +84 -0
  191. package/src/rag/chunkers/markdown-chunker.ts +40 -0
  192. package/src/rag/chunkers/text-chunker.ts +30 -0
  193. package/src/rag/decorators.ts +26 -0
  194. package/src/rag/index.ts +6 -0
  195. package/src/rag/rag-module.ts +78 -0
  196. package/src/rag/service.ts +134 -0
  197. package/src/rag/types.ts +47 -0
  198. package/src/vector-store/index.ts +5 -0
  199. package/src/vector-store/stores/memory-store.ts +69 -0
  200. package/src/vector-store/stores/pinecone-store.ts +123 -0
  201. package/src/vector-store/stores/qdrant-store.ts +147 -0
  202. package/src/vector-store/types.ts +77 -0
  203. package/src/vector-store/vector-store-module.ts +50 -0
  204. package/tests/ai/ai-module.test.ts +46 -0
  205. package/tests/ai/ai-service.test.ts +91 -0
  206. package/tests/ai/tool-registry.test.ts +57 -0
  207. package/tests/ai-guard/ai-guard-module.test.ts +23 -0
  208. package/tests/ai-guard/content-moderator.test.ts +65 -0
  209. package/tests/ai-guard/pii-detector.test.ts +41 -0
  210. package/tests/conversation/conversation-module.test.ts +26 -0
  211. package/tests/conversation/conversation-service.test.ts +64 -0
  212. package/tests/conversation/memory-store.test.ts +68 -0
  213. package/tests/core/cluster.test.ts +45 -1
  214. package/tests/embedding/embedding-service.test.ts +55 -0
  215. package/tests/mcp/mcp-server.test.ts +85 -0
  216. package/tests/prompt/prompt-module.test.ts +30 -0
  217. package/tests/prompt/prompt-service.test.ts +74 -0
  218. package/tests/rag/chunkers.test.ts +58 -0
  219. package/tests/rag/rag-service.test.ts +66 -0
  220. package/tests/vector-store/memory-vector-store.test.ts +84 -0
  221. package/tests/interceptor/perf/interceptor-performance.test.ts +0 -340
  222. package/tests/perf/optimization.test.ts +0 -182
  223. package/tests/perf/regression.test.ts +0 -120
@@ -0,0 +1,47 @@
1
+ import type { EmbeddingProvider } from '../types';
2
+ import { AiProviderError, AiRateLimitError } from '../../ai/errors';
3
+
4
+ export interface OpenAIEmbeddingProviderConfig {
5
+ apiKey: string;
6
+ /** Default: text-embedding-3-small */
7
+ model?: string;
8
+ baseUrl?: string;
9
+ }
10
+
11
+ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
12
+ public readonly name = 'openai';
13
+ public readonly dimensions: number;
14
+ private readonly apiKey: string;
15
+ private readonly model: string;
16
+ private readonly baseUrl: string;
17
+
18
+ public constructor(config: OpenAIEmbeddingProviderConfig) {
19
+ this.apiKey = config.apiKey;
20
+ this.model = config.model ?? 'text-embedding-3-small';
21
+ this.baseUrl = (config.baseUrl ?? 'https://api.openai.com/v1').replace(/\/$/, '');
22
+ // text-embedding-3-small: 1536, text-embedding-3-large: 3072
23
+ this.dimensions = this.model.includes('large') ? 3072 : 1536;
24
+ }
25
+
26
+ public async embed(text: string): Promise<number[]> {
27
+ const results = await this.embedBatch([text]);
28
+ return results[0]!;
29
+ }
30
+
31
+ public async embedBatch(texts: string[]): Promise<number[][]> {
32
+ const res = await fetch(`${this.baseUrl}/embeddings`, {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ 'Authorization': `Bearer ${this.apiKey}`,
37
+ },
38
+ body: JSON.stringify({ input: texts, model: this.model }),
39
+ });
40
+
41
+ if (res.status === 429) throw new AiRateLimitError('openai-embedding');
42
+ if (!res.ok) throw new AiProviderError(await res.text(), 'openai-embedding', res.status);
43
+
44
+ const data = await res.json() as { data: Array<{ embedding: number[] }> };
45
+ return data.data.map((d) => d.embedding);
46
+ }
47
+ }
@@ -0,0 +1,55 @@
1
+ import { Injectable } from '../di/decorators';
2
+ import { Inject } from '../di/decorators';
3
+ import type { EmbeddingProvider, EmbeddingModuleOptions } from './types';
4
+ import { EMBEDDING_OPTIONS_TOKEN } from './types';
5
+
6
+ /**
7
+ * Embedding service — generates vector embeddings for text using the configured provider.
8
+ */
9
+ @Injectable()
10
+ export class EmbeddingService {
11
+ private readonly provider: EmbeddingProvider;
12
+ private readonly batchSize: number;
13
+
14
+ public constructor(
15
+ @Inject(EMBEDDING_OPTIONS_TOKEN) options: EmbeddingModuleOptions,
16
+ ) {
17
+ this.provider = new options.provider.provider(options.provider.config);
18
+ this.batchSize = options.batchSize ?? 100;
19
+ }
20
+
21
+ /**
22
+ * Generate an embedding vector for a single text
23
+ */
24
+ public async embed(text: string): Promise<number[]> {
25
+ return this.provider.embed(text);
26
+ }
27
+
28
+ /**
29
+ * Generate embedding vectors for multiple texts.
30
+ * Automatically batches requests to stay within provider limits.
31
+ */
32
+ public async embedBatch(texts: string[]): Promise<number[][]> {
33
+ const results: number[][] = [];
34
+ for (let i = 0; i < texts.length; i += this.batchSize) {
35
+ const batch = texts.slice(i, i + this.batchSize);
36
+ const embeddings = await this.provider.embedBatch(batch);
37
+ results.push(...embeddings);
38
+ }
39
+ return results;
40
+ }
41
+
42
+ /**
43
+ * Number of dimensions in embeddings produced by this provider
44
+ */
45
+ public get dimensions(): number {
46
+ return this.provider.dimensions;
47
+ }
48
+
49
+ /**
50
+ * Provider name
51
+ */
52
+ public get providerName(): string {
53
+ return this.provider.name;
54
+ }
55
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Abstract embedding provider interface
3
+ */
4
+ export interface EmbeddingProvider {
5
+ /** Generate embedding vector for a single text */
6
+ embed(text: string): Promise<number[]>;
7
+ /** Generate embedding vectors for multiple texts (batch) */
8
+ embedBatch(texts: string[]): Promise<number[][]>;
9
+ /** Embedding dimensions */
10
+ readonly dimensions: number;
11
+ readonly name: string;
12
+ }
13
+
14
+ export interface EmbeddingProviderConfig<T = unknown> {
15
+ name: string;
16
+ provider: new (config: T) => EmbeddingProvider;
17
+ config: T;
18
+ }
19
+
20
+ export interface EmbeddingModuleOptions {
21
+ provider: EmbeddingProviderConfig;
22
+ /** Max texts per batch request (default: 100) */
23
+ batchSize?: number;
24
+ }
25
+
26
+ export const EMBEDDING_SERVICE_TOKEN = Symbol('@dangao/bun-server:embedding:service');
27
+ export const EMBEDDING_OPTIONS_TOKEN = Symbol('@dangao/bun-server:embedding:options');
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { Application, type ApplicationOptions } from './core/application';
2
2
  export { applyDecorators } from './core/apply-decorators';
3
3
  export { BunServer, type ServerOptions } from './core/server';
4
- export { ClusterManager, type ClusterOptions } from './core/cluster';
4
+ export { ClusterManager, type ClusterOptions, type ClusterMode } from './core/cluster';
5
5
  export { Context } from './core/context';
6
6
  export { ContextService, CONTEXT_SERVICE_TOKEN, contextStore } from './core/context-service';
7
7
  export { Route, Router, RouteRegistry } from './router';
@@ -562,3 +562,13 @@ export {
562
562
  type RegisteredListener,
563
563
  } from './events';
564
564
 
565
+ // ── AI Modules (v2.0.0) ──────────────────────────────────────────────────────
566
+
567
+ export * from './ai';
568
+ export * from './conversation';
569
+ export * from './prompt';
570
+ export * from './embedding';
571
+ export * from './vector-store';
572
+ export * from './rag';
573
+ export * from './mcp';
574
+ export * from './ai-guard';
@@ -0,0 +1,60 @@
1
+ import type { McpToolDefinition, McpResourceDefinition } from './types';
2
+ import { MCP_TOOL_METADATA_KEY, MCP_RESOURCE_METADATA_KEY } from './types';
3
+
4
+ /**
5
+ * Mark a method as an MCP tool.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * \@McpTool({
10
+ * name: 'get_weather',
11
+ * description: 'Get current weather for a city',
12
+ * inputSchema: {
13
+ * type: 'object',
14
+ * properties: { city: { type: 'string' } },
15
+ * required: ['city'],
16
+ * },
17
+ * })
18
+ * public async getWeather({ city }: { city: string }) {
19
+ * return { temperature: 22, city };
20
+ * }
21
+ * ```
22
+ */
23
+ export function McpTool(definition: McpToolDefinition): MethodDecorator {
24
+ return (target, propertyKey) => {
25
+ Reflect.defineMetadata(MCP_TOOL_METADATA_KEY, definition, target, propertyKey);
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Mark a method as an MCP resource provider.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * \@McpResource({
35
+ * uri: 'user://{id}',
36
+ * name: 'User Profile',
37
+ * mimeType: 'application/json',
38
+ * })
39
+ * public async getUserResource(\@McpParam('id') id: string) {
40
+ * return this.userService.find(id);
41
+ * }
42
+ * ```
43
+ */
44
+ export function McpResource(definition: McpResourceDefinition): MethodDecorator {
45
+ return (target, propertyKey) => {
46
+ Reflect.defineMetadata(MCP_RESOURCE_METADATA_KEY, definition, target, propertyKey);
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Extract a named parameter from MCP tool input arguments.
52
+ */
53
+ export function McpParam(name: string): ParameterDecorator {
54
+ return (target, propertyKey, parameterIndex) => {
55
+ const existing: Array<{ index: number; name: string }> =
56
+ Reflect.getMetadata('mcp:params', target, propertyKey!) ?? [];
57
+ existing.push({ index: parameterIndex, name });
58
+ Reflect.defineMetadata('mcp:params', existing, target, propertyKey!);
59
+ };
60
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './decorators';
3
+ export * from './registry';
4
+ export * from './server';
5
+ export * from './mcp-module';
@@ -0,0 +1,58 @@
1
+ import { Module, MODULE_METADATA_KEY } from '../di/module';
2
+ import type { ModuleProvider } from '../di/module';
3
+ import { McpServer } from './server';
4
+ import { McpRegistry } from './registry';
5
+ import { MCP_SERVER_TOKEN, MCP_OPTIONS_TOKEN, type McpModuleOptions } from './types';
6
+
7
+ @Module({ providers: [] })
8
+ export class McpModule {
9
+ /**
10
+ * Configure the MCP module.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * McpModule.forRoot({
15
+ * transport: 'sse',
16
+ * path: '/mcp',
17
+ * serverInfo: { name: 'my-api', version: '1.0.0' },
18
+ * });
19
+ *
20
+ * // Register tools by scanning instances:
21
+ * const registry = container.resolve<McpRegistry>(MCP_SERVER_TOKEN);
22
+ * registry.scan(myServiceInstance);
23
+ * ```
24
+ */
25
+ public static forRoot(options: McpModuleOptions): typeof McpModule {
26
+ const registry = new McpRegistry();
27
+ const server = new McpServer(registry, options.serverInfo);
28
+
29
+ const resolvedOptions: McpModuleOptions = {
30
+ transport: 'sse',
31
+ path: '/mcp',
32
+ ...options,
33
+ };
34
+
35
+ const providers: ModuleProvider[] = [
36
+ { provide: MCP_OPTIONS_TOKEN, useValue: resolvedOptions },
37
+ { provide: MCP_SERVER_TOKEN, useValue: server },
38
+ { provide: McpRegistry, useValue: registry },
39
+ ];
40
+
41
+ const existing = Reflect.getMetadata(MODULE_METADATA_KEY, McpModule) || {};
42
+ Reflect.defineMetadata(MODULE_METADATA_KEY, {
43
+ ...existing,
44
+ providers: [...(existing.providers || []), ...providers],
45
+ exports: [
46
+ ...(existing.exports || []),
47
+ MCP_SERVER_TOKEN,
48
+ McpRegistry,
49
+ ],
50
+ }, McpModule);
51
+
52
+ return McpModule;
53
+ }
54
+
55
+ public static reset(): void {
56
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, McpModule);
57
+ }
58
+ }
@@ -0,0 +1,72 @@
1
+ import type { McpToolDefinition, McpResourceDefinition } from './types';
2
+ import { MCP_TOOL_METADATA_KEY, MCP_RESOURCE_METADATA_KEY } from './types';
3
+
4
+ export interface RegisteredMcpTool extends McpToolDefinition {
5
+ execute(args: Record<string, unknown>): Promise<unknown>;
6
+ }
7
+
8
+ export interface RegisteredMcpResource extends McpResourceDefinition {
9
+ read(uriParams: Record<string, string>): Promise<unknown>;
10
+ }
11
+
12
+ /**
13
+ * Registry for MCP tools and resources discovered via decorators.
14
+ */
15
+ export class McpRegistry {
16
+ private readonly tools = new Map<string, RegisteredMcpTool>();
17
+ private readonly resources = new Map<string, RegisteredMcpResource>();
18
+
19
+ /**
20
+ * Scan an object instance for @McpTool() and @McpResource() decorated methods
21
+ */
22
+ public scan(instance: object): void {
23
+ const proto = Object.getPrototypeOf(instance);
24
+ const methodNames = Object.getOwnPropertyNames(proto).filter(
25
+ (key) => key !== 'constructor',
26
+ );
27
+
28
+ for (const methodName of methodNames) {
29
+ const toolDef: McpToolDefinition | undefined = Reflect.getMetadata(
30
+ MCP_TOOL_METADATA_KEY,
31
+ proto,
32
+ methodName,
33
+ );
34
+ if (toolDef) {
35
+ const method = (instance as Record<string, unknown>)[methodName] as (args: unknown) => Promise<unknown>;
36
+ this.tools.set(toolDef.name, {
37
+ ...toolDef,
38
+ execute: (args) => method.call(instance, args),
39
+ });
40
+ }
41
+
42
+ const resourceDef: McpResourceDefinition | undefined = Reflect.getMetadata(
43
+ MCP_RESOURCE_METADATA_KEY,
44
+ proto,
45
+ methodName,
46
+ );
47
+ if (resourceDef) {
48
+ const method = (instance as Record<string, unknown>)[methodName] as (params: unknown) => Promise<unknown>;
49
+ this.resources.set(resourceDef.uri, {
50
+ ...resourceDef,
51
+ read: (params) => method.call(instance, params),
52
+ });
53
+ }
54
+ }
55
+ }
56
+
57
+ public getTools(): RegisteredMcpTool[] {
58
+ return Array.from(this.tools.values());
59
+ }
60
+
61
+ public getResources(): RegisteredMcpResource[] {
62
+ return Array.from(this.resources.values());
63
+ }
64
+
65
+ public getTool(name: string): RegisteredMcpTool | undefined {
66
+ return this.tools.get(name);
67
+ }
68
+
69
+ public getResource(uri: string): RegisteredMcpResource | undefined {
70
+ return this.resources.get(uri);
71
+ }
72
+ }
@@ -0,0 +1,164 @@
1
+ import type { McpServerInfo, JsonRpcRequest, JsonRpcResponse } from './types';
2
+ import type { McpRegistry } from './registry';
3
+
4
+ /**
5
+ * MCP protocol server — handles JSON-RPC 2.0 requests from MCP clients.
6
+ * Supports SSE transport (HTTP) and stdio transport.
7
+ *
8
+ * Implements MCP specification 2024-11-05.
9
+ */
10
+ export class McpServer {
11
+ private readonly registry: McpRegistry;
12
+ private readonly serverInfo: McpServerInfo;
13
+
14
+ public constructor(registry: McpRegistry, serverInfo: McpServerInfo) {
15
+ this.registry = registry;
16
+ this.serverInfo = serverInfo;
17
+ }
18
+
19
+ /**
20
+ * Handle an incoming JSON-RPC request and return a response
21
+ */
22
+ public async handle(request: JsonRpcRequest): Promise<JsonRpcResponse> {
23
+ try {
24
+ const result = await this.dispatch(request.method, request.params);
25
+ return { jsonrpc: '2.0', id: request.id ?? null, result };
26
+ } catch (err) {
27
+ return {
28
+ jsonrpc: '2.0',
29
+ id: request.id ?? null,
30
+ error: {
31
+ code: err instanceof McpError ? err.code : -32603,
32
+ message: err instanceof Error ? err.message : 'Internal error',
33
+ },
34
+ };
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Handle a raw HTTP request body (JSON-RPC over HTTP)
40
+ * Returns a Response suitable for Bun's serve()
41
+ */
42
+ public async handleHttp(req: Request): Promise<Response> {
43
+ const body = await req.json() as JsonRpcRequest;
44
+ const response = await this.handle(body);
45
+ return new Response(JSON.stringify(response), {
46
+ headers: { 'Content-Type': 'application/json' },
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Create an SSE response that keeps the connection open for streaming
52
+ * This is the SSE transport endpoint
53
+ */
54
+ public createSseResponse(): Response {
55
+ const registry = this.registry;
56
+ const serverInfo = this.serverInfo;
57
+ const encoder = new TextEncoder();
58
+
59
+ const stream = new ReadableStream({
60
+ start(controller) {
61
+ // Send initial server info event
62
+ const initEvent = `event: endpoint\ndata: ${JSON.stringify({
63
+ type: 'endpoint',
64
+ method: 'POST',
65
+ })}\n\n`;
66
+ controller.enqueue(encoder.encode(initEvent));
67
+
68
+ // Keep connection alive with periodic pings
69
+ const pingInterval = setInterval(() => {
70
+ try {
71
+ controller.enqueue(encoder.encode(': ping\n\n'));
72
+ } catch {
73
+ clearInterval(pingInterval);
74
+ }
75
+ }, 15000);
76
+ },
77
+ });
78
+
79
+ return new Response(stream, {
80
+ headers: {
81
+ 'Content-Type': 'text/event-stream',
82
+ 'Cache-Control': 'no-cache',
83
+ 'Connection': 'keep-alive',
84
+ 'X-MCP-Server': `${serverInfo.name}/${serverInfo.version}`,
85
+ },
86
+ });
87
+ }
88
+
89
+ private async dispatch(method: string, params: unknown): Promise<unknown> {
90
+ switch (method) {
91
+ case 'initialize':
92
+ return {
93
+ protocolVersion: '2024-11-05',
94
+ capabilities: { tools: {}, resources: {} },
95
+ serverInfo: this.serverInfo,
96
+ };
97
+
98
+ case 'tools/list':
99
+ return {
100
+ tools: this.registry.getTools().map((t) => ({
101
+ name: t.name,
102
+ description: t.description,
103
+ inputSchema: t.inputSchema,
104
+ })),
105
+ };
106
+
107
+ case 'tools/call': {
108
+ const { name, arguments: args = {} } = params as { name: string; arguments?: Record<string, unknown> };
109
+ const tool = this.registry.getTool(name);
110
+ if (!tool) throw new McpError(-32601, `Tool "${name}" not found`);
111
+ const result = await tool.execute(args);
112
+ return {
113
+ content: [
114
+ {
115
+ type: 'text',
116
+ text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
117
+ },
118
+ ],
119
+ };
120
+ }
121
+
122
+ case 'resources/list':
123
+ return {
124
+ resources: this.registry.getResources().map((r) => ({
125
+ uri: r.uri,
126
+ name: r.name,
127
+ description: r.description,
128
+ mimeType: r.mimeType,
129
+ })),
130
+ };
131
+
132
+ case 'resources/read': {
133
+ const { uri } = params as { uri: string };
134
+ const resource = this.registry.getResource(uri);
135
+ if (!resource) throw new McpError(-32601, `Resource "${uri}" not found`);
136
+ const content = await resource.read({});
137
+ return {
138
+ contents: [
139
+ {
140
+ uri,
141
+ mimeType: resource.mimeType ?? 'application/json',
142
+ text: typeof content === 'string' ? content : JSON.stringify(content),
143
+ },
144
+ ],
145
+ };
146
+ }
147
+
148
+ case 'ping':
149
+ return {};
150
+
151
+ default:
152
+ throw new McpError(-32601, `Method "${method}" not found`);
153
+ }
154
+ }
155
+ }
156
+
157
+ class McpError extends Error {
158
+ public constructor(
159
+ public readonly code: number,
160
+ message: string,
161
+ ) {
162
+ super(message);
163
+ }
164
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * MCP tool definition
3
+ */
4
+ export interface McpToolDefinition {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: Record<string, unknown>;
8
+ }
9
+
10
+ /**
11
+ * MCP resource definition
12
+ */
13
+ export interface McpResourceDefinition {
14
+ uri: string;
15
+ name: string;
16
+ description?: string;
17
+ mimeType?: string;
18
+ }
19
+
20
+ /**
21
+ * MCP server info
22
+ */
23
+ export interface McpServerInfo {
24
+ name: string;
25
+ version: string;
26
+ description?: string;
27
+ }
28
+
29
+ /**
30
+ * MCP transport type
31
+ */
32
+ export type McpTransport = 'sse' | 'stdio';
33
+
34
+ export interface McpModuleOptions {
35
+ transport?: McpTransport;
36
+ /** Path for SSE endpoint (default: '/mcp') */
37
+ path?: string;
38
+ serverInfo: McpServerInfo;
39
+ }
40
+
41
+ export const MCP_SERVER_TOKEN = Symbol('@dangao/bun-server:mcp:server');
42
+ export const MCP_OPTIONS_TOKEN = Symbol('@dangao/bun-server:mcp:options');
43
+
44
+ /** Metadata key for @McpTool() */
45
+ export const MCP_TOOL_METADATA_KEY = '@dangao/bun-server:mcp:tool';
46
+ /** Metadata key for @McpResource() */
47
+ export const MCP_RESOURCE_METADATA_KEY = '@dangao/bun-server:mcp:resource';
48
+
49
+ /** JSON-RPC 2.0 Request */
50
+ export interface JsonRpcRequest {
51
+ jsonrpc: '2.0';
52
+ id: string | number | null;
53
+ method: string;
54
+ params?: unknown;
55
+ }
56
+
57
+ /** JSON-RPC 2.0 Response */
58
+ export interface JsonRpcResponse {
59
+ jsonrpc: '2.0';
60
+ id: string | number | null;
61
+ result?: unknown;
62
+ error?: { code: number; message: string; data?: unknown };
63
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './service';
3
+ export * from './prompt-module';
4
+ export * from './stores/memory-store';
5
+ export * from './stores/file-store';
@@ -0,0 +1,61 @@
1
+ import { Module, MODULE_METADATA_KEY } from '../di/module';
2
+ import type { ModuleProvider } from '../di/module';
3
+ import { PromptService } from './service';
4
+ import {
5
+ PROMPT_SERVICE_TOKEN,
6
+ PROMPT_OPTIONS_TOKEN,
7
+ type PromptModuleOptions,
8
+ } from './types';
9
+ import { InMemoryPromptStore } from './stores/memory-store';
10
+
11
+ @Module({ providers: [] })
12
+ export class PromptModule {
13
+ /**
14
+ * Configure the prompt module.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // With in-memory store (default):
19
+ * PromptModule.forRoot({});
20
+ *
21
+ * // With file store:
22
+ * PromptModule.forRoot({
23
+ * store: new FilePromptStore({ promptsDir: './.prompts' }),
24
+ * });
25
+ * ```
26
+ */
27
+ public static forRoot(options: PromptModuleOptions = {}): typeof PromptModule {
28
+ const resolvedOptions: PromptModuleOptions = {
29
+ ...options,
30
+ store: options.store ?? new InMemoryPromptStore(),
31
+ };
32
+
33
+ const service = new PromptService(resolvedOptions as Required<PromptModuleOptions>);
34
+
35
+ const providers: ModuleProvider[] = [
36
+ { provide: PROMPT_OPTIONS_TOKEN, useValue: resolvedOptions },
37
+ { provide: PROMPT_SERVICE_TOKEN, useValue: service },
38
+ PromptService,
39
+ ];
40
+
41
+ const existing = Reflect.getMetadata(MODULE_METADATA_KEY, PromptModule) || {};
42
+ Reflect.defineMetadata(MODULE_METADATA_KEY, {
43
+ ...existing,
44
+ providers: [...(existing.providers || []), ...providers],
45
+ exports: [
46
+ ...(existing.exports || []),
47
+ PROMPT_SERVICE_TOKEN,
48
+ PromptService,
49
+ ],
50
+ }, PromptModule);
51
+
52
+ return PromptModule;
53
+ }
54
+
55
+ /**
56
+ * Reset module state (for testing)
57
+ */
58
+ public static reset(): void {
59
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, PromptModule);
60
+ }
61
+ }