@synth-coder/memhub 0.1.2 → 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);
@@ -39,169 +28,40 @@ interface PackageJson {
39
28
  version?: string;
40
29
  }
41
30
 
42
- const packageJson = JSON.parse(
43
- readFileSync(join(__dirname, '../../package.json'), 'utf-8')
44
- ) as PackageJson;
31
+ // npm package runtime: dist/src/server -> package root
32
+ const packageJsonPath = join(__dirname, '../../../package.json');
33
+
34
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as PackageJson;
45
35
 
46
36
  /**
47
- * MCP Server implementation
37
+ * Create McpServer instance using SDK
48
38
  */
49
- export class McpServer {
50
- private readonly memoryService: MemoryService;
51
-
52
- constructor() {
53
- const storagePath = process.env.MEMHUB_STORAGE_PATH || './memories';
54
- this.memoryService = new MemoryService({ storagePath });
55
- }
56
-
57
- /**
58
- * Starts the MCP server and begins listening for requests on stdin
59
- */
60
- start(): void {
61
- this.log('info', 'MemHub MCP Server starting...');
62
-
63
- process.stdin.setEncoding('utf-8');
64
-
65
- let buffer = '';
66
-
67
- process.stdin.on('data', (chunk: string) => {
68
- buffer += chunk;
69
-
70
- // Process complete lines (JSON-RPC messages)
71
- const lines = buffer.split('\n');
72
- buffer = lines.pop() || ''; // Keep incomplete line in buffer
73
-
74
- for (const line of lines) {
75
- if (line.trim()) {
76
- void this.handleMessage(line.trim());
77
- }
78
- }
79
- });
80
-
81
- process.stdin.on('end', () => {
82
- this.log('info', 'Stdin closed, shutting down...');
83
- process.exit(0);
84
- });
85
-
86
- process.on('SIGINT', () => {
87
- this.log('info', 'Received SIGINT, shutting down...');
88
- process.exit(0);
89
- });
90
-
91
- process.on('SIGTERM', () => {
92
- this.log('info', 'Received SIGTERM, shutting down...');
93
- process.exit(0);
94
- });
95
- }
96
-
97
- /**
98
- * Handles an incoming JSON-RPC message
99
- *
100
- * @param message - The JSON-RPC message string
101
- */
102
- private async handleMessage(message: string): Promise<void> {
103
- let request: JsonRpcRequest | null = null;
104
-
105
- try {
106
- request = JSON.parse(message) as JsonRpcRequest;
107
- } catch {
108
- this.sendError(null, ERROR_CODES.PARSE_ERROR, 'Parse error: Invalid JSON');
109
- return;
110
- }
111
-
112
- // Validate JSON-RPC request
113
- if (request.jsonrpc !== '2.0' || !request.method) {
114
- this.sendError(
115
- request.id ?? null,
116
- ERROR_CODES.INVALID_REQUEST,
117
- 'Invalid Request'
118
- );
119
- return;
120
- }
121
-
122
- try {
123
- const result = await this.handleMethod(request.method, request.params);
124
-
125
- // Send response (only for requests with id, not notifications)
126
- if (request.id !== undefined) {
127
- this.sendResponse(request.id, result);
128
- }
129
- } catch (error) {
130
- this.handleError(request.id ?? null, error);
131
- }
132
- }
133
-
134
- /**
135
- * Handles a specific method call
136
- *
137
- * @param method - The method name
138
- * @param params - The method parameters
139
- * @returns The method result
140
- */
141
- private async handleMethod(
142
- method: string,
143
- params: unknown
144
- ): Promise<unknown> {
145
- switch (method) {
146
- case MCP_METHODS.INITIALIZE:
147
- return this.handleInitialize(params as InitializeParams);
148
-
149
- case MCP_METHODS.INITIALIZED:
150
- // Notification, no response needed
151
- this.log('info', 'Client initialized');
152
- return null;
153
-
154
- case MCP_METHODS.SHUTDOWN:
155
- return null;
156
-
157
- case MCP_METHODS.EXIT:
158
- process.exit(0);
159
- return null;
160
-
161
- case MCP_METHODS.TOOLS_LIST:
162
- return { tools: TOOL_DEFINITIONS };
163
-
164
- case MCP_METHODS.TOOLS_CALL:
165
- return this.handleToolCall(params as ToolCallRequest);
166
-
167
- default:
168
- throw new ServiceError(
169
- `Method not found: ${method}`,
170
- ErrorCode.METHOD_NOT_FOUND
171
- );
172
- }
173
- }
174
-
175
- /**
176
- * Handles the initialize method
177
- *
178
- * @param params - Initialize parameters
179
- * @returns Initialize result
180
- */
181
- private handleInitialize(params: InitializeParams): InitializeResult {
182
- this.log('info', `Client initializing: ${params.clientInfo.name} v${params.clientInfo.version}`);
183
-
184
- return {
185
- 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
+ {
186
50
  capabilities: {
187
51
  tools: { listChanged: false },
188
52
  logging: {},
189
53
  },
190
- serverInfo: {
191
- name: SERVER_INFO.name,
192
- version: packageJson.version || SERVER_INFO.version,
193
- },
194
- };
195
- }
54
+ }
55
+ );
56
+
57
+ // Handle tools/list request
58
+ server.setRequestHandler(ListToolsRequestSchema, () => {
59
+ return { tools: TOOL_DEFINITIONS };
60
+ });
196
61
 
