purmemo-mcp 3.2.0 → 3.2.2

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/README.md CHANGED
@@ -50,13 +50,15 @@ This repository contains the **open-source MCP protocol wrapper** that connects
50
50
 
51
51
  ### 1. Get Your API Key
52
52
 
53
- Sign up at [app.purmemo.ai](https://app.purmemo.ai) to get your free API key.
53
+ 1. Sign up at [purmemo.ai/register](https://www.purmemo.ai/register)
54
+ 2. Sign in and go to [purmemo.ai/settings](https://www.purmemo.ai/settings)
55
+ 3. Click "API Keys" tab → "Create New Key"
56
+ 4. Name it (e.g., "Claude Desktop") → "Create Key"
57
+ 5. Copy the key immediately (won't be shown again)
54
58
 
55
- ### 2. Install the MCP Server
59
+ ### 2. No Installation Needed!
56
60
 
57
- ```bash
58
- npm install -g purmemo-mcp
59
- ```
61
+ Claude Desktop will automatically download the MCP server using `npx` (see configuration below).
60
62
 
61
63
  ### 3. Configure Claude Desktop
62
64
 
@@ -232,9 +234,8 @@ The following remain proprietary:
232
234
  ## 🆘 Support
233
235
 
234
236
  - 📧 Email: support@purmemo.ai
235
- - 💬 Discord: [discord.gg/purmemo](https://discord.gg/purmemo)
236
237
  - 🐛 Issues: [GitHub Issues](https://github.com/coladapo/purmemo-mcp/issues)
237
- - 🌐 Status: [status.purmemo.ai](https://status.purmemo.ai)
238
+ - 🌐 Website: [purmemo.ai](https://purmemo.ai)
238
239
 
239
240
  ## 🗺️ Roadmap
240
241
 
package/package.json CHANGED
@@ -1,21 +1,26 @@
1
1
  {
2
2
  "name": "purmemo-mcp",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "Official Model Context Protocol (MCP) server for Purmemo - Your AI-powered second brain with 5 complete tools",
5
- "main": "src/server-final.js",
5
+ "main": "src/server.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "purmemo-mcp": "./src/server-final.js",
8
+ "purmemo-mcp": "./src/server.js",
9
9
  "purmemo-mcp-setup": "./src/setup.js"
10
10
  },
11
11
  "files": [
12
- "src/",
13
- "bin/",
12
+ "src/server.js",
13
+ "src/setup.js",
14
+ "src/index.js",
15
+ "src/auth/",
16
+ "src/diagnose.js",
17
+ "src/diagnose-production.js",
18
+ "src/setup-emergency.js",
14
19
  "README.md",
15
20
  "LICENSE"
16
21
  ],
17
22
  "scripts": {
18
- "start": "node src/server-final.js",
23
+ "start": "node src/server.js",
19
24
  "setup": "node src/setup.js setup",
20
25
  "status": "node src/setup.js status",
21
26
  "logout": "node src/setup.js logout",
package/src/server.js CHANGED
@@ -1,28 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * pūrmemo MCP Server - Secure Thin Client v1.1.2
4
- * All processing happens on pūrmemo servers
3
+ * Final Working Purmemo MCP Server v3.1.0
4
+ * Fixed search endpoint to use correct HTTP method
5
5
  */
6
6
 
7
7
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
8
8
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
- import {
10
- CallToolRequestSchema,
11
- ListToolsRequestSchema,
12
- } from '@modelcontextprotocol/sdk/types.js';
13
- import fetch from 'node-fetch';
14
-
15
- // Check for required environment variables
16
- const API_KEY = process.env.PUO_MEMO_API_KEY;
17
- const API_URL = process.env.PUO_MEMO_API_URL || 'https://api.puo-memo.com';
18
-
19
- if (!API_KEY) {
20
- console.error('❌ PUO_MEMO_API_KEY environment variable is required');
21
- console.error('Get your API key at https://app.purmemo.ai');
22
- process.exit(1);
23
- }
9
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
10
+
11
+ // Configuration
12
+ const API_URL = process.env.PURMEMO_API_URL || 'https://api.purmemo.ai';
13
+ const USER_AGENT = 'purmemo-mcp/3.1.0';
14
+
15
+ // Store auth token in memory
16
+ let authToken = null;
17
+ let tokenExpiry = null;
24
18
 
25
- // Tool definitions - just schemas, no implementation
19
+ // Tool definitions
26
20
  const TOOLS = [
27
21
  {
28
22
  name: 'memory',
@@ -32,8 +26,7 @@ const TOOLS = [
32
26
  properties: {
33
27
  content: { type: 'string', description: 'What to remember' },
34
28
  title: { type: 'string', description: 'Optional: Title for the memory' },
35
- tags: { type: 'array', items: { type: 'string' }, description: 'Optional: Tags' },
36
- attachments: { type: 'array', items: { type: 'string' }, description: 'Optional: File paths or URLs' }
29
+ tags: { type: 'array', items: { type: 'string' }, description: 'Optional: Tags' }
37
30
  },
38
31
  required: ['content']
39
32
  }
@@ -45,23 +38,19 @@ const TOOLS = [
45
38
  type: 'object',
46
39
  properties: {
47
40
  query: { type: 'string', description: 'What to search for' },
48
- limit: { type: 'integer', description: 'How many results (default: 10)', default: 10 },
49
- search_type: { type: 'string', enum: ['keyword', 'semantic', 'hybrid'], default: 'hybrid' }
50
- }
41
+ limit: { type: 'integer', description: 'How many results (default: 10)', default: 10 }
42
+ },
43
+ required: ['query']
51
44
  }
52
45
  },
53
46
  {
54
47
  name: 'entities',
55
- description: '🧠 List entities or get entity graph',
48
+ description: '🏷️ Extract entities from memories',
56
49
  inputSchema: {
57
50
  type: 'object',
58
51
  properties: {
59
- entity_name: { type: 'string', description: 'Entity to get graph for' },
60
- entity_type: {
61
- type: 'string',
62
- enum: ['person', 'organization', 'location', 'event', 'project'],
63
- description: 'Filter by entity type'
64
- }
52
+ entity_name: { type: 'string', description: 'Optional: Specific entity to look up' },
53
+ entity_type: { type: 'string', description: 'Optional: Filter by entity type' }
65
54
  }
66
55
  }
67
56
  },
@@ -72,9 +61,10 @@ const TOOLS = [
72
61
  type: 'object',
73
62
  properties: {
74
63
  memory_id: { type: 'string', description: 'Memory ID to attach files to' },
75
- file_paths: { type: 'array', items: { type: 'string' }, description: 'File paths or URLs' }
64
+ files: { type: 'array', items: { type: 'string' }, description: 'File paths or URLs to attach' },
65
+ description: { type: 'string', description: 'Optional: Description of the attachments' }
76
66
  },
77
- required: ['memory_id', 'file_paths']
67
+ required: ['memory_id', 'files']
78
68
  }
79
69
  },
80
70
  {
@@ -83,154 +73,408 @@ const TOOLS = [
83
73
  inputSchema: {
84
74
  type: 'object',
85
75
  properties: {
86
- memory_id: { type: 'string', description: 'ID of the memory to correct' },
87
- correction: { type: 'string', description: 'The corrected content' },
88
- reason: { type: 'string', description: 'Optional: Reason for the correction' }
76
+ memory_id: { type: 'string', description: 'Memory ID to add correction to' },
77
+ correction: { type: 'string', description: 'The correction text or details' },
78
+ type: { type: 'string', enum: ['factual', 'spelling', 'update', 'clarification'], description: 'Type of correction', default: 'update' }
89
79
  },
90
80
  required: ['memory_id', 'correction']
91
81
  }
92
82
  }
93
83
  ];
94
84
 
95
- // Create MCP server
85
+ // Create server
96
86
  const server = new Server(
97
- {
98
- name: 'purmemo',
99
- vendor: 'pūrmemo',
100
- version: '1.1.2'
101
- },
102
- {
103
- capabilities: {
104
- tools: {}
87
+ { name: 'purmemo-mcp', version: '3.2.0' },
88
+ { capabilities: { tools: {} } }
89
+ );
90
+
91
+ // Authentication function supporting both API key and login
92
+ async function authenticate() {
93
+ // Check for API key first (more secure)
94
+ const apiKey = process.env.PURMEMO_API_KEY;
95
+ if (apiKey) {
96
+ // API keys don't expire, return directly
97
+ return apiKey;
98
+ }
99
+
100
+ // Fallback to token-based auth if no API key
101
+ if (authToken && tokenExpiry && Date.now() < tokenExpiry) {
102
+ return authToken;
103
+ }
104
+
105
+ // Get credentials from environment
106
+ const email = process.env.PURMEMO_EMAIL || process.env.PUO_MEMO_EMAIL || 'demo@puo-memo.com';
107
+ const password = process.env.PURMEMO_PASSWORD || process.env.PUO_MEMO_PASSWORD || 'demodemo123';
108
+
109
+ try {
110
+ const response = await fetch(`${API_URL}/api/auth/login`, {
111
+ method: 'POST',
112
+ headers: {
113
+ 'Content-Type': 'application/x-www-form-urlencoded',
114
+ 'User-Agent': USER_AGENT
115
+ },
116
+ body: new URLSearchParams({
117
+ username: email, // OAuth2 uses 'username' field for email
118
+ password: password,
119
+ grant_type: 'password'
120
+ })
121
+ });
122
+
123
+ if (response.ok) {
124
+ const data = await response.json();
125
+ authToken = data.access_token;
126
+ // Token expires in 1 hour, refresh 5 minutes early
127
+ tokenExpiry = Date.now() + (55 * 60 * 1000);
128
+ return authToken;
105
129
  }
130
+ } catch (error) {
131
+ // Silent failure for MCP compatibility
106
132
  }
107
- );
133
+
134
+ return null;
135
+ }
108
136
 
109
- // Handle tool listing
110
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
111
- tools: TOOLS
112
- }));
137
+ // Create auth message
138
+ function createAuthMessage(toolName) {
139
+ return {
140
+ content: [{
141
+ type: 'text',
142
+ text: `🔐 Authentication Required\n\n` +
143
+ `To use ${toolName}, please set up credentials:\n\n` +
144
+ `**Recommended (Secure)**: Use API Key\n` +
145
+ `"env": {\n` +
146
+ ` "PURMEMO_API_KEY": "your-api-key-here"\n` +
147
+ `}\n\n` +
148
+ `Get your API key at: https://app.purmemo.ai/settings/api-keys\n\n` +
149
+ `**Alternative**: Use email/password\n` +
150
+ `"env": {\n` +
151
+ ` "PURMEMO_EMAIL": "your-email@example.com",\n` +
152
+ ` "PURMEMO_PASSWORD": "your-password"\n` +
153
+ `}\n\n` +
154
+ `Then restart Claude Desktop.`
155
+ }]
156
+ };
157
+ }
113
158
 
114
- // Handle tool execution - forward all to API
115
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
116
- const { name, arguments: args } = request.params;
159
+ // API call helper
160
+ async function makeApiCall(endpoint, options = {}) {
161
+ const token = await authenticate();
162
+
163
+ if (!token) {
164
+ throw new Error('NO_AUTH');
165
+ }
166
+
167
+ const defaultHeaders = {
168
+ 'Authorization': `Bearer ${token}`,
169
+ 'User-Agent': USER_AGENT
170
+ };
117
171
 
172
+ // Only add Content-Type for POST/PUT requests with body
173
+ if (options.body) {
174
+ defaultHeaders['Content-Type'] = 'application/json';
175
+ }
176
+
177
+ const response = await fetch(`${API_URL}${endpoint}`, {
178
+ ...options,
179
+ headers: {
180
+ ...defaultHeaders,
181
+ ...options.headers
182
+ }
183
+ });
184
+
185
+ if (!response.ok) {
186
+ const errorText = await response.text();
187
+ throw new Error(`API Error ${response.status}: ${errorText}`);
188
+ }
189
+
190
+ return await response.json();
191
+ }
192
+
193
+ // Tool handlers
194
+ async function handleMemory(args) {
118
195
  try {
119
- let response;
196
+ const data = await makeApiCall('/api/v5/memories/', {
197
+ method: 'POST',
198
+ body: JSON.stringify({
199
+ content: args.content,
200
+ title: args.title,
201
+ tags: args.tags || []
202
+ })
203
+ });
204
+
205
+ return {
206
+ content: [{
207
+ type: 'text',
208
+ text: `✅ Memory saved successfully!\n\n` +
209
+ `📝 Content: ${args.content}\n` +
210
+ `🔗 ID: ${data.id || data.memory_id || 'Unknown'}\n` +
211
+ (args.title ? `📋 Title: ${args.title}\n` : '') +
212
+ (args.tags?.length ? `🏷️ Tags: ${args.tags.join(', ')}\n` : '')
213
+ }]
214
+ };
215
+ } catch (error) {
216
+ if (error.message === 'NO_AUTH') {
217
+ return createAuthMessage('memory');
218
+ }
219
+ return {
220
+ content: [{
221
+ type: 'text',
222
+ text: `❌ Error saving memory: ${error.message}`
223
+ }]
224
+ };
225
+ }
226
+ }
227
+
228
+ async function handleRecall(args) {
229
+ try {
230
+ // FIXED: Use GET with query parameter instead of POST to /search
231
+ const params = new URLSearchParams({
232
+ query: args.query,
233
+ page_size: String(args.limit || 10)
234
+ });
120
235
 
121
- switch (name) {
122
- case 'memory':
123
- // POST request for creating memory
124
- response = await fetch(`${API_URL}/api/v5/memories`, {
125
- method: 'POST',
126
- headers: {
127
- 'Authorization': `Bearer ${API_KEY}`,
128
- 'Content-Type': 'application/json',
129
- 'User-Agent': 'purmemo-mcp/1.1.2'
130
- },
131
- body: JSON.stringify(args)
132
- });
133
- break;
134
-
135
- case 'recall':
136
- // GET request for search
137
- const searchParams = new URLSearchParams({
138
- q: args.query || '',
139
- limit: args.limit || 10,
140
- search_type: args.search_type || 'hybrid'
141
- });
142
- response = await fetch(`${API_URL}/api/v5/memories/search?${searchParams}`, {
143
- headers: {
144
- 'Authorization': `Bearer ${API_KEY}`,
145
- 'User-Agent': 'purmemo-mcp/1.1.2'
146
- }
147
- });
148
- break;
149
-
150
- case 'entities':
151
- // GET request for entities
152
- const entityParams = new URLSearchParams();
153
- if (args.entity_name) entityParams.append('name', args.entity_name);
154
- if (args.entity_type) entityParams.append('type', args.entity_type);
155
-
156
- response = await fetch(`${API_URL}/api/v5/entities?${entityParams}`, {
157
- headers: {
158
- 'Authorization': `Bearer ${API_KEY}`,
159
- 'User-Agent': 'purmemo-mcp/1.1.2'
160
- }
161
- });
162
- break;
163
-
164
- case 'attach':
165
- // POST request for attachments
166
- response = await fetch(`${API_URL}/api/v5/memories/${args.memory_id}/attachments`, {
167
- method: 'POST',
168
- headers: {
169
- 'Authorization': `Bearer ${API_KEY}`,
170
- 'Content-Type': 'application/json',
171
- 'User-Agent': 'purmemo-mcp/1.1.2'
172
- },
173
- body: JSON.stringify({ file_paths: args.file_paths })
174
- });
175
- break;
176
-
177
- case 'correction':
178
- // POST request for corrections
179
- response = await fetch(`${API_URL}/api/v5/memories/${args.memory_id}/corrections`, {
180
- method: 'POST',
181
- headers: {
182
- 'Authorization': `Bearer ${API_KEY}`,
183
- 'Content-Type': 'application/json',
184
- 'User-Agent': 'purmemo-mcp/1.1.2'
185
- },
186
- body: JSON.stringify({
187
- correction: args.correction,
188
- reason: args.reason
189
- })
190
- });
191
- break;
192
-
193
- default:
194
- throw new Error(`Unknown tool: ${name}`);
236
+ const data = await makeApiCall(`/api/v5/memories/?${params}`, {
237
+ method: 'GET' // Use GET not POST
238
+ });
239
+
240
+ // Handle both direct array response and paginated response
241
+ const memories = data.results || data.memories || data;
242
+
243
+ if (!memories || (Array.isArray(memories) && memories.length === 0)) {
244
+ return {
245
+ content: [{
246
+ type: 'text',
247
+ text: `🔍 No memories found for "${args.query}"`
248
+ }]
249
+ };
195
250
  }
251
+
252
+ const memoryList = Array.isArray(memories) ? memories : [memories];
253
+ let resultText = `🔍 Found ${memoryList.length} memories for "${args.query}"\n\n`;
196
254
 
197
- if (!response.ok) {
198
- const error = await response.text();
199
- throw new Error(`API error: ${error}`);
255
+ memoryList.forEach((memory, index) => {
256
+ resultText += `${index + 1}. **${memory.title || 'Untitled'}**\n`;
257
+ resultText += ` 📝 ${memory.content.substring(0, 150)}${memory.content.length > 150 ? '...' : ''}\n`;
258
+ if (memory.tags?.length) {
259
+ resultText += ` 🏷️ ${memory.tags.join(', ')}\n`;
260
+ }
261
+ if (memory.created_at) {
262
+ resultText += ` 📅 ${new Date(memory.created_at).toLocaleDateString()}\n`;
263
+ }
264
+ resultText += '\n';
265
+ });
266
+
267
+ return {
268
+ content: [{ type: 'text', text: resultText }]
269
+ };
270
+ } catch (error) {
271
+ if (error.message === 'NO_AUTH') {
272
+ return createAuthMessage('recall');
200
273
  }
274
+ return {
275
+ content: [{
276
+ type: 'text',
277
+ text: `❌ Error searching memories: ${error.message}`
278
+ }]
279
+ };
280
+ }
281
+ }
282
+
283
+ async function handleEntities(args) {
284
+ try {
285
+ const params = new URLSearchParams();
286
+ if (args.entity_name) params.set('name', args.entity_name);
287
+ if (args.entity_type) params.set('type', args.entity_type);
201
288
 
202
- const result = await response.json();
289
+ const data = await makeApiCall(`/api/v5/entities?${params}`, {
290
+ method: 'GET'
291
+ });
292
+
293
+ // Check for backend error
294
+ if (data.error) {
295
+ // Handle known error about missing entities table
296
+ if (data.error.includes('entities table')) {
297
+ return {
298
+ content: [{
299
+ type: 'text',
300
+ text: `🏷️ Entity extraction is being set up\n\n` +
301
+ `The entity extraction feature is currently being configured.\n` +
302
+ `This feature will automatically extract:\n\n` +
303
+ `• People: names mentioned in memories\n` +
304
+ `• Places: locations referenced\n` +
305
+ `• Organizations: companies, teams\n` +
306
+ `• Technologies: tools, frameworks\n` +
307
+ `• Concepts: ideas, topics\n\n` +
308
+ `Please check back later once setup is complete.`
309
+ }]
310
+ };
311
+ }
312
+ // Other errors
313
+ throw new Error(data.error);
314
+ }
315
+
316
+ // Handle empty entities
317
+ if (!data.entities || data.entities.length === 0) {
318
+ return {
319
+ content: [{
320
+ type: 'text',
321
+ text: `🏷️ No entities found\n\n` +
322
+ `Entities are extracted from your memories. ` +
323
+ `Save some memories first, and entities will be automatically extracted.\n\n` +
324
+ `Examples of entities:\n` +
325
+ `• People: names mentioned in memories\n` +
326
+ `• Places: locations referenced\n` +
327
+ `• Organizations: companies, teams\n` +
328
+ `• Concepts: ideas, technologies`
329
+ }]
330
+ };
331
+ }
332
+
333
+ let resultText = `🏷️ Found ${data.entities.length} entities\n\n`;
203
334
 
335
+ data.entities.forEach(entity => {
336
+ // Handle both camelCase and snake_case field names
337
+ const name = entity.name || entity.entity_name;
338
+ const type = entity.entityType || entity.entity_type || entity.type;
339
+ const occurrences = entity.metrics?.occurrences || entity.occurrence_count || entity.frequency;
340
+
341
+ resultText += `**${name}** (${type})\n`;
342
+ if (occurrences) {
343
+ resultText += ` 📊 Mentioned ${occurrences} times\n`;
344
+ }
345
+ if (entity.metrics?.confidence) {
346
+ resultText += ` 🎯 Confidence: ${(entity.metrics.confidence * 100).toFixed(0)}%\n`;
347
+ }
348
+ resultText += '\n';
349
+ });
350
+
351
+ if (data.summary) {
352
+ resultText += `\n📈 Summary:\n`;
353
+ resultText += ` Total entities: ${data.summary.totalEntities}\n`;
354
+ resultText += ` Types found: ${data.summary.typesFound}\n`;
355
+ if (data.summary.averageConfidence) {
356
+ resultText += ` Average confidence: ${(data.summary.averageConfidence * 100).toFixed(0)}%\n`;
357
+ }
358
+ }
359
+
360
+ return {
361
+ content: [{ type: 'text', text: resultText }]
362
+ };
363
+ } catch (error) {
364
+ if (error.message === 'NO_AUTH') {
365
+ return createAuthMessage('entities');
366
+ }
204
367
  return {
205
368
  content: [{
206
369
  type: 'text',
207
- text: JSON.stringify(result, null, 2)
370
+ text: `❌ Error fetching entities: ${error.message}`
371
+ }]
372
+ };
373
+ }
374
+ }
375
+
376
+ async function handleAttach(args) {
377
+ try {
378
+ const data = await makeApiCall(`/api/v5/memories/${args.memory_id}/attachments`, {
379
+ method: 'POST',
380
+ body: JSON.stringify({
381
+ files: args.files,
382
+ description: args.description || ""
383
+ })
384
+ });
385
+
386
+ return {
387
+ content: [{
388
+ type: 'text',
389
+ text: `✅ Files attached successfully!\n\n` +
390
+ `📎 Memory ID: ${args.memory_id}\n` +
391
+ `📁 Files: ${args.files.join(', ')}\n` +
392
+ (args.description ? `📝 Description: ${args.description}\n` : '') +
393
+ `🔗 Attachment ID: ${data.id || data.attachment_id || 'Unknown'}`
208
394
  }]
209
395
  };
210
-
211
396
  } catch (error) {
212
- console.error('Tool execution error:', error);
397
+ if (error.message === 'NO_AUTH') {
398
+ return createAuthMessage('attach');
399
+ }
213
400
  return {
214
401
  content: [{
215
402
  type: 'text',
216
- text: JSON.stringify({ error: error.message }, null, 2)
403
+ text: `❌ Error attaching files: ${error.message}`
217
404
  }]
218
405
  };
219
406
  }
220
- });
407
+ }
221
408
 
222
- // Start the server
223
- async function main() {
224
- console.error('🚀 pūrmemo MCP Server v1.1.2 - Hybrid Open-Core Model');
225
- console.error('📡 Connected to:', API_URL);
226
- console.error('🔐 OAuth 2.1 + API key authentication');
227
- console.error('💡 94% accuracy with <50ms retrieval');
228
-
229
- const transport = new StdioServerTransport();
230
- await server.connect(transport);
409
+ async function handleCorrection(args) {
410
+ try {
411
+ const data = await makeApiCall(`/api/v5/memories/${args.memory_id}/corrections`, {
412
+ method: 'POST',
413
+ body: JSON.stringify({
414
+ correction: args.correction,
415
+ type: args.type || 'update'
416
+ })
417
+ });
418
+
419
+ return {
420
+ content: [{
421
+ type: 'text',
422
+ text: `✅ Correction added successfully!\n\n` +
423
+ `📝 Memory ID: ${args.memory_id}\n` +
424
+ `✏️ Correction: ${args.correction}\n` +
425
+ `🏷️ Type: ${args.type || 'update'}\n` +
426
+ `🔗 Correction ID: ${data.id || data.correction_id || 'Unknown'}`
427
+ }]
428
+ };
429
+ } catch (error) {
430
+ if (error.message === 'NO_AUTH') {
431
+ return createAuthMessage('correction');
432
+ }
433
+ return {
434
+ content: [{
435
+ type: 'text',
436
+ text: `❌ Error adding correction: ${error.message}`
437
+ }]
438
+ };
439
+ }
231
440
  }
232
441
 
233
- main().catch((error) => {
234
- console.error('Server error:', error);
235
- process.exit(1);
236
- });
442
+ // Request handlers
443
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
444
+
445
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
446
+ const { name, arguments: args } = request.params;
447
+
448
+ try {
449
+ switch (name) {
450
+ case 'memory':
451
+ return await handleMemory(args);
452
+ case 'recall':
453
+ return await handleRecall(args);
454
+ case 'entities':
455
+ return await handleEntities(args);
456
+ case 'attach':
457
+ return await handleAttach(args);
458
+ case 'correction':
459
+ return await handleCorrection(args);
460
+ default:
461
+ return {
462
+ content: [{
463
+ type: 'text',
464
+ text: `❌ Unknown tool: ${name}\n\nAvailable tools:\n• memory - Save memories\n• recall - Search memories\n• entities - List entities\n• attach - Attach files\n• correction - Add corrections`
465
+ }]
466
+ };
467
+ }
468
+ } catch (error) {
469
+ return {
470
+ content: [{
471
+ type: 'text',
472
+ text: `❌ Unexpected error: ${error.message}`
473
+ }]
474
+ };
475
+ }
476
+ });
477
+
478
+ // Start server
479
+ const transport = new StdioServerTransport();
480
+ server.connect(transport).catch(() => process.exit(1));
package/bin/purmemo-mcp DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- require('../src/index.js');