@synth-coder/memhub 0.1.3 → 0.1.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MCP (Model Context Protocol) specific types and constants
3
- * Defines the protocol structures for stdio-based communication
3
+ * Tool definitions and business-level types (protocol types provided by SDK)
4
4
  */
5
5
 
6
6
  import type { Memory } from './types.js';
@@ -19,86 +19,7 @@ export const SERVER_INFO = {
19
19
  } as const;
20
20
 
21
21
  // ============================================================================
22
- // MCP Request/Response Types
23
- // ============================================================================
24
-
25
- /** JSON-RPC request ID */
26
- export type RequestId = string | number;
27
-
28
- /** Base JSON-RPC request structure */
29
- export interface JsonRpcRequest<T = unknown> {
30
- jsonrpc: '2.0';
31
- id?: RequestId;
32
- method: string;
33
- params?: T;
34
- }
35
-
36
- /** Base JSON-RPC response structure */
37
- export interface JsonRpcResponse<T = unknown> {
38
- jsonrpc: '2.0';
39
- id: RequestId | null;
40
- result?: T;
41
- error?: JsonRpcError;
42
- }
43
-
44
- /** JSON-RPC error structure */
45
- export interface JsonRpcError {
46
- code: number;
47
- message: string;
48
- data?: unknown;
49
- }
50
-
51
- /** JSON-RPC notification (no response expected) */
52
- export interface JsonRpcNotification<T = unknown> {
53
- jsonrpc: '2.0';
54
- method: string;
55
- params?: T;
56
- }
57
-
58
- // ============================================================================
59
- // MCP Lifecycle Messages
60
- // ============================================================================
61
-
62
- /** Initialize request parameters */
63
- export interface InitializeParams {
64
- protocolVersion: string;
65
- capabilities: ClientCapabilities;
66
- clientInfo: Implementation;
67
- }
68
-
69
- /** Initialize result */
70
- export interface InitializeResult {
71
- protocolVersion: string;
72
- capabilities: ServerCapabilities;
73
- serverInfo: Implementation;
74
- }
75
-
76
- /** Implementation information */
77
- export interface Implementation {
78
- name: string;
79
- version: string;
80
- }
81
-
82
- /** Client capabilities */
83
- export interface ClientCapabilities {
84
- // Client can handle these features
85
- readonly experimental?: Record<string, unknown>;
86
- readonly roots?: { listChanged?: boolean };
87
- readonly sampling?: Record<string, unknown>;
88
- }
89
-
90
- /** Server capabilities */
91
- export interface ServerCapabilities {
92
- // Server provides these features
93
- readonly experimental?: Record<string, unknown>;
94
- readonly logging?: Record<string, unknown>;
95
- readonly prompts?: { listChanged?: boolean };
96
- readonly resources?: { subscribe?: boolean; listChanged?: boolean };
97
- readonly tools?: { listChanged?: boolean };
98
- }
99
-
100
- // ============================================================================
101
- // MCP Tool Definitions
22
+ // MCP Tool Definitions (kept for SDK use)
102
23
  // ============================================================================
103
24
 
104
25
  /** Tool definition for tool/list */
@@ -116,34 +37,6 @@ export interface ToolInputSchema {
116
37
  additionalProperties?: boolean;
117
38
  }
118
39
 
119
- /** Tool call request */
120
- export interface ToolCallRequest {
121
- name: string;
122
- arguments?: Record<string, unknown>;
123
- }
124
-
125
- /** Tool call result */
126
- export interface ToolCallResult {
127
- content: ToolContent[];
128
- isError?: boolean;
129
- }
130
-
131
- /** Tool content types */
132
- export type ToolContent = TextContent | ImageContent;
133
-
134
- /** Text content */
135
- export interface TextContent {
136
- type: 'text';
137
- text: string;
138
- }
139
-
140
- /** Image content */
141
- export interface ImageContent {
142
- type: 'image';
143
- data: string; // base64 encoded
144
- mimeType: string;
145
- }
146
-
147
40
  // ============================================================================
