@memoryrelay/mcp-server 0.1.6
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/LICENSE +21 -0
- package/README.md +583 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +804 -0
- package/dist/index.js.map +1 -0
- package/docs/SECURITY.md +449 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/logger.ts","../src/server.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Configuration schema with security validation\n */\nexport const configSchema = z.object({\n apiKey: z.string()\n .startsWith('mem_', { message: 'API key must start with \"mem_\"' })\n .min(20, { message: 'API key appears to be invalid (too short)' }),\n apiUrl: z.string()\n .url({ message: 'API URL must be a valid URL' })\n .default('https://api.memoryrelay.net'),\n agentId: z.string()\n .optional()\n .describe('Agent identifier - auto-detected if not provided'),\n timeout: z.number()\n .positive({ message: 'Timeout must be positive' })\n .default(30000),\n logLevel: z.enum(['debug', 'info', 'warn', 'error'])\n .default('info'),\n});\n\nexport type Config = z.infer<typeof configSchema>;\n\n/**\n * Load and validate configuration from environment variables\n */\nexport function loadConfig(): Config {\n try {\n const config = configSchema.parse({\n apiKey: process.env.MEMORYRELAY_API_KEY,\n apiUrl: process.env.MEMORYRELAY_API_URL,\n agentId: process.env.MEMORYRELAY_AGENT_ID,\n timeout: process.env.MEMORYRELAY_TIMEOUT \n ? parseInt(process.env.MEMORYRELAY_TIMEOUT, 10) \n : undefined,\n logLevel: process.env.MEMORYRELAY_LOG_LEVEL,\n });\n\n return config;\n } catch (error) {\n if (error instanceof z.ZodError) {\n const issues = error.issues.map(issue => \n ` - ${issue.path.join('.')}: ${issue.message}`\n ).join('\\n');\n \n throw new Error(\n `Configuration validation failed:\\n${issues}\\n\\n` +\n 'Please check your environment variables:\\n' +\n ' - MEMORYRELAY_API_KEY (required, starts with \"mem_\")\\n' +\n ' - MEMORYRELAY_API_URL (optional, default: https://api.memoryrelay.net)\\n' +\n ' - MEMORYRELAY_AGENT_ID (optional, auto-detected)\\n' +\n ' - MEMORYRELAY_TIMEOUT (optional, default: 30000)\\n' +\n ' - MEMORYRELAY_LOG_LEVEL (optional, default: info)'\n );\n }\n throw error;\n }\n}\n\n/**\n * Get or generate agent ID\n */\nexport function getAgentId(config: Config): string {\n if (config.agentId) {\n return config.agentId;\n }\n\n // Auto-generate from hostname\n const hostname = process.env.HOSTNAME || 'unknown';\n return `agent-${hostname.slice(0, 8)}`;\n}\n","/**\n * Security-hardened logger for MCP server\n * \n * - All output to stderr (stdout reserved for MCP protocol)\n * - Automatic masking of API keys (anything starting with \"mem_\")\n * - No internal paths in error messages\n */\n\ntype LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n};\n\nexport class Logger {\n private minLevel: number;\n\n constructor(level: LogLevel = 'info') {\n this.minLevel = LOG_LEVELS[level];\n }\n\n /**\n * Mask sensitive data in log messages\n * - API keys starting with \"mem_\" are masked\n * - Internal paths are sanitized\n */\n private sanitize(message: string): string {\n let sanitized = message;\n\n // Mask API keys (mem_xxx -> mem_****)\n sanitized = sanitized.replace(/mem_[a-zA-Z0-9_-]+/g, 'mem_****');\n\n // Remove internal paths (anything that looks like a file path)\n sanitized = sanitized.replace(/\\/[a-zA-Z0-9_\\-./]+\\.(ts|js|json)/g, '<file>');\n sanitized = sanitized.replace(/at\\s+[^\\s]+\\s+\\([^)]+\\)/g, 'at <location>');\n\n return sanitized;\n }\n\n /**\n * Format log message with timestamp and level\n */\n private format(level: LogLevel, message: string, data?: unknown): string {\n const timestamp = new Date().toISOString();\n const sanitizedMessage = this.sanitize(message);\n \n let output = `[${timestamp}] [${level.toUpperCase()}] ${sanitizedMessage}`;\n \n if (data !== undefined) {\n const sanitizedData = this.sanitize(JSON.stringify(data, null, 2));\n output += `\\n${sanitizedData}`;\n }\n \n return output;\n }\n\n debug(message: string, data?: unknown): void {\n if (this.minLevel <= LOG_LEVELS.debug) {\n console.error(this.format('debug', message, data));\n }\n }\n\n info(message: string, data?: unknown): void {\n if (this.minLevel <= LOG_LEVELS.info) {\n console.error(this.format('info', message, data));\n }\n }\n\n warn(message: string, data?: unknown): void {\n if (this.minLevel <= LOG_LEVELS.warn) {\n console.error(this.format('warn', message, data));\n }\n }\n\n error(message: string, data?: unknown): void {\n if (this.minLevel <= LOG_LEVELS.error) {\n console.error(this.format('error', message, data));\n }\n }\n}\n\n// Export singleton instance\nlet logger: Logger;\n\nexport function initLogger(level: LogLevel = 'info'): Logger {\n logger = new Logger(level);\n return logger;\n}\n\nexport function getLogger(): Logger {\n if (!logger) {\n logger = new Logger();\n }\n return logger;\n}\n","/**\n * MCP Server implementation for MemoryRelay\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport { MemoryRelayClient } from './client.js';\nimport { getLogger } from './logger.js';\nimport type { ClientConfig } from './types.js';\n\n/**\n * HTML-encode string to prevent XSS\n */\nfunction sanitizeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/\\//g, '/');\n}\n\n/**\n * Validate UUID format\n */\nconst uuidSchema = z.string().uuid();\n\nfunction validateUuid(id: string, fieldName: string = 'id'): void {\n const result = uuidSchema.safeParse(id);\n if (!result.success) {\n throw new Error(`Invalid ${fieldName}: must be a valid UUID`);\n }\n}\n\nexport class MemoryRelayMCPServer {\n private server: Server;\n private client: MemoryRelayClient;\n private logger = getLogger();\n\n constructor(config: ClientConfig) {\n this.client = new MemoryRelayClient(config);\n \n this.server = new Server(\n {\n name: '@memoryrelay/mcp-server',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n this.setupHandlers();\n this.logger.info('MCP server initialized');\n }\n\n /**\n * Setup MCP protocol handlers\n */\n private setupHandlers(): void {\n // List available tools\n this.server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: 'memory_store',\n description: 'Store a new memory. Use this to save important information, facts, preferences, or context that should be remembered for future conversations.',\n inputSchema: {\n type: 'object',\n properties: {\n content: {\n type: 'string',\n description: 'The memory content to store. Be specific and include relevant context.',\n },\n metadata: {\n type: 'object',\n description: 'Optional key-value metadata to attach to the memory',\n additionalProperties: { type: 'string' },\n },\n },\n required: ['content'],\n },\n },\n {\n name: 'memory_search',\n description: 'Search memories using natural language. Returns the most relevant memories based on semantic similarity to the query.',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Natural language search query',\n },\n limit: {\n type: 'number',\n description: 'Maximum number of results to return (1-50)',\n minimum: 1,\n maximum: 50,\n default: 10,\n },\n threshold: {\n type: 'number',\n description: 'Minimum similarity threshold (0-1)',\n minimum: 0,\n maximum: 1,\n default: 0.5,\n },\n },\n required: ['query'],\n },\n },\n {\n name: 'memory_list',\n description: 'List recent memories chronologically. Use to review what has been remembered.',\n inputSchema: {\n type: 'object',\n properties: {\n limit: {\n type: 'number',\n description: 'Number of memories to return (1-100)',\n minimum: 1,\n maximum: 100,\n default: 20,\n },\n offset: {\n type: 'number',\n description: 'Offset for pagination',\n minimum: 0,\n default: 0,\n },\n },\n },\n },\n {\n name: 'memory_get',\n description: 'Retrieve a specific memory by its ID.',\n inputSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The memory ID (UUID) to retrieve',\n },\n },\n required: ['id'],\n },\n },\n {\n name: 'memory_update',\n description: 'Update the content of an existing memory. Use to correct or expand stored information.',\n inputSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The memory ID (UUID) to update',\n },\n content: {\n type: 'string',\n description: 'The new content to replace the existing memory',\n },\n metadata: {\n type: 'object',\n description: 'Updated metadata (replaces existing)',\n additionalProperties: { type: 'string' },\n },\n },\n required: ['id', 'content'],\n },\n },\n {\n name: 'memory_delete',\n description: 'Permanently delete a memory. Use sparingly - memories are valuable context.',\n inputSchema: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The memory ID (UUID) to delete',\n },\n },\n required: ['id'],\n },\n },\n {\n name: 'entity_create',\n description: 'Create a named entity (person, place, organization, project, concept) for the knowledge graph. Entities help organize and connect memories.',\n inputSchema: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n minLength: 1,\n maxLength: 200,\n description: 'Entity name (1-200 characters)',\n },\n type: {\n type: 'string',\n enum: ['person', 'place', 'organization', 'project', 'concept', 'other'],\n description: 'Entity type classification',\n },\n metadata: {\n type: 'object',\n description: 'Optional key-value metadata',\n additionalProperties: { type: 'string' },\n },\n },\n required: ['name', 'type'],\n },\n },\n {\n name: 'entity_link',\n description: 'Link an entity to a memory to establish relationships in the knowledge graph.',\n inputSchema: {\n type: 'object',\n properties: {\n entity_id: {\n type: 'string',\n description: 'Entity UUID',\n },\n memory_id: {\n type: 'string',\n description: 'Memory UUID',\n },\n relationship: {\n type: 'string',\n description: 'Relationship type (e.g., \"mentioned_in\", \"created_by\", \"relates_to\")',\n default: 'mentioned_in',\n },\n },\n required: ['entity_id', 'memory_id'],\n },\n },\n {\n name: 'memory_health',\n description: 'Check API connectivity and health status.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n ],\n }));\n\n // Handle tool calls\n this.server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params;\n\n this.logger.debug(`Tool called: ${name}`, args);\n\n try {\n switch (name) {\n case 'memory_store': {\n const memory = await this.client.storeMemory(\n args.content as string,\n args.metadata as Record<string, string> | undefined\n );\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(memory, null, 2),\n },\n ],\n };\n }\n\n case 'memory_search': {\n const results = await this.client.searchMemories(\n args.query as string,\n args.limit as number | undefined,\n args.threshold as number | undefined\n );\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n { memories: results, total: results.length },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n case 'memory_list': {\n const response = await this.client.listMemories(\n args.limit as number | undefined,\n args.offset as number | undefined\n );\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(response, null, 2),\n },\n ],\n };\n }\n\n case 'memory_get': {\n const id = args.id as string;\n validateUuid(id, 'memory_id');\n \n const memory = await this.client.getMemory(id);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(memory, null, 2),\n },\n ],\n };\n }\n\n case 'memory_update': {\n const id = args.id as string;\n validateUuid(id, 'memory_id');\n \n const memory = await this.client.updateMemory(\n id,\n args.content as string,\n args.metadata as Record<string, string> | undefined\n );\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(memory, null, 2),\n },\n ],\n };\n }\n\n case 'memory_delete': {\n const id = args.id as string;\n validateUuid(id, 'memory_id');\n \n await this.client.deleteMemory(id);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n { success: true, message: 'Memory deleted successfully' },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n case 'entity_create': {\n // Validate input schema\n const entitySchema = z.object({\n name: z.string().min(1).max(200),\n type: z.enum(['person', 'place', 'organization', 'project', 'concept', 'other']),\n metadata: z.record(z.string()).optional(),\n });\n\n const validatedInput = entitySchema.parse(args);\n \n // Sanitize name to prevent XSS\n const sanitizedName = sanitizeHtml(validatedInput.name);\n \n const entity = await this.client.createEntity(\n sanitizedName,\n validatedInput.type,\n validatedInput.metadata\n );\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(entity, null, 2),\n },\n ],\n };\n }\n\n case 'entity_link': {\n // Validate input schema\n const linkSchema = z.object({\n entity_id: z.string().uuid(),\n memory_id: z.string().uuid(),\n relationship: z.string().default('mentioned_in'),\n });\n\n const validatedInput = linkSchema.parse(args);\n \n await this.client.linkEntity(\n validatedInput.entity_id,\n validatedInput.memory_id,\n validatedInput.relationship\n );\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n success: true,\n message: 'Entity linked to memory successfully',\n entity_id: validatedInput.entity_id,\n memory_id: validatedInput.memory_id,\n relationship: validatedInput.relationship,\n },\n null,\n 2\n ),\n },\n ],\n };\n }\n\n case 'memory_health': {\n const health = await this.client.healthCheck();\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(health, null, 2),\n },\n ],\n };\n }\n\n default:\n throw new Error(`Unknown tool: ${name}`);\n }\n } catch (error) {\n let errorMessage = 'Unknown error';\n let errorDetails: unknown = undefined;\n\n if (error instanceof z.ZodError) {\n errorMessage = 'Validation error';\n errorDetails = error.errors;\n } else if (error instanceof Error) {\n errorMessage = error.message;\n }\n\n this.logger.error(`Tool execution failed: ${name}`, {\n error: errorMessage,\n details: errorDetails,\n });\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n error: 'Tool execution failed',\n message: errorMessage,\n details: errorDetails,\n },\n null,\n 2\n ),\n },\n ],\n isError: true,\n };\n }\n });\n }\n\n /**\n * Start the MCP server with STDIO transport\n */\n async start(): Promise<void> {\n const transport = new StdioServerTransport();\n await this.server.connect(transport);\n this.logger.info('MCP server started on STDIO');\n }\n}\n","/**\n * MemoryRelay API client with retry logic and error handling\n */\n\nimport type {\n Memory,\n Entity,\n SearchResult,\n ListResponse,\n ClientConfig,\n EntityType,\n} from './types.js';\nimport { getLogger } from './logger.js';\n\n// Retry configuration\nconst MAX_RETRIES = 3;\nconst INITIAL_DELAY_MS = 1000;\nconst MAX_CONTENT_SIZE = 50 * 1024; // 50KB\n\n/**\n * Exponential backoff retry wrapper\n */\nasync function withRetry<T>(\n fn: () => Promise<T>,\n retries: number = MAX_RETRIES\n): Promise<T> {\n let lastError: Error | undefined;\n \n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n \n // Don't retry on certain errors\n if (\n lastError.message.includes('401') ||\n lastError.message.includes('403') ||\n lastError.message.includes('404') ||\n lastError.message.includes('400')\n ) {\n throw lastError;\n }\n \n // On last attempt, throw the error\n if (attempt === retries) {\n throw lastError;\n }\n \n // Exponential backoff with jitter\n const delay = INITIAL_DELAY_MS * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * delay;\n await new Promise(resolve => setTimeout(resolve, delay + jitter));\n }\n }\n \n throw lastError || new Error('Retry failed');\n}\n\n/**\n * Mask API key in error messages for security\n */\nfunction maskApiKey(message: string, apiKey: string): string {\n if (!apiKey) return message;\n const maskedKey = apiKey.substring(0, 8) + '***';\n return message.replace(new RegExp(apiKey, 'g'), maskedKey);\n}\n\nexport class MemoryRelayClient {\n private config: ClientConfig;\n private logger = getLogger();\n\n constructor(config: ClientConfig) {\n this.config = config;\n this.logger.info('MemoryRelay client initialized', {\n apiUrl: config.apiUrl,\n agentId: config.agentId,\n });\n }\n\n /**\n * Make authenticated HTTP request to MemoryRelay API with retry logic\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown\n ): Promise<T> {\n return withRetry(async () => {\n const url = `${this.config.apiUrl}${path}`;\n \n this.logger.debug(`API request: ${method} ${path}`);\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'User-Agent': '@memoryrelay/mcp-server',\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n if (!response.ok) {\n // Handle rate limiting with retry\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n const waitMs = retryAfter ? parseInt(retryAfter) * 1000 : 5000;\n this.logger.warn(`Rate limited, waiting ${waitMs}ms`);\n await new Promise(resolve => setTimeout(resolve, waitMs));\n throw new Error(`Rate limited: 429 - Retry after ${waitMs}ms`);\n }\n\n const errorData = await response.json().catch(() => ({}));\n const errorMsg = `API request failed: ${response.status} ${response.statusText}` +\n (errorData.message ? ` - ${errorData.message}` : '');\n \n // Mask API key in error message\n throw new Error(maskApiKey(errorMsg, this.config.apiKey));\n }\n\n const data = await response.json();\n this.logger.debug(`API response: ${method} ${path}`, { status: response.status });\n \n return data as T;\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new Error(`Request timeout after ${this.config.timeout}ms`);\n }\n // Mask API key in all error messages\n error.message = maskApiKey(error.message, this.config.apiKey);\n }\n throw error;\n } finally {\n clearTimeout(timeout);\n }\n });\n }\n\n /**\n * Validate content size\n */\n private validateContentSize(content: string): void {\n if (content.length > MAX_CONTENT_SIZE) {\n throw new Error(`Content exceeds maximum size of ${MAX_CONTENT_SIZE} bytes`);\n }\n }\n\n /**\n * Store a new memory\n */\n async storeMemory(content: string, metadata?: Record<string, string>): Promise<Memory> {\n this.validateContentSize(content);\n \n return this.request<Memory>('POST', '/v1/memories/memories', {\n content,\n metadata,\n agent_id: this.config.agentId,\n });\n }\n\n /**\n * Search memories using semantic search\n */\n async searchMemories(\n query: string,\n limit: number = 10,\n threshold: number = 0.5\n ): Promise<SearchResult[]> {\n this.validateContentSize(query);\n \n const response = await this.request<{ data: SearchResult[] }>(\n 'POST',\n '/v1/memories/memories/search',\n { query, limit, threshold, agent_id: this.config.agentId }\n );\n return response.data;\n }\n\n /**\n * List recent memories with pagination\n */\n async listMemories(limit: number = 20, offset: number = 0): Promise<ListResponse<Memory>> {\n return this.request<ListResponse<Memory>>(\n 'GET',\n `/v1/memories/memories?limit=${limit}&offset=${offset}`\n );\n }\n\n /**\n * Get a specific memory by ID\n */\n async getMemory(id: string): Promise<Memory> {\n return this.request<Memory>('GET', `/v1/memories/memories/${id}`);\n }\n\n /**\n * Update an existing memory\n */\n async updateMemory(\n id: string,\n content: string,\n metadata?: Record<string, string>\n ): Promise<Memory> {\n this.validateContentSize(content);\n \n return this.request<Memory>('PATCH', `/v1/memories/memories/${id}`, {\n content,\n metadata,\n });\n }\n\n /**\n * Delete a memory\n */\n async deleteMemory(id: string): Promise<void> {\n await this.request<void>('DELETE', `/v1/memories/memories/${id}`);\n }\n\n /**\n * Create a named entity\n */\n async createEntity(\n name: string,\n type: EntityType,\n metadata?: Record<string, string>\n ): Promise<Entity> {\n this.validateContentSize(name);\n \n return this.request<Entity>('POST', '/v1/entities', {\n name,\n type,\n metadata,\n });\n }\n\n /**\n * Link an entity to a memory\n */\n async linkEntity(\n entityId: string,\n memoryId: string,\n relationship: string = 'mentioned_in'\n ): Promise<void> {\n await this.request<void>('POST', '/v1/entities/links', {\n entity_id: entityId,\n memory_id: memoryId,\n relationship,\n });\n }\n\n /**\n * Get an entity by ID\n */\n async getEntity(id: string): Promise<Entity> {\n return this.request<Entity>('GET', `/v1/entities/${id}`);\n }\n\n /**\n * List entities with pagination\n */\n async listEntities(limit: number = 20, offset: number = 0): Promise<ListResponse<Entity>> {\n return this.request<ListResponse<Entity>>(\n 'GET',\n `/v1/entities?limit=${limit}&offset=${offset}`\n );\n }\n\n /**\n * Delete an entity\n */\n async deleteEntity(id: string): Promise<void> {\n await this.request<void>('DELETE', `/v1/entities/${id}`);\n }\n\n /**\n * Health check - verify API connectivity\n */\n async healthCheck(): Promise<{ status: string; message: string }> {\n try {\n // Simple GET request to check API is reachable\n await this.request<{ status: string }>('GET', '/v1/health');\n return {\n status: 'healthy',\n message: 'API connection successful',\n };\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : 'Unknown error';\n return {\n status: 'unhealthy',\n message: `API connection failed: ${errorMsg}`,\n };\n }\n }\n}\n","#!/usr/bin/env node\n/**\n * MemoryRelay MCP Server - Entry Point\n * \n * Provides persistent memory storage for AI agents via Model Context Protocol\n */\n\nimport { loadConfig, getAgentId } from './config.js';\nimport { initLogger, getLogger } from './logger.js';\nimport { MemoryRelayMCPServer } from './server.js';\n\nasync function main() {\n try {\n // Load and validate configuration\n const config = loadConfig();\n \n // Initialize logger with configured level\n initLogger(config.logLevel);\n const logger = getLogger();\n\n logger.info('Starting MemoryRelay MCP server');\n\n // Get or generate agent ID\n const agentId = getAgentId(config);\n\n // Create and start MCP server\n const server = new MemoryRelayMCPServer({\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n agentId,\n timeout: config.timeout,\n });\n\n await server.start();\n\n // Keep process alive\n process.on('SIGINT', () => {\n logger.info('Received SIGINT, shutting down gracefully');\n process.exit(0);\n });\n\n process.on('SIGTERM', () => {\n logger.info('Received SIGTERM, shutting down gracefully');\n process.exit(0);\n });\n\n } catch (error) {\n const logger = getLogger();\n \n if (error instanceof Error) {\n logger.error('Fatal error:', { message: error.message });\n \n // Print user-friendly error to stderr\n console.error('\\nā Failed to start MemoryRelay MCP server\\n');\n console.error(error.message);\n console.error('\\nFor help, see: https://github.com/Alteriom/ai-memory-service/tree/main/mcp\\n');\n } else {\n logger.error('Fatal error:', { error });\n console.error('\\nā An unexpected error occurred\\n');\n }\n \n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["z","z","logger"]}
|
package/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# Security Guide
|
|
2
|
+
|
|
3
|
+
This document outlines the security measures implemented in the MemoryRelay MCP server and provides best practices for secure deployment.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## š Overview
|
|
8
|
+
|
|
9
|
+
The MemoryRelay MCP server is designed with security as a top priority:
|
|
10
|
+
|
|
11
|
+
- **No credential storage** - API keys never written to disk
|
|
12
|
+
- **Automatic key masking** - Sensitive data hidden in all logs
|
|
13
|
+
- **Input validation** - All inputs validated before processing
|
|
14
|
+
- **Error sanitization** - No internal paths or sensitive data leaked
|
|
15
|
+
- **STDIO isolation** - Logs strictly to stderr per MCP spec
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## š API Key Handling
|
|
20
|
+
|
|
21
|
+
### Best Practices
|
|
22
|
+
|
|
23
|
+
#### ā
DO
|
|
24
|
+
|
|
25
|
+
- Store API keys in environment variables only
|
|
26
|
+
- Use your MCP client's configuration file to set environment variables
|
|
27
|
+
- Rotate API keys regularly (recommended: every 90 days)
|
|
28
|
+
- Use different API keys for different agents/environments
|
|
29
|
+
- Revoke compromised keys immediately via the MemoryRelay dashboard
|
|
30
|
+
|
|
31
|
+
#### ā DON'T
|
|
32
|
+
|
|
33
|
+
- Never commit API keys to version control
|
|
34
|
+
- Never hardcode API keys in configuration files
|
|
35
|
+
- Never share API keys between production and development
|
|
36
|
+
- Never log API keys manually in your own code
|
|
37
|
+
- Never expose API keys in command-line arguments (use env vars)
|
|
38
|
+
|
|
39
|
+
### How Keys Are Protected
|
|
40
|
+
|
|
41
|
+
The MCP server automatically masks any string starting with `mem_` in:
|
|
42
|
+
|
|
43
|
+
- Application logs (stderr)
|
|
44
|
+
- Error messages
|
|
45
|
+
- Debug output
|
|
46
|
+
- API request/response logging
|
|
47
|
+
|
|
48
|
+
**Example:**
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// In logs, this:
|
|
52
|
+
MEMORYRELAY_API_KEY=mem_prod_abc123xyz789
|
|
53
|
+
|
|
54
|
+
// Becomes:
|
|
55
|
+
MEMORYRELAY_API_KEY=mem_prod_***
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Key Format
|
|
59
|
+
|
|
60
|
+
Valid API keys have the format:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
mem_{environment}_{random_string}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Where `{environment}` is typically:
|
|
67
|
+
- `prod` - Production keys
|
|
68
|
+
- `dev` - Development keys
|
|
69
|
+
- `test` - Testing keys
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## š Environment Variables
|
|
74
|
+
|
|
75
|
+
### Recommended Configuration
|
|
76
|
+
|
|
77
|
+
Always configure via your MCP client's environment block:
|
|
78
|
+
|
|
79
|
+
**OpenClaw** (`~/.openclaw/openclaw.json`):
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"memoryrelay": {
|
|
84
|
+
"command": "npx",
|
|
85
|
+
"args": ["-y", "@memoryrelay/mcp-server"],
|
|
86
|
+
"env": {
|
|
87
|
+
"MEMORYRELAY_API_KEY": "mem_prod_xxxxx",
|
|
88
|
+
"MEMORYRELAY_AGENT_ID": "iris",
|
|
89
|
+
"MEMORYRELAY_LOG_LEVEL": "info"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Claude Desktop**:
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"memoryrelay": {
|
|
101
|
+
"command": "npx",
|
|
102
|
+
"args": ["-y", "@memoryrelay/mcp-server"],
|
|
103
|
+
"env": {
|
|
104
|
+
"MEMORYRELAY_API_KEY": "mem_prod_xxxxx"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### File Permissions
|
|
112
|
+
|
|
113
|
+
Protect your configuration files:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# macOS/Linux
|
|
117
|
+
chmod 600 ~/.openclaw/openclaw.json
|
|
118
|
+
chmod 600 ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
|
119
|
+
|
|
120
|
+
# Verify permissions
|
|
121
|
+
ls -la ~/.openclaw/openclaw.json
|
|
122
|
+
# Should show: -rw------- (read/write for owner only)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Environment Variable Precedence
|
|
126
|
+
|
|
127
|
+
The server reads configuration in this order (first found wins):
|
|
128
|
+
|
|
129
|
+
1. `MEMORYRELAY_API_KEY` - Explicit API key
|
|
130
|
+
2. `MEMORYRELAY_API_URL` - Custom API endpoint (defaults to production)
|
|
131
|
+
3. `MEMORYRELAY_AGENT_ID` - Agent identifier
|
|
132
|
+
4. `OPENCLAW_AGENT_NAME` - OpenClaw auto-detection fallback
|
|
133
|
+
5. Hostname-based generation - Last resort for agent ID
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## š Logging & Data Handling
|
|
138
|
+
|
|
139
|
+
### What Gets Logged
|
|
140
|
+
|
|
141
|
+
The MCP server logs to **stderr only** (stdout is reserved for MCP protocol):
|
|
142
|
+
|
|
143
|
+
#### Always Logged (INFO level):
|
|
144
|
+
- Server startup/shutdown events
|
|
145
|
+
- API connectivity status
|
|
146
|
+
- Tool invocation names (but NOT parameters)
|
|
147
|
+
|
|
148
|
+
#### Debug Level Only:
|
|
149
|
+
- Tool invocation parameters (with sensitive data masked)
|
|
150
|
+
- API request/response metadata (not full bodies)
|
|
151
|
+
- Validation errors with sanitized details
|
|
152
|
+
|
|
153
|
+
#### NEVER Logged:
|
|
154
|
+
- Full API key values
|
|
155
|
+
- Memory content (your actual data)
|
|
156
|
+
- Full API request/response bodies in production
|
|
157
|
+
- Internal file paths or stack traces
|
|
158
|
+
|
|
159
|
+
### Log Levels
|
|
160
|
+
|
|
161
|
+
Configure via `MEMORYRELAY_LOG_LEVEL`:
|
|
162
|
+
|
|
163
|
+
| Level | Purpose | Use Case |
|
|
164
|
+
|-------|---------|----------|
|
|
165
|
+
| `error` | Critical errors only | Production (minimal logging) |
|
|
166
|
+
| `info` | Standard operations | Production (recommended) |
|
|
167
|
+
| `debug` | Detailed diagnostics | Development/troubleshooting |
|
|
168
|
+
|
|
169
|
+
**Production Recommendation:** Use `info` level
|
|
170
|
+
|
|
171
|
+
**Development:** Use `debug` to see tool parameters and API details
|
|
172
|
+
|
|
173
|
+
### Sensitive Data Masking
|
|
174
|
+
|
|
175
|
+
Automatic masking patterns:
|
|
176
|
+
|
|
177
|
+
- **API Keys**: `mem_*` ā `mem_prod_***`
|
|
178
|
+
- **UUIDs**: Logged but not considered sensitive (they're already access-controlled)
|
|
179
|
+
- **Usernames/Emails**: Not currently logged
|
|
180
|
+
|
|
181
|
+
### Example Log Output
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
[2026-02-12T00:00:00.000Z] INFO: Starting MemoryRelay MCP server
|
|
185
|
+
[2026-02-12T00:00:00.100Z] INFO: MCP server initialized
|
|
186
|
+
[2026-02-12T00:00:00.200Z] INFO: MCP server started on STDIO
|
|
187
|
+
[2026-02-12T00:00:10.000Z] DEBUG: Tool called: memory_store {"content":"...","metadata":{...}}
|
|
188
|
+
[2026-02-12T00:00:10.100Z] DEBUG: API request: POST /memories (key: mem_prod_***)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## ā
Input Validation
|
|
194
|
+
|
|
195
|
+
### Automatic Validation
|
|
196
|
+
|
|
197
|
+
All tool inputs are validated using Zod schemas:
|
|
198
|
+
|
|
199
|
+
#### Memory Content
|
|
200
|
+
- Type: String (required)
|
|
201
|
+
- No length limit (API enforces reasonable limits)
|
|
202
|
+
- HTML-encoded when used in entity names
|
|
203
|
+
|
|
204
|
+
#### Entity Names
|
|
205
|
+
- Type: String (required)
|
|
206
|
+
- Length: 1-200 characters
|
|
207
|
+
- Automatically sanitized to prevent XSS
|
|
208
|
+
|
|
209
|
+
#### UUIDs
|
|
210
|
+
- Format: Standard UUID v4
|
|
211
|
+
- Validated before API calls
|
|
212
|
+
- Invalid UUIDs rejected immediately
|
|
213
|
+
|
|
214
|
+
#### Metadata
|
|
215
|
+
- Type: Object with string values
|
|
216
|
+
- Keys and values are strings only
|
|
217
|
+
- No nested objects allowed
|
|
218
|
+
|
|
219
|
+
#### Numbers
|
|
220
|
+
- Limits and offsets: Non-negative integers
|
|
221
|
+
- Thresholds: Float between 0 and 1
|
|
222
|
+
- Timeouts: Positive integers
|
|
223
|
+
|
|
224
|
+
### XSS Prevention
|
|
225
|
+
|
|
226
|
+
Entity names are HTML-encoded:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
// Input:
|
|
230
|
+
name: "<script>alert('xss')</script>"
|
|
231
|
+
|
|
232
|
+
// Stored as:
|
|
233
|
+
name: "<script>alert('xss')</script>"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
This prevents injection attacks if entity names are displayed in web interfaces.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## š« Error Handling
|
|
241
|
+
|
|
242
|
+
### Error Sanitization
|
|
243
|
+
|
|
244
|
+
Errors are sanitized before being returned to clients:
|
|
245
|
+
|
|
246
|
+
#### What's Included:
|
|
247
|
+
- User-friendly error message
|
|
248
|
+
- Error type (validation, network, API error)
|
|
249
|
+
- Field-level validation details (for Zod errors)
|
|
250
|
+
|
|
251
|
+
#### What's Excluded:
|
|
252
|
+
- Internal file paths
|
|
253
|
+
- Stack traces
|
|
254
|
+
- Environment variables
|
|
255
|
+
- API endpoint details
|
|
256
|
+
- Server configuration
|
|
257
|
+
|
|
258
|
+
### Example Error Response
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"error": "Tool execution failed",
|
|
263
|
+
"message": "Validation error",
|
|
264
|
+
"details": [
|
|
265
|
+
{
|
|
266
|
+
"code": "invalid_type",
|
|
267
|
+
"expected": "string",
|
|
268
|
+
"received": "number",
|
|
269
|
+
"path": ["content"],
|
|
270
|
+
"message": "Expected string, received number"
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## š Rate Limiting
|
|
279
|
+
|
|
280
|
+
### Client-Side Behavior
|
|
281
|
+
|
|
282
|
+
The MCP server respects API rate limits:
|
|
283
|
+
|
|
284
|
+
- **Timeout**: Default 30 seconds per request (configurable)
|
|
285
|
+
- **Retries**: No automatic retries (MCP clients should handle retries)
|
|
286
|
+
- **Backoff**: Not implemented (API returns 429 if rate limited)
|
|
287
|
+
|
|
288
|
+
### API Rate Limits
|
|
289
|
+
|
|
290
|
+
The MemoryRelay API enforces these limits (as of 2026-02-12):
|
|
291
|
+
|
|
292
|
+
| Tier | Requests/min | Requests/day |
|
|
293
|
+
|------|--------------|--------------|
|
|
294
|
+
| Free | 20 | 1,000 |
|
|
295
|
+
| Pro | 100 | 10,000 |
|
|
296
|
+
| Enterprise | Custom | Custom |
|
|
297
|
+
|
|
298
|
+
**429 Responses**: When rate limited, the API returns:
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"error": "Rate limit exceeded",
|
|
302
|
+
"retry_after": 60
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Handling Rate Limits
|
|
307
|
+
|
|
308
|
+
The MCP server will return rate limit errors to the client. Your MCP client (Claude Desktop, OpenClaw) should:
|
|
309
|
+
|
|
310
|
+
1. Display the error to the user
|
|
311
|
+
2. Retry after the `retry_after` period
|
|
312
|
+
3. Consider caching frequently accessed memories
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## š”ļø Security Checklist
|
|
317
|
+
|
|
318
|
+
Use this checklist when deploying the MCP server:
|
|
319
|
+
|
|
320
|
+
### Configuration
|
|
321
|
+
|
|
322
|
+
- [ ] API key stored in environment variable (not hardcoded)
|
|
323
|
+
- [ ] Configuration file permissions set to 600 (owner read/write only)
|
|
324
|
+
- [ ] Different API keys for different environments (dev/prod)
|
|
325
|
+
- [ ] `MEMORYRELAY_LOG_LEVEL` set to `info` or `error` in production
|
|
326
|
+
- [ ] Custom `MEMORYRELAY_AGENT_ID` configured (optional but recommended)
|
|
327
|
+
|
|
328
|
+
### Environment
|
|
329
|
+
|
|
330
|
+
- [ ] Node.js version 18+ (22+ recommended)
|
|
331
|
+
- [ ] MCP client is up to date (Claude Desktop, OpenClaw)
|
|
332
|
+
- [ ] Server runs with minimal privileges (not root/admin)
|
|
333
|
+
- [ ] Network connectivity to `api.memoryrelay.net` confirmed
|
|
334
|
+
|
|
335
|
+
### Monitoring
|
|
336
|
+
|
|
337
|
+
- [ ] Server logs reviewed regularly for errors
|
|
338
|
+
- [ ] API key usage monitored via MemoryRelay dashboard
|
|
339
|
+
- [ ] Unusual activity alerts configured (if available)
|
|
340
|
+
- [ ] Regular API key rotation schedule established
|
|
341
|
+
|
|
342
|
+
### Response Plan
|
|
343
|
+
|
|
344
|
+
- [ ] Procedure documented for API key compromise
|
|
345
|
+
- [ ] Backup MCP client configuration saved securely
|
|
346
|
+
- [ ] Contact information for MemoryRelay support saved
|
|
347
|
+
- [ ] Incident response plan includes MCP server logs
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## šØ Security Issues
|
|
352
|
+
|
|
353
|
+
### Reporting Vulnerabilities
|
|
354
|
+
|
|
355
|
+
If you discover a security vulnerability:
|
|
356
|
+
|
|
357
|
+
1. **DO NOT** open a public GitHub issue
|
|
358
|
+
2. Email security@memoryrelay.net with:
|
|
359
|
+
- Description of the vulnerability
|
|
360
|
+
- Steps to reproduce
|
|
361
|
+
- Potential impact
|
|
362
|
+
- Suggested fix (if any)
|
|
363
|
+
|
|
364
|
+
3. Allow 90 days for response and patch before public disclosure
|
|
365
|
+
|
|
366
|
+
### Security Updates
|
|
367
|
+
|
|
368
|
+
Monitor for security updates:
|
|
369
|
+
|
|
370
|
+
- **GitHub Releases**: https://github.com/memoryrelay/mcp-server/releases
|
|
371
|
+
- **npm Security Advisories**: `npm audit` in the project directory
|
|
372
|
+
- **Dependencies**: Regularly run `npm update` and `npm audit fix`
|
|
373
|
+
|
|
374
|
+
### Known Limitations
|
|
375
|
+
|
|
376
|
+
Current known limitations:
|
|
377
|
+
|
|
378
|
+
1. **No built-in encryption**: Memory content is encrypted at rest by the API, but not additionally encrypted by the MCP server
|
|
379
|
+
2. **No client authentication**: MCP protocol doesn't provide client authentication (trust your MCP client)
|
|
380
|
+
3. **No request signing**: API requests are authenticated via API key only
|
|
381
|
+
4. **No rate limit caching**: Server doesn't cache rate limit state
|
|
382
|
+
|
|
383
|
+
These are either API-level concerns or MCP protocol limitations, not server bugs.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## š§ Advanced Security Configuration
|
|
388
|
+
|
|
389
|
+
### Custom API Endpoint
|
|
390
|
+
|
|
391
|
+
For self-hosted MemoryRelay deployments:
|
|
392
|
+
|
|
393
|
+
```json
|
|
394
|
+
{
|
|
395
|
+
"env": {
|
|
396
|
+
"MEMORYRELAY_API_URL": "https://memory.internal.company.com",
|
|
397
|
+
"MEMORYRELAY_API_KEY": "mem_prod_xxxxx"
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Security Note:** Always use HTTPS for custom endpoints.
|
|
403
|
+
|
|
404
|
+
### Network Isolation
|
|
405
|
+
|
|
406
|
+
For high-security environments:
|
|
407
|
+
|
|
408
|
+
1. Use a firewall to restrict outbound connections to `api.memoryrelay.net` only
|
|
409
|
+
2. Use a proxy server for MCP server traffic (set `HTTPS_PROXY` env var)
|
|
410
|
+
3. Consider running MCP server in a sandboxed environment (Docker, VM)
|
|
411
|
+
|
|
412
|
+
### Audit Logging
|
|
413
|
+
|
|
414
|
+
To enable comprehensive audit logging:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
# Run MCP server with debug logging redirected to a file
|
|
418
|
+
# (Your MCP client must support this - OpenClaw does)
|
|
419
|
+
|
|
420
|
+
# In openclaw.json:
|
|
421
|
+
{
|
|
422
|
+
"mcpServers": {
|
|
423
|
+
"memoryrelay": {
|
|
424
|
+
"command": "bash",
|
|
425
|
+
"args": ["-c", "npx -y @memoryrelay/mcp-server 2>> /var/log/memoryrelay-mcp.log"],
|
|
426
|
+
"env": {
|
|
427
|
+
"MEMORYRELAY_LOG_LEVEL": "debug",
|
|
428
|
+
"MEMORYRELAY_API_KEY": "mem_prod_xxxxx"
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Warning:** Debug logs may contain sensitive information. Protect log files appropriately.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## š Further Reading
|
|
440
|
+
|
|
441
|
+
- [MCP Security Best Practices](https://modelcontextprotocol.io/security)
|
|
442
|
+
- [MemoryRelay API Documentation](https://api.memoryrelay.net/docs)
|
|
443
|
+
- [OWASP Secure Coding Practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/)
|
|
444
|
+
- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
**Last Updated:** 2026-02-12
|
|
449
|
+
**Version:** 0.1.0
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memoryrelay/mcp-server",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "MCP server for MemoryRelay - persistent memory for AI agents",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"memoryrelay-mcp-server": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"docs"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"dev": "tsup --watch",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"test:integration": "vitest run --config vitest.integration.config.ts tests/integration.test.ts",
|
|
22
|
+
"test:server": "vitest run tests/server.test.ts",
|
|
23
|
+
"test:security": "vitest run tests/security.test.ts",
|
|
24
|
+
"test:all": "npm test && npm run test:integration",
|
|
25
|
+
"test:coverage": "vitest run --coverage",
|
|
26
|
+
"type-check": "tsc --noEmit",
|
|
27
|
+
"lint": "echo 'Lint not configured yet'",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"model-context-protocol",
|
|
33
|
+
"memory",
|
|
34
|
+
"ai",
|
|
35
|
+
"agent",
|
|
36
|
+
"openclaw",
|
|
37
|
+
"claude",
|
|
38
|
+
"persistent-memory",
|
|
39
|
+
"semantic-search",
|
|
40
|
+
"knowledge-graph"
|
|
41
|
+
],
|
|
42
|
+
"author": "MemoryRelay Team",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/memoryrelay/mcp-server.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/memoryrelay/mcp-server/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/memoryrelay/mcp-server#readme",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"registry": "https://registry.npmjs.org/"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
58
|
+
"zod": "^3.25.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/node": "^22.0.0",
|
|
62
|
+
"tsup": "^8.0.0",
|
|
63
|
+
"typescript": "^5.4.0",
|
|
64
|
+
"vitest": "^3.0.0"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=18.0.0"
|
|
68
|
+
}
|
|
69
|
+
}
|