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 +8 -7
- package/package.json +11 -6
- package/src/server.js +393 -149
- package/bin/purmemo-mcp +0 -2
- package/src/server-ai-enhanced.js +0 -509
- package/src/server-complete.js +0 -625
- package/src/server-final.js +0 -480
- package/src/server-graph-enhanced.js +0 -801
- package/src/server-minimal.js +0 -224
- package/src/server-oauth-original.js +0 -404
- package/src/server-oauth.js +0 -472
- package/src/server-production.js +0 -346
- package/src/server-working.js +0 -309
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 [
|
|
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.
|
|
59
|
+
### 2. No Installation Needed!
|
|
56
60
|
|
|
57
|
-
|
|
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
|
-
- 🌐
|
|
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.
|
|
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
|
|
5
|
+
"main": "src/server.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"purmemo-mcp": "./src/server
|
|
8
|
+
"purmemo-mcp": "./src/server.js",
|
|
9
9
|
"purmemo-mcp-setup": "./src/setup.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"src/",
|
|
13
|
-
"
|
|
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
|
|
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
|
-
*
|
|
4
|
-
*
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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
|
-
|
|
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: '
|
|
48
|
+
description: '🏷️ Extract entities from memories',
|
|
56
49
|
inputSchema: {
|
|
57
50
|
type: 'object',
|
|
58
51
|
properties: {
|
|
59
|
-
entity_name: { type: 'string', description: '
|
|
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
|
-
|
|
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', '
|
|
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
|
|
87
|
-
correction: { type: 'string', description: 'The
|
|
88
|
-
|
|
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
|
|
85
|
+
// Create server
|
|
96
86
|
const server = new Server(
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
//
|
|
115
|
-
|
|
116
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
397
|
+
if (error.message === 'NO_AUTH') {
|
|
398
|
+
return createAuthMessage('attach');
|
|
399
|
+
}
|
|
213
400
|
return {
|
|
214
401
|
content: [{
|
|
215
402
|
type: 'text',
|
|
216
|
-
text:
|
|
403
|
+
text: `❌ Error attaching files: ${error.message}`
|
|
217
404
|
}]
|
|
218
405
|
};
|
|
219
406
|
}
|
|
220
|
-
}
|
|
407
|
+
}
|
|
221
408
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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