148
41
  // MCP Tool List
149
42
  // ============================================================================
@@ -209,41 +102,9 @@ export const TOOL_DEFINITIONS: readonly Tool[] = [
209
102
  ] as const;
210
103
 
211
104
  // ============================================================================
212
- // MCP Methods
213
- // ============================================================================
214
-
215
- /** All MCP method names */
216
- export const MCP_METHODS = {
217
- // Lifecycle
218
- INITIALIZE: 'initialize',
219
- INITIALIZED: 'notifications/initialized',
220
- SHUTDOWN: 'shutdown',
221
- EXIT: 'exit',
222
-
223
- // Tools
224
- TOOLS_LIST: 'tools/list',
225
- TOOLS_CALL: 'tools/call',
226
-
227
- // Logging
228
- LOGGING_MESSAGE: 'notifications/message',
229
-
230
- // Progress
231
- PROGRESS: 'notifications/progress',
232
- } as const;
233
-
234
- // ============================================================================
235
- // Error Codes (Standard MCP + Custom)
105
+ // Error Codes (MemHub Custom)
236
106
  // ============================================================================
237
107
 
238
- /** Standard JSON-RPC error codes */
239
- export const JSONRPC_ERROR_CODES = {
240
- PARSE_ERROR: -32700,
241
- INVALID_REQUEST: -32600,
242
- METHOD_NOT_FOUND: -32601,
243
- INVALID_PARAMS: -32602,
244
- INTERNAL_ERROR: -32603,
245
- } as const;
246
-
247
108
  /** MemHub custom error codes */
248
109
  export const MEMHUB_ERROR_CODES = {
249
110
  NOT_FOUND: -32001,
@@ -252,9 +113,13 @@ export const MEMHUB_ERROR_CODES = {
252
113
  DUPLICATE_ERROR: -32004,
253
114
  } as const;
254
115
 
255
- /** Combined error codes */
116
+ /** Combined error codes (includes JSON-RPC standard codes) */
256
117
  export const ERROR_CODES = {
257
- ...JSONRPC_ERROR_CODES,
118
+ PARSE_ERROR: -32700,
119
+ INVALID_REQUEST: -32600,
120
+ METHOD_NOT_FOUND: -32601,
121
+ INVALID_PARAMS: -32602,
122
+ INTERNAL_ERROR: -32603,
258
123
  ...MEMHUB_ERROR_CODES,
259
124
  } as const;
260
125
 
@@ -300,4 +165,4 @@ export type ToolInput<T extends ToolName> = T extends 'memory_load'
300
165
  category?: string;
301
166
  importance?: number;
302
167
  }
303
- : never;
168
+ : never;
@@ -1,36 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * MCP Server - Model Context Protocol server implementation
4
- * Communicates via stdio
4
+ * Uses @modelcontextprotocol/sdk for protocol handling
5
5
  */
6
6
 
7
7
  import { readFileSync } from 'fs';
8
8
  import { join, dirname } from 'path';
9
9
  import { fileURLToPath } from 'url';
10
- import type {
11
- JsonRpcRequest,
12
- JsonRpcResponse,
13
- JsonRpcError,
14
- RequestId,
15
- InitializeParams,
16
- InitializeResult,
17
- ToolCallRequest,
18
- ToolCallResult,
19
- TextContent,
20
- } from '../contracts/mcp.js';
10
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
21
12
  import {
22
- MCP_PROTOCOL_VERSION,
23
- SERVER_INFO,
24
- TOOL_DEFINITIONS,
25
- MCP_METHODS,
26
- ERROR_CODES,
27
- } from '../contracts/mcp.js';
28
- import { ErrorCode } from '../contracts/types.js';
13
+ CallToolRequestSchema,
14
+ ListToolsRequestSchema,
15
+ } from '@modelcontextprotocol/sdk/types.js';
29
16
  import { MemoryService, ServiceError } from '../services/memory-service.js';
