@prmichaelsen/remember-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +65 -0
- package/AGENT.md +840 -0
- package/README.md +72 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/access-control-result-pattern.md +458 -0
- package/agent/design/action-audit-memory-types.md +637 -0
- package/agent/design/common-template-fields.md +282 -0
- package/agent/design/complete-tool-set.md +407 -0
- package/agent/design/content-types-expansion.md +521 -0
- package/agent/design/cross-database-id-strategy.md +358 -0
- package/agent/design/default-template-library.md +423 -0
- package/agent/design/firestore-wrapper-analysis.md +606 -0
- package/agent/design/llm-provider-abstraction.md +691 -0
- package/agent/design/location-handling-architecture.md +523 -0
- package/agent/design/memory-templates-design.md +364 -0
- package/agent/design/permissions-storage-architecture.md +680 -0
- package/agent/design/relationship-storage-strategy.md +361 -0
- package/agent/design/remember-mcp-implementation-tasks.md +417 -0
- package/agent/design/remember-mcp-progress.yaml +141 -0
- package/agent/design/requirements-enhancements.md +468 -0
- package/agent/design/requirements.md +56 -0
- package/agent/design/template-storage-strategy.md +412 -0
- package/agent/design/template-suggestion-system.md +853 -0
- package/agent/design/trust-escalation-prevention.md +343 -0
- package/agent/design/trust-system-implementation.md +592 -0
- package/agent/design/user-preferences.md +683 -0
- package/agent/design/weaviate-collection-strategy.md +461 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-foundation.md +121 -0
- package/agent/milestones/milestone-2-core-memory-system.md +150 -0
- package/agent/milestones/milestone-3-relationships-graph.md +116 -0
- package/agent/milestones/milestone-4-user-preferences.md +103 -0
- package/agent/milestones/milestone-5-template-system.md +126 -0
- package/agent/milestones/milestone-6-auth-multi-tenancy.md +124 -0
- package/agent/milestones/milestone-7-trust-permissions.md +133 -0
- package/agent/milestones/milestone-8-testing-quality.md +137 -0
- package/agent/milestones/milestone-9-deployment-documentation.md +147 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.md +1271 -0
- package/agent/patterns/firebase-admin-sdk-v8-usage.md +950 -0
- package/agent/patterns/firestore-users-pattern-best-practices.md +347 -0
- package/agent/patterns/library-services.md +454 -0
- package/agent/patterns/testing-colocated.md +316 -0
- package/agent/progress.yaml +395 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/task-1-initialize-project-structure.md +266 -0
- package/agent/tasks/task-2-install-dependencies.md +199 -0
- package/agent/tasks/task-3-setup-weaviate-client.md +330 -0
- package/agent/tasks/task-4-setup-firestore-client.md +362 -0
- package/agent/tasks/task-5-create-basic-mcp-server.md +114 -0
- package/agent/tasks/task-6-create-integration-tests.md +195 -0
- package/agent/tasks/task-7-finalize-milestone-1.md +363 -0
- package/agent/tasks/task-8-setup-utility-scripts.md +382 -0
- package/agent/tasks/task-9-create-server-factory.md +404 -0
- package/dist/config.d.ts +26 -0
- package/dist/constants/content-types.d.ts +60 -0
- package/dist/firestore/init.d.ts +14 -0
- package/dist/firestore/paths.d.ts +53 -0
- package/dist/firestore/paths.spec.d.ts +2 -0
- package/dist/server-factory.d.ts +40 -0
- package/dist/server-factory.js +1741 -0
- package/dist/server-factory.spec.d.ts +2 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +1690 -0
- package/dist/tools/create-memory.d.ts +94 -0
- package/dist/tools/delete-memory.d.ts +47 -0
- package/dist/tools/search-memory.d.ts +88 -0
- package/dist/types/memory.d.ts +183 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/weaviate/client.d.ts +39 -0
- package/dist/weaviate/client.spec.d.ts +2 -0
- package/dist/weaviate/schema.d.ts +29 -0
- package/esbuild.build.js +60 -0
- package/esbuild.watch.js +25 -0
- package/jest.config.js +31 -0
- package/jest.e2e.config.js +17 -0
- package/package.json +68 -0
- package/src/.gitkeep +0 -0
- package/src/config.ts +56 -0
- package/src/constants/content-types.ts +454 -0
- package/src/firestore/init.ts +68 -0
- package/src/firestore/paths.spec.ts +75 -0
- package/src/firestore/paths.ts +124 -0
- package/src/server-factory.spec.ts +60 -0
- package/src/server-factory.ts +215 -0
- package/src/server.ts +243 -0
- package/src/tools/create-memory.ts +198 -0
- package/src/tools/delete-memory.ts +126 -0
- package/src/tools/search-memory.ts +216 -0
- package/src/types/memory.ts +276 -0
- package/src/utils/logger.ts +42 -0
- package/src/weaviate/client.spec.ts +58 -0
- package/src/weaviate/client.ts +114 -0
- package/src/weaviate/schema.ts +288 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firestore collection path helpers
|
|
3
|
+
* Following the environment-based prefix + users subcollection pattern
|
|
4
|
+
*
|
|
5
|
+
* Pattern from agentbase.me:
|
|
6
|
+
* - Environment prefix: e0.remember-mcp (dev), remember-mcp (prod)
|
|
7
|
+
* - User-scoped data: {BASE}.users/{user_id}/* (per agent/patterns/firestore-users-pattern-best-practices.md)
|
|
8
|
+
* - Shared data: {BASE}.templates/default, {BASE}.user-permissions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const APP_NAME = 'remember-mcp';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the database collection prefix based on environment
|
|
15
|
+
* - Development: Uses ENVIRONMENT env var or DB_PREFIX, defaults to 'e0.remember-mcp'
|
|
16
|
+
* - Production: Uses base 'remember-mcp'
|
|
17
|
+
*
|
|
18
|
+
* This allows developers to use their own database entries as a sandbox per dev or branch.
|
|
19
|
+
*/
|
|
20
|
+
function getBasePrefix(): string {
|
|
21
|
+
// Check for explicit environment variable (e0, e1, e2, etc.)
|
|
22
|
+
const environment = process.env.ENVIRONMENT;
|
|
23
|
+
if (environment && environment !== 'production' && environment !== 'prod') {
|
|
24
|
+
return `${environment}.${APP_NAME}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check if we're in development mode
|
|
28
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
29
|
+
|
|
30
|
+
if (isDevelopment) {
|
|
31
|
+
// Check for custom DB_PREFIX env var first
|
|
32
|
+
const customPrefix = process.env.DB_PREFIX;
|
|
33
|
+
if (customPrefix) {
|
|
34
|
+
return customPrefix;
|
|
35
|
+
}
|
|
36
|
+
// Default to e0.{APP_NAME} in development
|
|
37
|
+
return `e0.${APP_NAME}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Production uses the base APP_NAME
|
|
41
|
+
return APP_NAME;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const BASE = getBasePrefix();
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// USER-SCOPED COLLECTIONS (under users/{user_id}/)
|
|
48
|
+
// Per agent/patterns/firestore-users-pattern-best-practices.md
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get path to user preferences document
|
|
53
|
+
* Pattern: {BASE}.users/{user_id}/preferences
|
|
54
|
+
*/
|
|
55
|
+
export function getUserPreferencesPath(userId: string): string {
|
|
56
|
+
return `${BASE}.users/${userId}/preferences`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get path to user's templates collection
|
|
61
|
+
* Pattern: {BASE}.users/{user_id}/templates
|
|
62
|
+
*/
|
|
63
|
+
export function getUserTemplatesPath(userId: string): string {
|
|
64
|
+
return `${BASE}.users/${userId}/templates`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get path to user's access logs collection
|
|
69
|
+
* Pattern: {BASE}.users/{user_id}/access-logs
|
|
70
|
+
*/
|
|
71
|
+
export function getUserAccessLogsPath(userId: string): string {
|
|
72
|
+
return `${BASE}.users/${userId}/access-logs`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get path to user's trust relationships collection
|
|
77
|
+
* Pattern: {BASE}.users/{user_id}/trust-relationships
|
|
78
|
+
*/
|
|
79
|
+
export function getUserTrustRelationshipsPath(userId: string): string {
|
|
80
|
+
return `${BASE}.users/${userId}/trust-relationships`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// CROSS-USER COLLECTIONS (outside users/)
|
|
85
|
+
// These involve multiple users, so can't be under users/{user_id}/
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get path to user's allowed accessors collection (permissions)
|
|
90
|
+
* Pattern: {BASE}.user-permissions/{owner_user_id}/allowed-accessors
|
|
91
|
+
*
|
|
92
|
+
* Note: Outside users/ because it involves two users (owner + accessor)
|
|
93
|
+
*/
|
|
94
|
+
export function getUserPermissionsPath(ownerUserId: string): string {
|
|
95
|
+
return `${BASE}.user-permissions/${ownerUserId}/allowed-accessors`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get path to specific permission document
|
|
100
|
+
* Pattern: {BASE}.user-permissions/{owner_user_id}/allowed-accessors/{accessor_user_id}
|
|
101
|
+
*/
|
|
102
|
+
export function getUserPermissionPath(ownerUserId: string, accessorUserId: string): string {
|
|
103
|
+
return `${BASE}.user-permissions/${ownerUserId}/allowed-accessors/${accessorUserId}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// SHARED/GLOBAL COLLECTIONS
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get path to default templates collection
|
|
112
|
+
* Pattern: {BASE}.templates/default
|
|
113
|
+
*/
|
|
114
|
+
export function getDefaultTemplatesPath(): string {
|
|
115
|
+
return `${BASE}.templates/default`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get path to specific default template
|
|
120
|
+
* Pattern: {BASE}.templates/default/{template_id}
|
|
121
|
+
*/
|
|
122
|
+
export function getDefaultTemplatePath(templateId: string): string {
|
|
123
|
+
return `${BASE}.templates/default/${templateId}`;
|
|
124
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { createServer } from './server-factory.js';
|
|
3
|
+
|
|
4
|
+
describe('Server Factory', () => {
|
|
5
|
+
describe('Parameter Validation', () => {
|
|
6
|
+
it('should create server instance with valid parameters', () => {
|
|
7
|
+
const server = createServer('test-token', 'user123');
|
|
8
|
+
expect(server).toBeDefined();
|
|
9
|
+
expect(server).toHaveProperty('setRequestHandler');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should require accessToken', () => {
|
|
13
|
+
expect(() => createServer('', 'user123')).toThrow('accessToken is required');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should require userId', () => {
|
|
17
|
+
expect(() => createServer('token', '')).toThrow('userId is required');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should accept custom options', () => {
|
|
21
|
+
const server = createServer('token', 'user123', {
|
|
22
|
+
name: 'custom-name',
|
|
23
|
+
version: '2.0.0',
|
|
24
|
+
});
|
|
25
|
+
expect(server).toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('Server Isolation', () => {
|
|
30
|
+
it('should create separate instances for different users', () => {
|
|
31
|
+
const server1 = createServer('token1', 'user1');
|
|
32
|
+
const server2 = createServer('token2', 'user2');
|
|
33
|
+
|
|
34
|
+
expect(server1).not.toBe(server2);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should scope operations to userId', () => {
|
|
38
|
+
const server = createServer('token', 'user123');
|
|
39
|
+
expect(server).toBeDefined();
|
|
40
|
+
// Note: Actual scoping tested in integration tests
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('Options', () => {
|
|
45
|
+
it('should use default name if not provided', () => {
|
|
46
|
+
const server = createServer('token', 'user123');
|
|
47
|
+
expect(server).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should use custom name if provided', () => {
|
|
51
|
+
const server = createServer('token', 'user123', { name: 'custom' });
|
|
52
|
+
expect(server).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should use custom version if provided', () => {
|
|
56
|
+
const server = createServer('token', 'user123', { version: '2.0.0' });
|
|
57
|
+
expect(server).toBeDefined();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server factory for mcp-auth compatibility
|
|
3
|
+
* Creates isolated server instances per user/tenant
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import {
|
|
8
|
+
CallToolRequestSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
ErrorCode,
|
|
11
|
+
McpError,
|
|
12
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { logger } from './utils/logger.js';
|
|
14
|
+
|
|
15
|
+
// Import memory tools
|
|
16
|
+
import { createMemoryTool, handleCreateMemory } from './tools/create-memory.js';
|
|
17
|
+
import { searchMemoryTool, handleSearchMemory } from './tools/search-memory.js';
|
|
18
|
+
import { deleteMemoryTool, handleDeleteMemory } from './tools/delete-memory.js';
|
|
19
|
+
|
|
20
|
+
export interface ServerOptions {
|
|
21
|
+
name?: string;
|
|
22
|
+
version?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a server instance for a specific user/tenant
|
|
27
|
+
*
|
|
28
|
+
* This factory function is compatible with mcp-auth wrapping pattern.
|
|
29
|
+
* It creates isolated server instances with no shared state.
|
|
30
|
+
*
|
|
31
|
+
* @param accessToken - User's access token (reserved for future external APIs)
|
|
32
|
+
* @param userId - User identifier for scoping operations
|
|
33
|
+
* @param options - Optional server configuration
|
|
34
|
+
* @returns Configured MCP Server instance (not connected to transport)
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* // Direct usage
|
|
39
|
+
* const server = createServer('token', 'user123');
|
|
40
|
+
* const transport = new StdioServerTransport();
|
|
41
|
+
* await server.connect(transport);
|
|
42
|
+
*
|
|
43
|
+
* // With mcp-auth
|
|
44
|
+
* import { wrapServer } from '@prmichaelsen/mcp-auth';
|
|
45
|
+
* const wrapped = wrapServer({
|
|
46
|
+
* serverFactory: createServer,
|
|
47
|
+
* authProvider: new JWTAuthProvider({ ... }),
|
|
48
|
+
* tokenResolver: new APITokenResolver({ ... }),
|
|
49
|
+
* resourceType: 'remember',
|
|
50
|
+
* transport: { type: 'sse', port: 3000 }
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createServer(
|
|
55
|
+
accessToken: string,
|
|
56
|
+
userId: string,
|
|
57
|
+
options: ServerOptions = {}
|
|
58
|
+
): Server {
|
|
59
|
+
if (!accessToken) {
|
|
60
|
+
throw new Error('accessToken is required');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!userId) {
|
|
64
|
+
throw new Error('userId is required');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
logger.debug('Creating server instance', { userId });
|
|
68
|
+
|
|
69
|
+
// Create MCP server
|
|
70
|
+
const server = new Server(
|
|
71
|
+
{
|
|
72
|
+
name: options.name || 'remember-mcp',
|
|
73
|
+
version: options.version || '0.1.0',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
capabilities: {
|
|
77
|
+
tools: {},
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Register handlers with userId scope
|
|
83
|
+
registerHandlers(server, userId, accessToken);
|
|
84
|
+
|
|
85
|
+
return server;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Register MCP handlers scoped to userId
|
|
90
|
+
*/
|
|
91
|
+
function registerHandlers(server: Server, userId: string, accessToken: string): void {
|
|
92
|
+
// List available tools
|
|
93
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
94
|
+
return {
|
|
95
|
+
tools: [
|
|
96
|
+
{
|
|
97
|
+
name: 'health_check',
|
|
98
|
+
description: 'Check server health and database connections',
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
createMemoryTool,
|
|
105
|
+
searchMemoryTool,
|
|
106
|
+
deleteMemoryTool,
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Handle tool calls
|
|
112
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
113
|
+
const { name, arguments: args } = request.params;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
let result: string;
|
|
117
|
+
|
|
118
|
+
switch (name) {
|
|
119
|
+
case 'health_check':
|
|
120
|
+
result = await handleHealthCheck(userId);
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case 'remember_create_memory':
|
|
124
|
+
result = await handleCreateMemory(args as any, userId);
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case 'remember_search_memory':
|
|
128
|
+
result = await handleSearchMemory(args as any, userId);
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case 'remember_delete_memory':
|
|
132
|
+
result = await handleDeleteMemory(args as any, userId);
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
default:
|
|
136
|
+
throw new McpError(
|
|
137
|
+
ErrorCode.MethodNotFound,
|
|
138
|
+
`Unknown tool: ${name}`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: 'text',
|
|
146
|
+
text: result,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof McpError) {
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
logger.error(`Tool execution failed for ${name}:`, error);
|
|
156
|
+
throw new McpError(
|
|
157
|
+
ErrorCode.InternalError,
|
|
158
|
+
`Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Health check handler (scoped to userId)
|
|
166
|
+
*/
|
|
167
|
+
async function handleHealthCheck(userId: string): Promise<string> {
|
|
168
|
+
const health = {
|
|
169
|
+
status: 'healthy',
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
userId: userId,
|
|
172
|
+
server: {
|
|
173
|
+
name: 'remember-mcp',
|
|
174
|
+
version: '0.1.0',
|
|
175
|
+
},
|
|
176
|
+
databases: {
|
|
177
|
+
weaviate: {
|
|
178
|
+
connected: false,
|
|
179
|
+
userCollection: `Memory_${userId}`,
|
|
180
|
+
},
|
|
181
|
+
firestore: {
|
|
182
|
+
connected: false,
|
|
183
|
+
userPath: `users/${userId}`,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Test Weaviate connection
|
|
190
|
+
const { getWeaviateClient } = await import('./weaviate/client.js');
|
|
191
|
+
const weaviateClient = getWeaviateClient();
|
|
192
|
+
health.databases.weaviate.connected = await weaviateClient.isReady();
|
|
193
|
+
} catch (error) {
|
|
194
|
+
logger.error('Weaviate health check failed:', error);
|
|
195
|
+
health.databases.weaviate.connected = false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
// Test Firestore connection
|
|
200
|
+
const { testFirestoreConnection } = await import('./firestore/init.js');
|
|
201
|
+
health.databases.firestore.connected = await testFirestoreConnection();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
logger.error('Firestore health check failed:', error);
|
|
204
|
+
health.databases.firestore.connected = false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Overall status
|
|
208
|
+
const allHealthy =
|
|
209
|
+
health.databases.weaviate.connected &&
|
|
210
|
+
health.databases.firestore.connected;
|
|
211
|
+
|
|
212
|
+
health.status = allHealthy ? 'healthy' : 'degraded';
|
|
213
|
+
|
|
214
|
+
return JSON.stringify(health, null, 2);
|
|
215
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
ErrorCode,
|
|
9
|
+
McpError,
|
|
10
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
+
import { config, validateConfig } from './config.js';
|
|
12
|
+
import { initWeaviateClient, testWeaviateConnection, getWeaviateClient } from './weaviate/client.js';
|
|
13
|
+
import { initFirestore, testFirestoreConnection } from './firestore/init.js';
|
|
14
|
+
import { logger } from './utils/logger.js';
|
|
15
|
+
|
|
16
|
+
// Import memory tools
|
|
17
|
+
import { createMemoryTool, handleCreateMemory } from './tools/create-memory.js';
|
|
18
|
+
import { searchMemoryTool, handleSearchMemory } from './tools/search-memory.js';
|
|
19
|
+
import { deleteMemoryTool, handleDeleteMemory } from './tools/delete-memory.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialize remember-mcp server
|
|
23
|
+
*/
|
|
24
|
+
async function initServer(): Promise<Server> {
|
|
25
|
+
logger.info('Initializing remember-mcp server...');
|
|
26
|
+
|
|
27
|
+
// Validate configuration
|
|
28
|
+
validateConfig();
|
|
29
|
+
|
|
30
|
+
// Initialize databases
|
|
31
|
+
logger.info('Connecting to databases...');
|
|
32
|
+
await initWeaviateClient();
|
|
33
|
+
initFirestore();
|
|
34
|
+
|
|
35
|
+
// Test connections
|
|
36
|
+
const weaviateOk = await testWeaviateConnection();
|
|
37
|
+
const firestoreOk = await testFirestoreConnection();
|
|
38
|
+
|
|
39
|
+
if (!weaviateOk || !firestoreOk) {
|
|
40
|
+
throw new Error('Database connection failed');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
logger.info('Database connections established');
|
|
44
|
+
|
|
45
|
+
// Create MCP server
|
|
46
|
+
const server = new Server(
|
|
47
|
+
{
|
|
48
|
+
name: 'remember-mcp',
|
|
49
|
+
version: '0.1.0',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
capabilities: {
|
|
53
|
+
tools: {},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Register handlers
|
|
59
|
+
registerHandlers(server);
|
|
60
|
+
|
|
61
|
+
logger.info('Server initialized successfully');
|
|
62
|
+
return server;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register MCP handlers
|
|
67
|
+
*/
|
|
68
|
+
function registerHandlers(server: Server): void {
|
|
69
|
+
// List available tools
|
|
70
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
71
|
+
return {
|
|
72
|
+
tools: [
|
|
73
|
+
{
|
|
74
|
+
name: 'health_check',
|
|
75
|
+
description: 'Check server health and database connections',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
createMemoryTool,
|
|
82
|
+
searchMemoryTool,
|
|
83
|
+
deleteMemoryTool,
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Handle tool calls
|
|
89
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
90
|
+
const { name, arguments: args } = request.params;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
let result: string;
|
|
94
|
+
|
|
95
|
+
// Extract userId from args or use default for now
|
|
96
|
+
// TODO: Get userId from authentication context in M6
|
|
97
|
+
const userId = (args as any).user_id || 'default_user';
|
|
98
|
+
|
|
99
|
+
switch (name) {
|
|
100
|
+
case 'health_check':
|
|
101
|
+
result = await handleHealthCheck();
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case 'remember_create_memory':
|
|
105
|
+
result = await handleCreateMemory(args as any, userId);
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
case 'remember_search_memory':
|
|
109
|
+
result = await handleSearchMemory(args as any, userId);
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case 'remember_delete_memory':
|
|
113
|
+
result = await handleDeleteMemory(args as any, userId);
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
default:
|
|
117
|
+
throw new McpError(
|
|
118
|
+
ErrorCode.MethodNotFound,
|
|
119
|
+
`Unknown tool: ${name}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
{
|
|
126
|
+
type: 'text',
|
|
127
|
+
text: result,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error instanceof McpError) {
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
logger.error(`Tool execution failed for ${name}:`, error);
|
|
137
|
+
throw new McpError(
|
|
138
|
+
ErrorCode.InternalError,
|
|
139
|
+
`Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Health check handler
|
|
147
|
+
*/
|
|
148
|
+
async function handleHealthCheck(): Promise<string> {
|
|
149
|
+
const health = {
|
|
150
|
+
status: 'healthy',
|
|
151
|
+
timestamp: new Date().toISOString(),
|
|
152
|
+
server: {
|
|
153
|
+
name: 'remember-mcp',
|
|
154
|
+
version: '0.1.0',
|
|
155
|
+
},
|
|
156
|
+
databases: {
|
|
157
|
+
weaviate: {
|
|
158
|
+
connected: false,
|
|
159
|
+
url: config.weaviate.url,
|
|
160
|
+
},
|
|
161
|
+
firestore: {
|
|
162
|
+
connected: false,
|
|
163
|
+
projectId: config.firebase.projectId,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Test Weaviate connection
|
|
170
|
+
const weaviateClient = getWeaviateClient();
|
|
171
|
+
health.databases.weaviate.connected = await weaviateClient.isReady();
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logger.error('Weaviate health check failed:', error);
|
|
174
|
+
health.databases.weaviate.connected = false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
// Test Firestore connection
|
|
179
|
+
health.databases.firestore.connected = await testFirestoreConnection();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
logger.error('Firestore health check failed:', error);
|
|
182
|
+
health.databases.firestore.connected = false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Overall status
|
|
186
|
+
const allHealthy =
|
|
187
|
+
health.databases.weaviate.connected &&
|
|
188
|
+
health.databases.firestore.connected;
|
|
189
|
+
|
|
190
|
+
health.status = allHealthy ? 'healthy' : 'degraded';
|
|
191
|
+
|
|
192
|
+
return JSON.stringify(health, null, 2);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Main server startup
|
|
197
|
+
*/
|
|
198
|
+
async function main(): Promise<void> {
|
|
199
|
+
try {
|
|
200
|
+
logger.info('Starting remember-mcp server...');
|
|
201
|
+
|
|
202
|
+
// Initialize server
|
|
203
|
+
const server = await initServer();
|
|
204
|
+
|
|
205
|
+
// Start with stdio transport
|
|
206
|
+
const transport = new StdioServerTransport();
|
|
207
|
+
await server.connect(transport);
|
|
208
|
+
|
|
209
|
+
logger.info('Server running on stdio transport');
|
|
210
|
+
|
|
211
|
+
// Note: When using stdio transport, avoid console.log as it interferes with JSON-RPC
|
|
212
|
+
// The logger is configured to handle this appropriately
|
|
213
|
+
} catch (error) {
|
|
214
|
+
logger.error('Server startup failed:', error);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Graceful shutdown handler
|
|
221
|
+
*/
|
|
222
|
+
function setupShutdownHandlers(server: Server): void {
|
|
223
|
+
const shutdown = async (signal: string) => {
|
|
224
|
+
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
225
|
+
try {
|
|
226
|
+
await server.close();
|
|
227
|
+
logger.info('Server closed successfully');
|
|
228
|
+
process.exit(0);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
logger.error('Error during shutdown:', error);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
236
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Start the server
|
|
240
|
+
main().catch((error) => {
|
|
241
|
+
logger.error('Fatal error:', error);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
});
|