197
- /**
198
- * Handles tool calls
199
- *
200
- * @param request - Tool call request
201
- * @returns Tool call result
202
- */
203
- private async handleToolCall(request: ToolCallRequest): Promise<ToolCallResult> {
204
- const { name, arguments: args } = request;
62
+ // Handle tools/call request
63
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
64
+ const { name, arguments: args } = request.params;
205
65
 
206
66
  try {
207
67
  let result: unknown;
@@ -209,13 +69,13 @@ export class McpServer {
209
69
  switch (name) {
210
70
  case 'memory_load': {
211
71
  const input = MemoryLoadInputSchema.parse(args ?? {});
212
- result = await this.memoryService.memoryLoad(input);
72
+ result = await memoryService.memoryLoad(input);
213
73
  break;
214
74
  }
215
75
 
216
76
  case 'memory_update': {
217
77
  const input = MemoryUpdateInputV2Schema.parse(args ?? {});
218
- result = await this.memoryService.memoryUpdate(input);
78
+ result = await memoryService.memoryUpdate(input);
219
79
  break;
220
80
  }
221
81
 
@@ -229,9 +89,9 @@ export class McpServer {
229
89
  return {
230
90
  content: [
231
91
  {
232
- type: 'text',
92
+ type: 'text' as const,
233
93
  text: JSON.stringify(result, null, 2),
234
- } as TextContent,
94
+ },
235
95
  ],
236
96
  };
237
97
  } catch (error) {
@@ -239,115 +99,46 @@ export class McpServer {
239
99
  return {
240
100
  content: [
241
101
  {
242
- type: 'text',
102
+ type: 'text' as const,
243
103
  text: JSON.stringify({ error: error.message }, null, 2),
244
- } as TextContent,
104
+ },
245
105
  ],
246
106
  isError: true,
247
107
  };
248
108
  }
249
109
 
250
- // Re-throw for generic error handling
251
- throw error;
252
- }
253
- }
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
+ }
254
121
 
255
- /**
256
- * Handles errors and sends appropriate error response
257
- *
258
- * @param id - Request ID
259
- * @param error - The error that occurred
260
- */
261
- private handleError(id: RequestId | null, error: unknown): void {
262
- if (error instanceof ServiceError) {
263
- this.sendError(id, error.code, error.message, error.data);
264
- } else if (error instanceof Error && error.name === 'ZodError') {
265
- this.sendError(
266
- id,
267
- ERROR_CODES.INVALID_PARAMS,
268
- `Invalid parameters: ${error.message}`
269
- );
270
- } else {
271
- this.sendError(
272
- id,
273
- ERROR_CODES.INTERNAL_ERROR,
274
- `Internal error: ${error instanceof Error ? error.message : 'Unknown error'}`
275
- );
122
+ // Re-throw for SDK error handling
123
+ throw error;
276
124
  }
277
- }
278
-
279
- /**
280
- * Sends a JSON-RPC response
281
- *
282
- * @param id - Request ID
283
- * @param result - Response result
284
- */
285
- private sendResponse(id: RequestId, result: unknown): void {
286
- const response: JsonRpcResponse = {
287
- jsonrpc: '2.0',
288
- id,
289
- result,
290
- };
291
- this.sendMessage(response);
292
- }
293
-
294
- /**
295
- * Sends a JSON-RPC error
296
- *
297
- * @param id - Request ID
298
- * @param code - Error code
299
- * @param message - Error message
300
- * @param data - Additional error data
301
- */
302
- private sendError(
303
- id: RequestId | null,
304
- code: number,
305
- message: string,
306
- data?: Record<string, unknown>
307
- ): void {
308
- const error: JsonRpcError = {
309
- code,
310
- message,
311
- data,
312
- };
125
+ });
313
126
 
314
- const response: JsonRpcResponse = {
315
- jsonrpc: '2.0',
316
- id: id ?? null,
317
- error,
318
- };
319
-
320
- this.sendMessage(response);
321
- }
322
-
323
- /**
324
- * Sends a message to stdout
325
- *
326
- * @param message - The message to send
327
- */
328
- private sendMessage(message: JsonRpcResponse | JsonRpcRequest): void {
329
- const json = JSON.stringify(message);
330
- process.stdout.write(json + '\n');
331
- }
127
+ return server;
128
+ }
332
129
 
333
- /**
334
- * Logs a message
335
- *
336
- * @param level - Log level
337
- * @param message - Log message
338
- */
339
- private log(level: 'debug' | 'info' | 'warn' | 'error', message: string): void {
340
- const logLevel = process.env.MEMHUB_LOG_LEVEL || 'info';
341
- 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();
342
136
 
343
- if (levels[level] >= levels[logLevel as keyof typeof levels]) {
344
- console.error(`[${level.toUpperCase()}] ${message}`);
345
- }
346
- }
137
+ await server.connect(transport);
138
+ console.error('MemHub MCP Server running on stdio');
347
139
  }
348
140
 
349
- // Start server if this file is run directly
350
- if (import.meta.url === `file://${process.argv[1]}`) {
351
- const server = new McpServer();
352
- server.start();
353
- }
141
+ main().catch((error) => {
142
+ console.error('Fatal error:', error);
143
+ process.exit(1);
144
+ });