memory-agent-mcp 0.0.0-stage → 1.0.1

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.
Files changed (4) hide show
  1. package/PLAN.md +42 -0
  2. package/README.md +64 -2
  3. package/index.js +422 -0
  4. package/package.json +30 -4
package/PLAN.md ADDED
@@ -0,0 +1,42 @@
1
+ # Memory MCP Wrapper — Plan & Status
2
+
3
+ ## Goal
4
+ Build and publish `memory-agent-mcp` npm package following `vibe-token-mcp` pattern.
5
+
6
+ ## Status: ✅ BUILT & TESTED
7
+
8
+ ## Completed
9
+ - [x] Create package.json
10
+ - [x] Create index.js with MCP tools: store, search, subscribe, health, stats, sample
11
+ - [x] Create README.md
12
+ - [x] Install dependencies (@modelcontextprotocol/sdk, viem)
13
+ - [x] Test MCP server startup (stdio)
14
+ - [x] Test tools/list (6 tools registered)
15
+ - [x] Test health tool (returns service status)
16
+ - [x] Test sample tool (handles IP token conflict gracefully)
17
+
18
+ ## TODO
19
+ - [ ] Test store tool with fresh IP (or handle existing token)
20
+ - [ ] Test search tool (free tier + paid)
21
+ - [ ] Test subscribe tool (x402 payment)
22
+ - [ ] Fix sample tool to handle existing write_token
23
+ - [ ] Publish to npm as `memory-agent-mcp`
24
+ - [ ] Add to x402-list MCP registry
25
+ - [ ] Create README for npm page
26
+ - [ ] Test end-to-end with real payment
27
+
28
+ ## Tools
29
+ | Tool | Description | Payment |
30
+ |------|-------------|---------|
31
+ | store | Store a memory (free) | Free |
32
+ | search | Semantic search (30/day free, then $0.01) | Free tier + x402 |
33
+ | subscribe | Subscribe to plan | x402 ($2/$9/$20) |
34
+ | health | Service health | Free |
35
+ | stats | Global stats | Free |
36
+ | sample | Demo store+search | Free |
37
+
38
+ ## Next Steps
39
+ 1. Publish to npm
40
+ 2. Add memory-mcp to OpenCode config
41
+ 3. Test with real wallet
42
+ 4. Update llms.txt and openapi.json to mention MCP
package/README.md CHANGED
@@ -1,3 +1,65 @@
1
- # Temporary Holding Version
1
+ # memory-agent-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ MCP wrapper for [Memory01](https://memory.node01.org) — agentic memory for AI agents.
4
+
5
+ ## Features
6
+
7
+ - **Store**: Free memory storage (first call returns a write_token)
8
+ - **Search**: Semantic search across 50+ languages (free tier: 30/day, then $0.01 USDC)
9
+ - **Subscribe**: Subscription plans (Starter $2/500, Pro $9/mo, Enterprise $20/mo)
10
+ - **Health**: Service health check (free)
11
+ - **Stats**: Global memory statistics (free)
12
+ - **Sample**: Free demo (store + search)
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install -g memory-agent-mcp
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ Set these environment variables:
23
+
24
+ - `MEMORY_BASE_URL` (default: https://memory.node01.org)
25
+ - `MEMORY_PAYTO` (USDC wallet address on Base for paid calls)
26
+ - `MEMORY_PRIVATE_KEY` (private key for x402 payment signing)
27
+
28
+ ## Usage with OpenCode
29
+
30
+ Add to your `opencode.json`:
31
+
32
+ ```json
33
+ {
34
+ "mcp": {
35
+ "memory": {
36
+ "type": "local",
37
+ "command": ["npx", "-y", "memory-agent-mcp"],
38
+ "environment": {
39
+ "MEMORY_PAYTO": "0x...",
40
+ "MEMORY_PRIVATE_KEY": "0x..."
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Tools
48
+
49
+ ### store
50
+ Store a memory. First call returns a write_token — save it! All future operations require it.
51
+
52
+ ### search
53
+ Search memories semantically. Free tier: 30 searches/day. After that, $0.01 USDC per search.
54
+
55
+ ### subscribe
56
+ Subscribe to a plan for more searches.
57
+
58
+ ### health
59
+ Check service health.
60
+
61
+ ### stats
62
+ Get global statistics.
63
+
64
+ ### sample
65
+ Free demo: store and search a sample memory.
package/index.js ADDED
@@ -0,0 +1,422 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * memory-agent-mcp — MCP stdio server for Memory01 agentic memory service
4
+ *
5
+ * Provides tools to interact with the Memory01 API:
6
+ * - store(agentId, key, value, writeToken?, sessionId?, metadata?) -> Store a memory (free)
7
+ * - search(agentId, query, writeToken, topK?, sessionId?, cursor?) -> Search memories (free tier 30/day, then $0.01)
8
+ * - subscribe(agentId, writeToken, plan) -> Subscribe to a plan (x402)
9
+ * - health() -> Service health check (free)
10
+ * - stats() -> Global memory stats (free)
11
+ * - sample() -> Free sample (store + search demo)
12
+ *
13
+ * Usage:
14
+ * npx memory-agent-mcp
15
+ * or: node index.js
16
+ *
17
+ * Configure with environment variables:
18
+ * MEMORY_BASE_URL (default: https://memory.node01.org)
19
+ * MEMORY_PAYTO (required for paid calls - USDC wallet address)
20
+ * MEMORY_PRIVATE_KEY (required for x402 payment signing)
21
+ */
22
+
23
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
24
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
25
+ import {
26
+ CallToolRequestSchema,
27
+ ListToolsRequestSchema,
28
+ ErrorCode,
29
+ McpError,
30
+ } from "@modelcontextprotocol/sdk/types.js";
31
+ import { createWalletClient, http, parseEther } from "viem";
32
+ import { privateKeyToAccount } from "viem/accounts";
33
+ import { base } from "viem/chains";
34
+
35
+ // ============================= Config =============================
36
+ const MEMORY_BASE_URL = process.env.MEMORY_BASE_URL || "https://memory.node01.org";
37
+ const MEMORY_PAYTO = process.env.MEMORY_PAYTO;
38
+ const MEMORY_PRIVATE_KEY = process.env.MEMORY_PRIVATE_KEY;
39
+
40
+ if (!MEMORY_PAYTO) {
41
+ console.error("MEMORY_PAYTO environment variable required for paid calls");
42
+ process.exit(1);
43
+ }
44
+
45
+ if (!MEMORY_PRIVATE_KEY) {
46
+ console.error("MEMORY_PRIVATE_KEY environment variable required for x402 payment signing");
47
+ process.exit(1);
48
+ }
49
+
50
+ const privateKey = MEMORY_PRIVATE_KEY.startsWith("0x") ? MEMORY_PRIVATE_KEY : `0x${MEMORY_PRIVATE_KEY}`;
51
+ const account = privateKeyToAccount(privateKey);
52
+
53
+ // ============================= x402 Payment Helper =============================
54
+ async function payAndRetry(endpoint, body, priceUsd = "0.01") {
55
+ const walletClient = createWalletClient({
56
+ account,
57
+ chain: base,
58
+ transport: http(),
59
+ });
60
+
61
+ // First call to get 402
62
+ const response = await fetch(`${MEMORY_BASE_URL}${endpoint}`, {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json", "Accept": "application/json" },
65
+ body: JSON.stringify(body),
66
+ });
67
+
68
+ if (response.status !== 402) {
69
+ const data = await response.json();
70
+ return { data, status: response.status };
71
+ }
72
+
73
+ const paymentRequired = await response.json();
74
+ const accepts = paymentRequired.accepts || [];
75
+ const exactOption = accepts.find(
76
+ (a) => a.scheme === "exact" && a.network === "eip155:8453"
77
+ );
78
+
79
+ if (!exactOption) {
80
+ throw new Error("No exact payment option for Base network found");
81
+ }
82
+
83
+ const facilitatorUrl = paymentRequired.extensions?.["pay-with-x"]?.facilitator || "https://facilitator.payai.network";
84
+
85
+ const paymentPayload = {
86
+ x402Version: paymentRequired.x402Version || 2,
87
+ scheme: "exact",
88
+ network: "eip155:8453",
89
+ payload: {
90
+ authorization: {
91
+ from: account.address,
92
+ to: exactOption.payTo,
93
+ value: priceUsd,
94
+ validAfter: Math.floor(Date.now() / 1000),
95
+ validBefore: Math.floor(Date.now() / 1000) + 300,
96
+ nonce: Math.floor(Math.random() * 1000000),
97
+ },
98
+ },
99
+ };
100
+
101
+ const signature = await walletClient.signTypedData({
102
+ domain: {
103
+ name: "x402 Payment",
104
+ version: "1",
105
+ chainId: 8453,
106
+ verifyingContract: exactOption.payTo,
107
+ },
108
+ types: {
109
+ ExactPayment: [
110
+ { name: "from", type: "address" },
111
+ { name: "to", type: "address" },
112
+ { name: "value", type: "uint256" },
113
+ { name: "validAfter", type: "uint256" },
114
+ { name: "validBefore", type: "uint256" },
115
+ { name: "nonce", type: "uint256" },
116
+ ],
117
+ },
118
+ primaryType: "ExactPayment",
119
+ message: {
120
+ from: account.address,
121
+ to: exactOption.payTo,
122
+ value: priceUsd,
123
+ validAfter: Math.floor(Date.now() / 1000),
124
+ validBefore: Math.floor(Date.now() / 1000) + 300,
125
+ nonce: Math.floor(Math.random() * 1000000),
126
+ },
127
+ });
128
+
129
+ paymentPayload.payload.authorization.signature = signature;
130
+
131
+ const settleResponse = await fetch(`${facilitatorUrl}/settle`, {
132
+ method: "POST",
133
+ headers: { "Content-Type": "application/json" },
134
+ body: JSON.stringify(paymentPayload),
135
+ });
136
+
137
+ if (!settleResponse.ok) {
138
+ const err = await settleResponse.text();
139
+ throw new Error(`Payment settlement failed: ${settleResponse.status} ${err}`);
140
+ }
141
+
142
+ const settleResult = await settleResponse.json();
143
+ const paymentHeader = settleResult.paymentHeader || settleResult.header || settleResult.payment;
144
+
145
+ // Retry with payment header
146
+ const finalResponse = await fetch(`${MEMORY_BASE_URL}${endpoint}`, {
147
+ method: "POST",
148
+ headers: {
149
+ "Content-Type": "application/json",
150
+ "Accept": "application/json",
151
+ "X-Payment": paymentHeader,
152
+ },
153
+ body: JSON.stringify(body),
154
+ });
155
+
156
+ const data = await finalResponse.json();
157
+ return { data, status: finalResponse.status };
158
+ }
159
+
160
+ // ============================= Free API Calls =============================
161
+ async function callFree(endpoint, method = "GET", body = null) {
162
+ const opts = { method, headers: { "Accept": "application/json" } };
163
+ if (body) {
164
+ opts.headers["Content-Type"] = "application/json";
165
+ opts.body = JSON.stringify(body);
166
+ }
167
+ const response = await fetch(`${MEMORY_BASE_URL}${endpoint}`, opts);
168
+ return { data: await response.json(), status: response.status };
169
+ }
170
+
171
+ // ============================= MCP Server =============================
172
+ const server = new Server(
173
+ { name: "memory-agent-mcp", version: "1.0.0" },
174
+ { capabilities: { tools: {} } }
175
+ );
176
+
177
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
178
+ return {
179
+ tools: [
180
+ {
181
+ name: "store",
182
+ description: "Store a memory (free). First call returns a write_token — save it! All future operations require it. Memories are scoped per write_token.",
183
+ inputSchema: {
184
+ type: "object",
185
+ properties: {
186
+ agentId: { type: "string", description: "Your agent identifier" },
187
+ key: { type: "string", description: "Memory key (max 256 chars)" },
188
+ value: { type: "string", description: "Memory value (max 10KB)" },
189
+ writeToken: { type: "string", description: "Write token from first store (omit on first call)" },
190
+ sessionId: { type: "string", description: "Optional session ID to scope memories" },
191
+ metadata: { type: "object", description: "Optional metadata (max 2KB)" },
192
+ },
193
+ required: ["agentId", "key", "value"],
194
+ },
195
+ },
196
+ {
197
+ name: "search",
198
+ description: "Search memories semantically (free tier: 30/day, then $0.01 USDC). Returns matching memories with relevance scores. Supports 50+ languages, cross-lingual search.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ agentId: { type: "string", description: "Your agent identifier" },
203
+ query: { type: "string", description: "Search query (max 500 chars). Omit for session-only chronological listing." },
204
+ writeToken: { type: "string", description: "Write token (required)" },
205
+ topK: { type: "number", description: "Max results (default 10, max 50)", default: 10 },
206
+ sessionId: { type: "string", description: "Optional: scope to session. Omit query for chronological listing." },
207
+ cursor: { type: "string", description: "Pagination cursor from previous response" },
208
+ },
209
+ required: ["agentId", "writeToken"],
210
+ },
211
+ },
212
+ {
213
+ name: "subscribe",
214
+ description: "Subscribe to a memory plan (x402). Starter: $2/500 credits. Pro: $9/mo unlimited. Enterprise: $20/mo unlimited+priority.",
215
+ inputSchema: {
216
+ type: "object",
217
+ properties: {
218
+ agentId: { type: "string", description: "Your agent identifier" },
219
+ writeToken: { type: "string", description: "Write token (required)" },
220
+ plan: { type: "string", enum: ["starter", "pro", "enterprise"], description: "Plan to subscribe to" },
221
+ },
222
+ required: ["agentId", "writeToken", "plan"],
223
+ },
224
+ },
225
+ {
226
+ name: "health",
227
+ description: "Check Memory01 service health (free). Returns service status, free tier info, plans.",
228
+ inputSchema: { type: "object", properties: {} },
229
+ },
230
+ {
231
+ name: "stats",
232
+ description: "Get global memory statistics (free). Returns total agents, entries, searches.",
233
+ inputSchema: { type: "object", properties: {} },
234
+ },
235
+ {
236
+ name: "sample",
237
+ description: "Get a free sample: store a demo memory and search it. Shows the shape of responses without paying.",
238
+ inputSchema: { type: "object", properties: {} },
239
+ },
240
+ ],
241
+ };
242
+ });
243
+
244
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
245
+ const { name, arguments: args } = request.params;
246
+
247
+ try {
248
+ switch (name) {
249
+ case "store": {
250
+ const { agentId, key, value, writeToken, sessionId, metadata } = args;
251
+ if (!agentId || !key || !value) {
252
+ throw new McpError(ErrorCode.InvalidParams, "agentId, key, and value are required");
253
+ }
254
+ if (key.length > 256) throw new McpError(ErrorCode.InvalidParams, "key max 256 chars");
255
+ if (value.length > 10240) throw new McpError(ErrorCode.InvalidParams, "value max 10KB");
256
+ if (metadata && JSON.stringify(metadata).length > 2048) throw new McpError(ErrorCode.InvalidParams, "metadata max 2KB");
257
+
258
+ const body = { agent_id: agentId, key, value };
259
+ if (writeToken) body.write_token = writeToken;
260
+ if (sessionId) body.session_id = sessionId;
261
+ if (metadata) body.metadata = metadata;
262
+
263
+ const result = await callFree("/api/memory/store", "POST", body);
264
+ if (result.status >= 400) {
265
+ throw new McpError(ErrorCode.InternalError, `API error: ${JSON.stringify(result.data)}`);
266
+ }
267
+
268
+ const r = result.data;
269
+ return {
270
+ content: [{
271
+ type: "text",
272
+ text: JSON.stringify({
273
+ ok: r.ok,
274
+ stored: r.stored,
275
+ write_token: r.write_token,
276
+ message: r.message,
277
+ key: r.key,
278
+ stored_at: r.stored_at,
279
+ updated_at: r.updated_at,
280
+ }, null, 2),
281
+ }],
282
+ };
283
+ }
284
+
285
+ case "search": {
286
+ const { agentId, query, writeToken, topK = 10, sessionId, cursor } = args;
287
+ if (!agentId || !writeToken) {
288
+ throw new McpError(ErrorCode.InvalidParams, "agentId and writeToken are required");
289
+ }
290
+ if (query && query.length > 500) throw new McpError(ErrorCode.InvalidParams, "query max 500 chars");
291
+
292
+ const body = { agent_id: agentId, write_token: writeToken, top_k: topK };
293
+ if (query) body.query = query;
294
+ if (sessionId) body.session_id = sessionId;
295
+ if (cursor) body.cursor = cursor;
296
+
297
+ // Try free first
298
+ const freeResult = await callFree("/api/memory/search", "POST", body);
299
+
300
+ if (freeResult.status === 402) {
301
+ // Free tier exhausted, pay
302
+ const paidResult = await payAndRetry("/api/memory/search", body, "0.01");
303
+ if (paidResult.status >= 400) {
304
+ throw new McpError(ErrorCode.InternalError, `API error: ${JSON.stringify(paidResult.data)}`);
305
+ }
306
+ const r = paidResult.data;
307
+ return {
308
+ content: [{
309
+ type: "text",
310
+ text: JSON.stringify({
311
+ results: r.results,
312
+ free_tier: r.free_tier,
313
+ tier: r.tier,
314
+ searched_at: r.searched_at,
315
+ next_cursor: r.next_cursor,
316
+ cursor_expires_in: r.cursor_expires_in,
317
+ }, null, 2),
318
+ }],
319
+ };
320
+ }
321
+
322
+ if (freeResult.status >= 400) {
323
+ throw new McpError(ErrorCode.InternalError, `API error: ${JSON.stringify(freeResult.data)}`);
324
+ }
325
+
326
+ const r = freeResult.data;
327
+ return {
328
+ content: [{
329
+ type: "text",
330
+ text: JSON.stringify({
331
+ results: r.results,
332
+ free_tier: r.free_tier,
333
+ tier: r.tier,
334
+ searched_at: r.searched_at,
335
+ next_cursor: r.next_cursor,
336
+ cursor_expires_in: r.cursor_expires_in,
337
+ }, null, 2),
338
+ }],
339
+ };
340
+ }
341
+
342
+ case "subscribe": {
343
+ const { agentId, writeToken, plan } = args;
344
+ if (!agentId || !writeToken || !plan) {
345
+ throw new McpError(ErrorCode.InvalidParams, "agentId, writeToken, and plan are required");
346
+ }
347
+
348
+ const body = { agent_id: agentId, write_token: writeToken, plan };
349
+ const priceMap = { starter: "2.00", pro: "9.00", enterprise: "20.00" };
350
+ const paidResult = await payAndRetry("/api/memory/subscribe", body, priceMap[plan] || "2.00");
351
+
352
+ if (paidResult.status >= 400) {
353
+ throw new McpError(ErrorCode.InternalError, `API error: ${JSON.stringify(paidResult.data)}`);
354
+ }
355
+
356
+ return {
357
+ content: [{
358
+ type: "text",
359
+ text: JSON.stringify(paidResult.data, null, 2),
360
+ }],
361
+ };
362
+ }
363
+
364
+ case "health": {
365
+ const result = await callFree("/api/health");
366
+ return {
367
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
368
+ };
369
+ }
370
+
371
+ case "stats": {
372
+ const result = await callFree("/api/memory/stats");
373
+ return {
374
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
375
+ };
376
+ }
377
+
378
+ case "sample": {
379
+ // Store a demo memory, then search it
380
+ const storeResult = await callFree("/api/memory/store", "POST", {
381
+ agent_id: "sample-agent",
382
+ key: "demo",
383
+ value: "This is a sample memory for testing Memory01 service",
384
+ });
385
+
386
+ const searchResult = await callFree("/api/memory/search", "POST", {
387
+ agent_id: "sample-agent",
388
+ write_token: storeResult.data.write_token,
389
+ query: "sample memory",
390
+ });
391
+
392
+ return {
393
+ content: [{
394
+ type: "text",
395
+ text: JSON.stringify({
396
+ store: storeResult.data,
397
+ search: searchResult.data,
398
+ }, null, 2),
399
+ }],
400
+ };
401
+ }
402
+
403
+ default:
404
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
405
+ }
406
+ } catch (error) {
407
+ if (error instanceof McpError) throw error;
408
+ console.error(`Tool ${name} error:`, error);
409
+ throw new McpError(ErrorCode.InternalError, String(error));
410
+ }
411
+ });
412
+
413
+ async function main() {
414
+ const transport = new StdioServerTransport();
415
+ await server.connect(transport);
416
+ console.error("memory-agent-mcp running on stdio");
417
+ }
418
+
419
+ main().catch((err) => {
420
+ console.error("Fatal error:", err);
421
+ process.exit(1);
422
+ });
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "memory-agent-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "MCP wrapper for Memory01 agentic memory service - free to store, paid to search",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "memory-mcp": "index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node index.js",
12
+ "test": "node test.js"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "memory",
17
+ "agent",
18
+ "x402",
19
+ "ai",
20
+ "base",
21
+ "storage"
22
+ ],
23
+ "author": "node01.org",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.0.0",
27
+ "viem": "^2.57.16"
28
+ },
29
+ "engines": {
30
+ "node": ">=20.0.0"
31
+ }
32
+ }