30
17
  import {
31
18
  MemoryLoadInputSchema,
32
19
  MemoryUpdateInputV2Schema,
33
20
  } from '../contracts/schemas.js';
21
+ import { TOOL_DEFINITIONS, SERVER_INFO } from '../contracts/mcp.js';
22
+ import { ErrorCode } from '../contracts/types.js';
34
23
 
35
24
  // Get package version
36
25
  const __filename = fileURLToPath(import.meta.url);
@@ -45,164 +34,34 @@ const packageJsonPath = join(__dirname, '../../../package.json');
45
34
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as PackageJson;
46
35
 
47
36
  /**
48
- * MCP Server implementation
37
+ * Create McpServer instance using SDK
49
38
  */
50
- export class McpServer {
51
- private readonly memoryService: MemoryService;
52
-
53
- constructor() {
54
- const storagePath = process.env.MEMHUB_STORAGE_PATH || './memories';
55
- this.memoryService = new MemoryService({ storagePath });
56
- }
57
-
58
- /**
59
- * Starts the MCP server and begins listening for requests on stdin
60
- */
61
- start(): void {
62
- this.log('info', 'MemHub MCP Server starting...');
63
-
64
- process.stdin.setEncoding('utf-8');
65
-
66
- let buffer = '';
67
-
68
- process.stdin.on('data', (chunk: string) => {
69
- buffer += chunk;
70
-
71
- // Process complete lines (JSON-RPC messages)
72
- const lines = buffer.split('\n');
73
- buffer = lines.pop() || ''; // Keep incomplete line in buffer
74
-
75
- for (const line of lines) {
76
- if (line.trim()) {
77
- void this.handleMessage(line.trim());
78
- }
79
- }
80
- });
81
-
82
- process.stdin.on('end', () => {
83
- this.log('info', 'Stdin closed, shutting down...');
84
- process.exit(0);
85
- });
86
-
87
- process.on('SIGINT', () => {
88
- this.log('info', 'Received SIGINT, shutting down...');
89
- process.exit(0);
90
- });
91
-
92
- process.on('SIGTERM', () => {
93
- this.log('info', 'Received SIGTERM, shutting down...');
94
- process.exit(0);
95
- });
96
- }
97
-
98
- /**
99
- * Handles an incoming JSON-RPC message
100
- *
101
- * @param message - The JSON-RPC message string
102
- */
103
- private async handleMessage(message: string): Promise<void> {
104
- let request: JsonRpcRequest | null = null;
105
-
106
- try {
107
- request = JSON.parse(message) as JsonRpcRequest;
108
- } catch {
109
- this.sendError(null, ERROR_CODES.PARSE_ERROR, 'Parse error: Invalid JSON');
110
- return;
111
- }
112
-
113
- // Validate JSON-RPC request
114
- if (request.jsonrpc !== '2.0' || !request.method) {
115
- this.sendError(
116
- request.id ?? null,
117
- ERROR_CODES.INVALID_REQUEST,
118
- 'Invalid Request'
119
- );
120
- return;
121
- }
122
-
123
- try {
124
- const result = await this.handleMethod(request.method, request.params);
125
-
126
- // Send response (only for requests with id, not notifications)
127
- if (request.id !== undefined) {
128
- this.sendResponse(request.id, result);
129
- }
130
- } catch (error) {
131
- this.handleError(request.id ?? null, error);
132
- }
133
- }
134
-
135
- /**
136
- * Handles a specific method call
137
- *
138
- * @param method - The method name
139
- * @param params - The method parameters
140
- * @returns The method result
141
- */
142
- private async handleMethod(
143
- method: string,
144
- params: unknown
145
- ): Promise<unknown> {
146
- switch (method) {
147
- case MCP_METHODS.INITIALIZE:
148
- return this.handleInitialize(params as InitializeParams);
149
-
150
- case MCP_METHODS.INITIALIZED:
151
- // Notification, no response needed
152
- this.log('info', 'Client initialized');
153
- return null;
154
-
155
- case MCP_METHODS.SHUTDOWN:
156
- return null;
157
-
158
- case MCP_METHODS.EXIT:
159
- process.exit(0);
160
- return null;
161
-
162
- case MCP_METHODS.TOOLS_LIST:
163
- return { tools: TOOL_DEFINITIONS };
164
-
165
- case MCP_METHODS.TOOLS_CALL:
166
- return this.handleToolCall(params as ToolCallRequest);
167
-
168
- default:
169
- throw new ServiceError(
170
- `Method not found: ${method}`,
171
- ErrorCode.METHOD_NOT_FOUND
172
- );
173
- }
174
- }
175
-
176
- /**
177
- * Handles the initialize method
178
- *
179
- * @param params - Initialize parameters
180
- * @returns Initialize result
181
- */
182
- private handleInitialize(params: InitializeParams): InitializeResult {
183
- this.log('info', `Client initializing: ${params.clientInfo.name} v${params.clientInfo.version}`);
184
-
185
- return {
186
- protocolVersion: MCP_PROTOCOL_VERSION,
39
+ export function createMcpServer(): Server {
40
+ const storagePath = process.env.MEMHUB_STORAGE_PATH || './memories';
41
+ const memoryService = new MemoryService({ storagePath });
42
+
43
+ // Create server using SDK
44
+ const server = new Server(
45
+ {
46
+ name: SERVER_INFO.name,
47
+ version: packageJson.version || SERVER_INFO.version,
48
+ },
49
+ {
187
50
  capabilities: {
188
51
  tools: { listChanged: false },
189
52
  logging: {},
190
53
  },
191
- serverInfo: {
192
- name: SERVER_INFO.name,
193
- version: packageJson.version || SERVER_INFO.version,
194
- },
195
- };
196
- }
54
+ }
55
+ );
56
+
57
+ // Handle tools/list request
58
+ server.setRequestHandler(ListToolsRequestSchema, () => {
59
+ return { tools: TOOL_DEFINITIONS };
60
+ });
197
61
 
198
- /**
199
- * Handles tool calls
200
- *
201
- * @param request - Tool call request
202
- * @returns Tool call result
203
- */
204
- private async handleToolCall(request: ToolCallRequest): Promise<ToolCallResult> {
205
- const { name, arguments: args } = request;
62
+ // Handle tools/call request
63
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
64
+ const { name, arguments: args } = request.params;
206
65
 
207
66
  try {
208
67
  let result: unknown;
@@ -210,13 +69,13 @@ export class McpServer {
210
69
  switch (name) {
211
70
  case 'memory_load': {
212
71
  const input = MemoryLoadInputSchema.parse(args ?? {});
213
- result = await this.memoryService.memoryLoad(input);
72
+ result = await memoryService.memoryLoad(input);
214
73
  break;
215
74
  }
216
75
 
217
76
  case 'memory_update': {
218
77
  const input = MemoryUpdateInputV2Schema.parse(args ?? {});
219
- result = await this.memoryService.memoryUpdate(input);
78
+ result = await memoryService.memoryUpdate(input);
220
79
  break;
221
80
  }
222
81
 
@@ -230,9 +89,9 @@ export class McpServer {
230
89
  return {
231
90
  content: [
232
91
  {
233
- type: 'text',
92
+ type: 'text' as const,
234
93
  text: JSON.stringify(result, null, 2),
235
- } as TextContent,
94
+ },
236
95
  ],
237
96
  };
238
97
  } catch (error) {
@@ -240,115 +99,46 @@ export class McpServer {
240
99
  return {
241
100
  content: [
242
101
  {
243
- type: 'text',
102
+ type: 'text' as const,
244
103
  text: JSON.stringify({ error: error.message }, null, 2),
245
- } as TextContent,
104
+ },
246
105
  ],
247
106
  isError: true,
248
107
  };
249
108
  }
250
109
 
251
- // Re-throw for generic error handling
252
- throw error;
253
- }
254
- }
110
+ if (error instanceof Error && error.name === 'ZodError') {
111
+ return {
112
+ content: [
113
+ {
114
+ type: 'text' as const,
115
+ text: JSON.stringify({ error: `Invalid parameters: ${error.message}` }, null, 2),
116
+ },
117
+ ],
118
+ isError: true,
119
+ };
120
+ }
255
121
 
256
- /**
257
- * Handles errors and sends appropriate error response
258
- *
259
- * @param id - Request ID
260
- * @param error - The error that occurred
261
- */
262
- private handleError(id: RequestId | null, error: unknown): void {
263
- if (error instanceof ServiceError) {
264
- this.sendError(id, error.code, error.message, error.data);
265
- } else if (error instanceof Error && error.name === 'ZodError') {
266
- this.sendError(
267
- id,
268
- ERROR_CODES.INVALID_PARAMS,
269
- `Invalid parameters: ${error.message}`
270
- );
271
- } else {
272
- this.sendError(
273
- id,
274
- ERROR_CODES.INTERNAL_ERROR,
275
- `Internal error: ${error instanceof Error ? error.message : 'Unknown error'}`
276
- );
122
+ // Re-throw for SDK error handling
123
+ throw error;
277
124
  }
278
- }
279
-
280
- /**
281
- * Sends a JSON-RPC response
282
- *
283
- * @param id - Request ID
284
- * @param result - Response result
285
- */
286
- private sendResponse(id: RequestId, result: unknown): void {
287
- const response: JsonRpcResponse = {
288
- jsonrpc: '2.0',
289
- id,
290
- result,
291
- };
292
- this.sendMessage(response);
293
- }
294
-
295
- /**
296
- * Sends a JSON-RPC error
297
- *
298
- * @param id - Request ID
299
- * @param code - Error code
300
- * @param message - Error message
301
- * @param data - Additional error data
302
- */
303
- private sendError(
304
- id: RequestId | null,
305
- code: number,
306
- message: string,
307
- data?: Record<string, unknown>
308
- ): void {
309
- const error: JsonRpcError = {
310
- code,
311
- message,
312
- data,
313
- };
125
+ });
314
126
 
315
- const response: JsonRpcResponse = {
316
- jsonrpc: '2.0',
317
- id: id ?? null,
318
- error,
319
- };
320
-
321
- this.sendMessage(response);
322
- }
323
-
324
- /**
325
- * Sends a message to stdout
326
- *
327
- * @param message - The message to send
328
- */
329
- private sendMessage(message: JsonRpcResponse | JsonRpcRequest): void {
330
- const json = JSON.stringify(message);
331
- process.stdout.write(json + '\n');
332
- }
127
+ return server;
128
+ }
333
129
 
334
- /**
335
- * Logs a message
336
- *
337
- * @param level - Log level
338
- * @param message - Log message
339
- */
340
- private log(level: 'debug' | 'info' | 'warn' | 'error', message: string): void {
341
- const logLevel = process.env.MEMHUB_LOG_LEVEL || 'info';
342
- const levels = { debug: 0, info: 1, warn: 2, error: 3 };
130
+ /**
131
+ * Start the MCP server
132
+ */
133
+ async function main(): Promise<void> {
134
+ const server = createMcpServer();
135
+ const transport = new StdioServerTransport();
343
136
 
344
- if (levels[level] >= levels[logLevel as keyof typeof levels]) {
345
- console.error(`[${level.toUpperCase()}] ${message}`);
346
- }
347
- }
137
+ await server.connect(transport);
138
+ console.error('MemHub MCP Server running on stdio');
348
139
  }
349
140
 
350
- // Start server if this file is run directly
351
- if (import.meta.url === `file://${process.argv[1]}`) {
352
- const server = new McpServer();
353
- server.start();
354
- }
141
+ main().catch((error) => {
142
+ console.error('Fatal error:', error);
143
+ process.exit(1);
144
+ });