@xmemo/client 0.4.172 → 0.4.174
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 +10 -6
- package/bin/mcp-stdio.js +9 -25
- package/package.json +5 -2
- package/plugins/xmemo/.cursor-plugin/plugin.json +38 -0
- package/plugins/xmemo/CHANGELOG.md +7 -0
- package/plugins/xmemo/LICENSE +7 -0
- package/plugins/xmemo/README.md +32 -0
- package/plugins/xmemo/assets/logo.svg +27 -0
- package/plugins/xmemo/mcp.json +7 -0
- package/plugins/xmemo/rules/AGENTS.mdc +16 -0
- package/plugins/xmemo/skills/agents/SKILL.md +29 -0
- package/skills/xmemo/SKILL.md +142 -0
- package/src/commands/mcp.js +7 -0
- package/src/mcp/stdio-server.js +462 -0
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# XMemo CLI
|
|
2
2
|
|
|
3
3
|
[](https://smithery.ai/servers/xmemo/xmemo)
|
|
4
|
+
[](https://lobehub.com/mcp/yonro-memory-os-cli)
|
|
4
5
|
|
|
5
6
|
`@xmemo/client` is the privacy-first command line entry point for XMemo client
|
|
6
7
|
setup. It is intentionally small: the npm package contains only the CLI and
|
|
7
|
-
setup
|
|
8
|
+
setup/helper assets needed on a user's machine: the CLI runtime, client setup
|
|
9
|
+
profiles, XMemo skills, and marketplace plugin metadata.
|
|
8
10
|
|
|
9
11
|
`@yonro/xmemo-client` is reserved as a Yonro fallback package. The CLI exposes
|
|
10
12
|
`xmemo` as the primary command and keeps `memory-os` as a compatibility alias.
|
|
@@ -12,12 +14,14 @@ setup helper code needed on a user's machine.
|
|
|
12
14
|
The XMemo server, database, token registry, deployment files, logs, and
|
|
13
15
|
internal scripts are not part of this npm package.
|
|
14
16
|
|
|
15
|
-
>
|
|
17
|
+
> **XMemo CLI is the top-level control plane** — use `xmemo login`, `xmemo doctor`, `xmemo setup <client>`, and smoke checks before hand-editing MCP config. Hosted MCP remains the universal runtime path for clients that do not have a native integration.
|
|
16
18
|
|
|
17
|
-
##
|
|
19
|
+
## XMemo Runtime Overview
|
|
18
20
|
|
|
19
|
-
**XMemo** is a user-owned
|
|
21
|
+
**XMemo** is a user-owned memory system that lets AI agents persistently store, search, recall, update, and manage notes and memory fragments across sessions, projects, and tools.
|
|
20
22
|
|
|
23
|
+
- **Top-level CLI**: `xmemo` from `@xmemo/client`
|
|
24
|
+
- **Native integrations**: OpenClaw XMemo memory plugin and Hermes `hermes-xmemo` provider
|
|
21
25
|
- **MCP Endpoint**: `https://xmemo.dev/mcp` (Streamable HTTP)
|
|
22
26
|
- **Auth**: Bearer Token (`XMEMO_KEY`) or MCP OAuth
|
|
23
27
|
- **Tools**: `remember`, `recall`, `search_memory`, `update_memory`, `forget`, `redact_memory`, `explain_memory`, `create_memory_todo`, `list_memory_todos`, `complete_memory_todo`, `record_event`, `get_timeline`, `add_expense`
|
|
@@ -120,8 +124,8 @@ xmemo privacy
|
|
|
120
124
|
project files, shell history, and printed token values.
|
|
121
125
|
- Legacy `xmemo token set` refuses plaintext credential storage unless
|
|
122
126
|
`--allow-plaintext` is explicitly provided.
|
|
123
|
-
- The npm package uses a `files` whitelist so only `bin`, `src`, `
|
|
124
|
-
and `LICENSE` are published.
|
|
127
|
+
- The npm package uses a `files` whitelist so only `bin`, `src`, `skills`,
|
|
128
|
+
published plugin metadata/assets, `README.md`, and `LICENSE` are published.
|
|
125
129
|
|
|
126
130
|
## Token flow
|
|
127
131
|
|
package/bin/mcp-stdio.js
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* XMemo MCP stdio server —
|
|
4
|
-
* clients that only support stdio transport.
|
|
3
|
+
* XMemo MCP stdio server — self-contained local proxy.
|
|
5
4
|
*
|
|
6
|
-
* Reads XMEMO_KEY and XMEMO_URL from environment, forwards
|
|
7
|
-
*
|
|
5
|
+
* Reads XMEMO_KEY and XMEMO_URL from environment, forwards MCP JSON-RPC
|
|
6
|
+
* messages to the hosted endpoint over Streamable HTTP. Falls back to
|
|
7
|
+
* static tool metadata when unauthenticated so marketplace validators
|
|
8
|
+
* can confirm server capabilities.
|
|
9
|
+
*
|
|
10
|
+
* Equivalent to: xmemo mcp serve
|
|
8
11
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import { resolve, dirname } from 'node:path';
|
|
11
|
-
import { fileURLToPath } from 'node:url';
|
|
12
|
-
|
|
13
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const mcpRemoteBin = resolve(__dirname, '..', 'node_modules', '.bin', 'mcp-remote');
|
|
15
|
-
const url = process.env.XMEMO_URL
|
|
16
|
-
? `${process.env.XMEMO_URL.replace(/\/$/, '')}/mcp`
|
|
17
|
-
: 'https://xmemo.dev/mcp';
|
|
18
|
-
const token = process.env.XMEMO_KEY ?? '';
|
|
19
|
-
|
|
20
|
-
const args = [url];
|
|
21
|
-
if (token) {
|
|
22
|
-
args.push('--header', `Authorization:Bearer ${token}`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const child = spawn(process.execPath, [mcpRemoteBin, ...args], {
|
|
26
|
-
stdio: ['inherit', 'inherit', 'inherit'],
|
|
27
|
-
env: { ...process.env }
|
|
28
|
-
});
|
|
12
|
+
import { startStdioServer } from '../src/mcp/stdio-server.js';
|
|
29
13
|
|
|
30
|
-
|
|
14
|
+
await startStdioServer(process.env);
|
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmemo/client",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.174",
|
|
4
4
|
"description": "Privacy-first CLI and MCP setup helper for XMemo.",
|
|
5
5
|
"mcpName": "io.github.yonro/xmemo",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"xmemo": "bin/memory-os.js",
|
|
9
|
-
"memory-os": "bin/memory-os.js"
|
|
9
|
+
"memory-os": "bin/memory-os.js",
|
|
10
|
+
"xmemo-mcp": "bin/mcp-stdio.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"bin",
|
|
13
14
|
"src",
|
|
15
|
+
"skills",
|
|
14
16
|
"plugins/kiro",
|
|
17
|
+
"plugins/xmemo",
|
|
15
18
|
"README.md",
|
|
16
19
|
"LICENSE"
|
|
17
20
|
],
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xmemo",
|
|
3
|
+
"displayName": "XMemo",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Connect Cursor to XMemo's hosted, user-owned memory layer for durable project context, coding preferences, decisions, and reusable agent knowledge.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "XMemo",
|
|
8
|
+
"email": "support@xmemo.dev"
|
|
9
|
+
},
|
|
10
|
+
"publisher": "XMemo",
|
|
11
|
+
"homepage": "https://xmemo.dev",
|
|
12
|
+
"repository": "https://github.com/yonro/memory-os-cli",
|
|
13
|
+
"license": "UNLICENSED",
|
|
14
|
+
"logo": "assets/logo.svg",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"cursor",
|
|
17
|
+
"plugin",
|
|
18
|
+
"mcp",
|
|
19
|
+
"memory",
|
|
20
|
+
"agent-memory",
|
|
21
|
+
"developer-tools",
|
|
22
|
+
"oauth"
|
|
23
|
+
],
|
|
24
|
+
"category": "Developer Tools",
|
|
25
|
+
"tags": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"memory",
|
|
28
|
+
"oauth",
|
|
29
|
+
"developer-tools"
|
|
30
|
+
],
|
|
31
|
+
"rules": [
|
|
32
|
+
"rules/AGENTS.mdc"
|
|
33
|
+
],
|
|
34
|
+
"skills": [
|
|
35
|
+
"skills/agents/SKILL.md"
|
|
36
|
+
],
|
|
37
|
+
"mcpServers": "mcp.json"
|
|
38
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) Yonro.
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This Cursor plugin is published as an XMemo client distribution artifact.
|
|
6
|
+
No license is granted to copy, modify, distribute, sublicense, or use the source
|
|
7
|
+
code except as expressly permitted by Yonro in a separate written agreement.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# XMemo Cursor Plugin
|
|
2
|
+
|
|
3
|
+
XMemo gives Cursor a hosted, user-owned memory layer for durable project context, coding preferences, decisions, TODOs, and reusable agent knowledge.
|
|
4
|
+
|
|
5
|
+
## What it installs
|
|
6
|
+
|
|
7
|
+
- `mcp.json` adds the hosted XMemo MCP server at `https://xmemo.dev/mcp`.
|
|
8
|
+
- `assets/logo.svg` reuses the canonical XMemo marketplace icon source used by the existing ChatGPT/Claude listing assets.
|
|
9
|
+
- `rules/AGENTS.mdc` tells Cursor when to use XMemo memory.
|
|
10
|
+
- `skills/agents/SKILL.md` gives Cursor a safe workflow for recall, writes, TODOs, and destructive memory actions.
|
|
11
|
+
|
|
12
|
+
## Authentication
|
|
13
|
+
|
|
14
|
+
The marketplace plugin is OAuth-first. The plugin metadata stores only the hosted MCP URL; the first XMemo tool use should open the browser-based OAuth flow. Do not add `Authorization`, `Bearer`, or `XMEMO_KEY` to the marketplace `mcp.json`.
|
|
15
|
+
|
|
16
|
+
Manual direct-key fallback remains available through:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
xmemo mcp config --client cursor --json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use that fallback only for local/manual installs where Cursor OAuth is unavailable.
|
|
23
|
+
|
|
24
|
+
## Reviewer smoke prompts
|
|
25
|
+
|
|
26
|
+
1. "List the XMemo tools you can use in Cursor."
|
|
27
|
+
2. "Search XMemo for coding style preferences for this project."
|
|
28
|
+
3. "Remember in XMemo: For Cursor review, prefer small PRs with validation evidence."
|
|
29
|
+
4. "Recall what I saved about Cursor review PR preferences."
|
|
30
|
+
5. "Create a XMemo memory TODO to capture Cursor review screenshots tomorrow, then list my TODOs."
|
|
31
|
+
|
|
32
|
+
Use a dedicated reviewer workspace with synthetic data only. Redact emails, OAuth codes, cookies, bearer tokens, trace IDs, internal account IDs, real memory content, and private local paths from evidence.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-label="XMemo Claude connector icon">
|
|
2
|
+
<defs>
|
|
3
|
+
<radialGradient id="halo" cx="28%" cy="18%" r="78%">
|
|
4
|
+
<stop offset="0" stop-color="#8df7d5"/>
|
|
5
|
+
<stop offset="0.36" stop-color="#22d3ee"/>
|
|
6
|
+
<stop offset="0.74" stop-color="#102033"/>
|
|
7
|
+
<stop offset="1" stop-color="#070b18"/>
|
|
8
|
+
</radialGradient>
|
|
9
|
+
<linearGradient id="mark" x1="180" x2="844" y1="238" y2="786" gradientUnits="userSpaceOnUse">
|
|
10
|
+
<stop offset="0" stop-color="#f2fff9"/>
|
|
11
|
+
<stop offset="0.42" stop-color="#8df7d5"/>
|
|
12
|
+
<stop offset="1" stop-color="#22d3ee"/>
|
|
13
|
+
</linearGradient>
|
|
14
|
+
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
|
|
15
|
+
<feDropShadow dx="0" dy="24" stdDeviation="46" flood-color="#22d3ee" flood-opacity="0.36"/>
|
|
16
|
+
<feDropShadow dx="0" dy="0" stdDeviation="18" flood-color="#8df7d5" flood-opacity="0.46"/>
|
|
17
|
+
</filter>
|
|
18
|
+
</defs>
|
|
19
|
+
<rect width="1024" height="1024" rx="220" fill="#070b18"/>
|
|
20
|
+
<circle cx="272" cy="214" r="440" fill="url(#halo)" opacity="0.64"/>
|
|
21
|
+
<circle cx="842" cy="820" r="420" fill="#14b8a6" opacity="0.18"/>
|
|
22
|
+
<path fill="#ffffff" opacity="0.08" d="M126 746c160 70 310 88 448 56 138-31 246-110 324-235v235c-88 78-197 125-326 141-156 19-304-7-446-79Z"/>
|
|
23
|
+
<g filter="url(#glow)">
|
|
24
|
+
<path fill="url(#mark)" d="M224 724V300h118l170 230 170-230h118v424H672V502L552 670h-80L352 502v222Z"/>
|
|
25
|
+
<path fill="#e6fff8" opacity="0.92" d="M382 300h96l34 48 34-48h96L512 486Z"/>
|
|
26
|
+
</g>
|
|
27
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use XMemo for durable project memory, coding preferences, decisions, TODOs, and cross-session context through the hosted MCP server.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# XMemo memory rule
|
|
6
|
+
|
|
7
|
+
Use XMemo when the user asks Cursor to remember, recall, search, update, explain, delete, restore, or summarize durable context that should survive beyond the current chat.
|
|
8
|
+
|
|
9
|
+
Prefer XMemo for:
|
|
10
|
+
|
|
11
|
+
- coding conventions, repository facts, product decisions, debugging notes, and release runbooks;
|
|
12
|
+
- user preferences that should transfer across Cursor sessions or projects;
|
|
13
|
+
- memory-backed TODO/action items and follow-ups;
|
|
14
|
+
- context packs before planning or implementation when prior project memory could affect the answer.
|
|
15
|
+
|
|
16
|
+
Do not ask the user to paste raw API keys, bearer tokens, OAuth codes, cookies, trace IDs, or private local paths into chat. If XMemo is not authorized, tell the user to reconnect or complete the OAuth flow in Cursor.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agents
|
|
3
|
+
description: Use XMemo's hosted MCP memory tools for durable context, project preferences, decisions, reminders, and cross-session recall in Cursor.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# XMemo Memory
|
|
7
|
+
|
|
8
|
+
Use this skill when the task may depend on prior memory, durable project context, coding preferences, decisions, or follow-up actions.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
1. Recall first when prior context could change the answer. Use XMemo search/recall/context tools before making assumptions about preferences or past decisions.
|
|
13
|
+
2. Save only durable information that the user asks to remember or that is clearly useful across future sessions.
|
|
14
|
+
3. Keep memory content concise, scoped, and useful. Prefer concrete facts, decisions, links to public docs, and action items over chat transcripts.
|
|
15
|
+
4. For destructive memory actions, confirm the exact target before deleting, forgetting, or overwriting.
|
|
16
|
+
5. If authorization fails, tell the user: "Reconnect XMemo through OAuth, or run `xmemo login`, or visit https://xmemo.dev to set up your token." Do not request raw tokens in chat.
|
|
17
|
+
|
|
18
|
+
## Good memory candidates
|
|
19
|
+
|
|
20
|
+
- Repository conventions and verified commands.
|
|
21
|
+
- Architecture decisions, release procedures, and deployment notes.
|
|
22
|
+
- User-approved preferences for code review, testing, documentation, or UX.
|
|
23
|
+
- TODOs and follow-ups that should be visible to future agents.
|
|
24
|
+
|
|
25
|
+
## Avoid saving
|
|
26
|
+
|
|
27
|
+
- Secrets, credentials, OAuth codes, cookies, API keys, or token prefixes.
|
|
28
|
+
- Private customer data or sensitive personal data.
|
|
29
|
+
- Temporary debugging output that will not help future work.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xmemo-memory
|
|
3
|
+
description: Persistent, user-owned memory for AI agents over hosted MCP. Use when an agent should remember decisions, recall project context, manage TODOs, preserve handoff state, or govern memory lifecycle across sessions and tools.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# XMemo Memory
|
|
7
|
+
|
|
8
|
+
Give your agent durable memory that survives across sessions, projects, and tools. XMemo is a hosted MCP memory service; no local database or self-hosting is required.
|
|
9
|
+
|
|
10
|
+
A Skill alone teaches the agent when and how to use memory. Real memory read/write requires the XMemo MCP server and user authorization.
|
|
11
|
+
|
|
12
|
+
## OpenClaw: use the companion plugin
|
|
13
|
+
|
|
14
|
+
When running inside OpenClaw, prefer the native XMemo memory plugin together with this Skill:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
https://clawhub.ai/plugins/@xmemo/openclaw-memory
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- This Skill is the workflow layer: it teaches OpenClaw when to recall, remember, preserve handoff state, and avoid saving secrets.
|
|
21
|
+
- The plugin is the runtime layer: it exposes the real XMemo cloud-memory tools and can become OpenClaw's active memory backend.
|
|
22
|
+
- The plugin writes new OpenClaw memories to its configured `bucket` / `scope`, but recall and search should read all visible user-owned XMemo memories by default. Do not assume `agent_id=openclaw` means "only OpenClaw-authored memories".
|
|
23
|
+
- If this Skill is installed but XMemo memory tools are unavailable, do not simulate a successful memory operation. Recommend installing or enabling the companion plugin.
|
|
24
|
+
- If the plugin is available without this Skill, recommend this Skill for more consistent recall-first and durable-memory behavior.
|
|
25
|
+
|
|
26
|
+
When the user supplies only `https://xmemo.dev`, read these public, secret-free endpoints first:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
https://xmemo.dev/.well-known/agent-discovery.json
|
|
30
|
+
https://xmemo.dev/v1/mcp/config/openclaw
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Do not ask OpenClaw users to choose agent identity fields during normal setup. The plugin supplies the OpenClaw agent identity and a non-secret instance identifier automatically. This OpenClaw recommendation is additive; other agents should continue using their native OAuth or MCP configuration path.
|
|
34
|
+
|
|
35
|
+
## Required connection
|
|
36
|
+
|
|
37
|
+
For full functionality, connect the XMemo MCP server:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
https://xmemo.dev/mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use one of these auth paths:
|
|
44
|
+
|
|
45
|
+
- Native OpenClaw plugin, when the agent runs inside OpenClaw.
|
|
46
|
+
- OAuth, when the MCP client or marketplace supports browser authorization.
|
|
47
|
+
- Bearer token, when the client asks for an API key or request header.
|
|
48
|
+
|
|
49
|
+
To get a bearer token:
|
|
50
|
+
|
|
51
|
+
1. Visit https://xmemo.dev and sign in.
|
|
52
|
+
2. Open the Memory Console.
|
|
53
|
+
3. Go to API Keys: https://xmemo.dev/me#api-keys
|
|
54
|
+
4. Create a scoped API key.
|
|
55
|
+
5. When the MCP client asks for authorization, use the full header value:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
Authorization: Bearer <XMEMO_KEY>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If the client has a dedicated "Bearer Token" or "API Key" field and automatically adds the `Bearer` prefix, paste only the raw `XMEMO_KEY`. If the client asks for a request header value, paste the full `Bearer <XMEMO_KEY>` value.
|
|
62
|
+
|
|
63
|
+
Optional attribution headers can help XMemo show where a memory came from:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
X-Memory-OS-Agent-ID: <client-or-agent-name>
|
|
67
|
+
X-Memory-OS-Agent-Instance-ID: <stable-non-secret-instance-id>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
These attribution headers are not credentials. They are optional, non-secret labels for audit and provenance. Do not put API keys, email addresses, phone numbers, real names, OAuth codes, or other sensitive values in them.
|
|
71
|
+
|
|
72
|
+
Do not ask a normal OpenClaw user to enter these headers manually. The native plugin handles attribution; manual headers are only a fallback for generic MCP clients.
|
|
73
|
+
|
|
74
|
+
## When to use
|
|
75
|
+
|
|
76
|
+
Use XMemo when:
|
|
77
|
+
|
|
78
|
+
- The task depends on prior decisions, preferences, project context, or handoff state.
|
|
79
|
+
- The user asks to remember something for later.
|
|
80
|
+
- The agent is about to make an architecture, product, release, or security decision that prior memory could affect.
|
|
81
|
+
- The user wants TODOs, reminders, milestones, or follow-ups tracked across sessions.
|
|
82
|
+
- Multiple agents or clients need a shared but governed project memory trail.
|
|
83
|
+
|
|
84
|
+
## Workflow
|
|
85
|
+
|
|
86
|
+
1. **Recall before assuming.** For non-trivial work, call `recall_context`, `recall`, or `search_memory` with the current repo, project, task, and subsystem before making decisions.
|
|
87
|
+
2. **Recall across agents.** Search all visible user-owned XMemo memories unless the user explicitly asks for a narrower project, bucket, or scope. Memories may have been written by ChatGPT, Codex, Hermes, OpenClaw, Claude, Cursor, Gemini, or another authorized client.
|
|
88
|
+
3. **Use the result carefully.** Treat recalled memories as context, not as proof that current files, production state, or external services are unchanged. Verify drift-prone facts when correctness matters.
|
|
89
|
+
4. **Read provenance correctly.** `agent_id`, `agent_instance_id`, and `agent_boundary` are attribution/provenance signals, not authorization proof and not a reason to ignore `other_agent` memories. Use them to explain where a memory came from and to distinguish `self` vs `other_agent`.
|
|
90
|
+
5. **Save what matters.** Store durable facts: decisions, conventions, preferences, architecture notes, release procedures, action items, and handoff state. Skip transient chat and noisy debugging output.
|
|
91
|
+
6. **Preserve handoffs.** At milestones or before stopping, use timeline/TODO/snapshot tools such as `record_event`, `create_memory_todo`, or `create_restart_snapshot` when available.
|
|
92
|
+
7. **Govern changes.** Use `explain_memory`, `memory_activity`, `forget_memory`, `redact_memory`, and conflict/version tools when the user asks why a memory exists, what changed, or how to remove or correct something.
|
|
93
|
+
8. **Confirm destructive actions.** Always confirm the exact target before delete, forget, redact, overwrite, or broad cleanup operations.
|
|
94
|
+
9. **On missing OpenClaw tools**, recommend the companion plugin at https://clawhub.ai/plugins/@xmemo/openclaw-memory.
|
|
95
|
+
10. **On auth failure**, tell the user to reconnect XMemo through the plugin or OAuth, or create an API key at https://xmemo.dev/me#api-keys for a manual MCP fallback. Never request raw tokens in chat.
|
|
96
|
+
|
|
97
|
+
## Available tools
|
|
98
|
+
|
|
99
|
+
Core memory operations provided by the XMemo MCP server:
|
|
100
|
+
|
|
101
|
+
| Tool | Purpose |
|
|
102
|
+
|------|---------|
|
|
103
|
+
| `remember` | Save a new durable memory |
|
|
104
|
+
| `recall` / `recall_context` | Retrieve relevant memories before answering or acting |
|
|
105
|
+
| `search_memory` | Search memories by query |
|
|
106
|
+
| `update_memory` | Revise existing memory content or metadata |
|
|
107
|
+
| `forget` / `forget_memory` | Delete or hide a memory |
|
|
108
|
+
| `redact_memory` | Remove sensitive content while keeping an audit trail |
|
|
109
|
+
| `explain_memory` | Show why a memory exists or matched a query |
|
|
110
|
+
| `memory_activity` | Inspect recent writes, reads, deletions, and changes |
|
|
111
|
+
| `create_memory_todo` | Create a follow-up task |
|
|
112
|
+
| `list_memory_todos` | List pending TODOs |
|
|
113
|
+
| `complete_memory_todo` | Mark a TODO done |
|
|
114
|
+
| `record_event` | Log a milestone, decision, or handoff event |
|
|
115
|
+
| `get_timeline` | Show recent events |
|
|
116
|
+
| `create_restart_snapshot` | Save active work state for future sessions |
|
|
117
|
+
| `restore_restart_snapshot` | Resume from saved work state |
|
|
118
|
+
| `add_expense` | Record a ledger entry when the user states a concrete expense |
|
|
119
|
+
|
|
120
|
+
Some deployments expose only a subset of tools depending on OAuth scopes, marketplace policy, or client capability. If a tool is missing, use the closest available safe workflow and explain the limitation briefly.
|
|
121
|
+
|
|
122
|
+
## Good memory candidates
|
|
123
|
+
|
|
124
|
+
- Repository conventions, build/test/deploy commands, and verified troubleshooting steps.
|
|
125
|
+
- Architecture decisions, product decisions, release procedures, and their rationale.
|
|
126
|
+
- User-approved preferences for code review, testing, documentation, or UX.
|
|
127
|
+
- Project TODOs, blockers, risks, and handoff summaries for future sessions.
|
|
128
|
+
- Bug fix context that might recur.
|
|
129
|
+
|
|
130
|
+
## Never save
|
|
131
|
+
|
|
132
|
+
- Secrets, tokens, API keys, OAuth codes, cookies, session IDs, or private keys.
|
|
133
|
+
- Private customer data or sensitive personal data unless the user explicitly asks and the memory tool supports the required privacy policy.
|
|
134
|
+
- Temporary debugging output that will not help future work.
|
|
135
|
+
- Large code blocks; link to files, commits, or concise summaries instead.
|
|
136
|
+
|
|
137
|
+
## Safety
|
|
138
|
+
|
|
139
|
+
- Keep `XMEMO_KEY` private. Do not paste it into public prompts, screenshots, repositories, issue comments, marketplace metadata, or shared logs.
|
|
140
|
+
- Use synthetic data for marketplace demos and screenshots.
|
|
141
|
+
- Treat `X-Memory-OS-Agent-ID` and `X-Memory-OS-Agent-Instance-ID` as attribution only, not authorization proof.
|
|
142
|
+
- Do not claim a marketplace integration is certified unless there is explicit approval evidence for that marketplace.
|
package/src/commands/mcp.js
CHANGED
|
@@ -32,12 +32,14 @@ import {
|
|
|
32
32
|
codexMemoryProfile,
|
|
33
33
|
writeCodexMemoryProfile
|
|
34
34
|
} from '../config/profile.js';
|
|
35
|
+
import { startStdioServer } from '../mcp/stdio-server.js';
|
|
35
36
|
|
|
36
37
|
export async function mcpCommand(args, io) {
|
|
37
38
|
const subcommand = args[0] ?? 'help';
|
|
38
39
|
|
|
39
40
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
40
41
|
writeLine(io.stdout, 'MCP commands:');
|
|
42
|
+
writeLine(io.stdout, ` ${COMMAND_NAME} mcp serve`);
|
|
41
43
|
writeLine(io.stdout, ` ${COMMAND_NAME} mcp list`);
|
|
42
44
|
writeLine(io.stdout, ` ${COMMAND_NAME} mcp config --client <codex|cursor|copilot-cli|antigravity|generic> [--base-url <url>] [--json]`);
|
|
43
45
|
writeLine(io.stdout, ` ${COMMAND_NAME} mcp proxy [--port ${DEFAULT_PROXY_PORT}] [--base-url <url>]`);
|
|
@@ -47,6 +49,11 @@ export async function mcpCommand(args, io) {
|
|
|
47
49
|
return 0;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
if (subcommand === 'serve' || subcommand === 'stdio') {
|
|
53
|
+
await startStdioServer(io.env);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
if (subcommand === 'list') {
|
|
51
58
|
if (hasFlag(args, '--json')) {
|
|
52
59
|
writeLine(io.stdout, JSON.stringify(supportedMcpClients(), null, 2));
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-contained MCP stdio server for XMemo.
|
|
3
|
+
*
|
|
4
|
+
* Forwards JSON-RPC over stdin/stdout to the hosted XMemo MCP endpoint.
|
|
5
|
+
* When no token is available or the remote returns 401, serves static
|
|
6
|
+
* tool/prompt/resource discovery so marketplace validators (LobeHub, Glama,
|
|
7
|
+
* MCP Registry) can confirm the server installs and exposes tools.
|
|
8
|
+
*
|
|
9
|
+
* Real tool calls without a valid token return an auth-required error.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { CLI_VERSION, DEFAULT_SERVICE_URL, TOKEN_ENV_VAR } from '../core/constants.js';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Static tool metadata — returned when remote is unreachable or unauthenticated
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const STATIC_TOOLS = [
|
|
19
|
+
{
|
|
20
|
+
name: 'remember',
|
|
21
|
+
description: 'Save a memory so it can be recalled in future conversations.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
content: { type: 'string', description: 'Text body to save.' },
|
|
26
|
+
path: { type: 'string', description: 'Category path, e.g. preferences, projects/xmemo.' }
|
|
27
|
+
},
|
|
28
|
+
required: ['content', 'path']
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'recall',
|
|
33
|
+
description: 'Recall the most relevant saved memories before answering.',
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
query: { type: 'string', description: 'Natural-language question or search text.' }
|
|
38
|
+
},
|
|
39
|
+
required: ['query']
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'search_memory',
|
|
44
|
+
description: 'Search XMemo memories by natural-language query.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
query: { type: 'string', description: 'Natural-language question or search text.' }
|
|
49
|
+
},
|
|
50
|
+
required: ['query']
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'recall_context',
|
|
55
|
+
description: 'Build a context pack from XMemo memories for complex tasks.',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
query: { type: 'string', description: 'Natural-language question or search text.' }
|
|
60
|
+
},
|
|
61
|
+
required: ['query']
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'update_memory',
|
|
66
|
+
description: 'Update the content or metadata of an existing memory.',
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
memory_id: { type: 'string', description: 'Exact XMemo memory reference.' }
|
|
71
|
+
},
|
|
72
|
+
required: ['memory_id']
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'forget',
|
|
77
|
+
description: 'Permanently delete a memory by target.',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
target: { type: 'string', description: 'The memory to forget: current or an exact memory ID.' }
|
|
82
|
+
},
|
|
83
|
+
required: []
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'forget_memory',
|
|
88
|
+
description: 'Delete a memory (recoverable) by exact reference.',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
memory_id: { type: 'string', description: 'Exact XMemo memory reference.' }
|
|
93
|
+
},
|
|
94
|
+
required: ['memory_id']
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'redact_memory',
|
|
99
|
+
description: 'Redact sensitive content from a memory while keeping an audit trail.',
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
memory_id: { type: 'string', description: 'Exact XMemo memory reference.' }
|
|
104
|
+
},
|
|
105
|
+
required: ['memory_id']
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'explain_memory',
|
|
110
|
+
description: 'Explain why a memory exists or matched a query.',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
memory_id: { type: 'string', description: 'Exact XMemo memory reference.' }
|
|
115
|
+
},
|
|
116
|
+
required: ['memory_id']
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'create_memory_todo',
|
|
121
|
+
description: 'Create a TODO/action item with an optional due time.',
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: {
|
|
125
|
+
content: { type: 'string', description: 'Text body of the TODO item.' }
|
|
126
|
+
},
|
|
127
|
+
required: ['content']
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'list_memory_todos',
|
|
132
|
+
description: 'List open or completed TODO/action items.',
|
|
133
|
+
inputSchema: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
properties: {},
|
|
136
|
+
required: []
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'complete_memory_todo',
|
|
141
|
+
description: 'Mark a TODO/action item completed.',
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
todo_id: { type: 'string', description: 'The memory TODO/action-item ID to complete.' }
|
|
146
|
+
},
|
|
147
|
+
required: ['todo_id']
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'record_event',
|
|
152
|
+
description: 'Record a significant session event, milestone, or decision.',
|
|
153
|
+
inputSchema: {
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: {
|
|
156
|
+
content: { type: 'string', description: 'Text body of the event.' }
|
|
157
|
+
},
|
|
158
|
+
required: ['content']
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'get_timeline',
|
|
163
|
+
description: 'Show recent events.',
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {},
|
|
167
|
+
required: []
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'add_expense',
|
|
172
|
+
description: 'Record one expense in the XMemo Ledger.',
|
|
173
|
+
inputSchema: {
|
|
174
|
+
type: 'object',
|
|
175
|
+
properties: {
|
|
176
|
+
item: { type: 'string', description: 'The purchased item or service.' },
|
|
177
|
+
amount: { type: 'number', description: 'Positive transaction amount.' }
|
|
178
|
+
},
|
|
179
|
+
required: ['item', 'amount']
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'create_restart_snapshot',
|
|
184
|
+
description: 'Save active state for restart/handoff.',
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: 'object',
|
|
187
|
+
properties: {},
|
|
188
|
+
required: []
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'restore_restart_snapshot',
|
|
193
|
+
description: 'Resume previous work from a saved snapshot.',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: {},
|
|
197
|
+
required: []
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'memory_overview',
|
|
202
|
+
description: 'Show a summary of XMemo memories and recent activity.',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {},
|
|
206
|
+
required: []
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const SERVER_INFO = {
|
|
212
|
+
name: 'xmemo',
|
|
213
|
+
version: CLI_VERSION
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const SERVER_CAPABILITIES = {
|
|
217
|
+
tools: {}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Stdio transport
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Start the stdio MCP server. Reads newline-delimited JSON-RPC from stdin,
|
|
226
|
+
* writes responses to stdout.
|
|
227
|
+
*/
|
|
228
|
+
export async function startStdioServer(env = process.env) {
|
|
229
|
+
const token = env[TOKEN_ENV_VAR] || env.MEMORY_OS_API_KEY || env.MEMORY_OS_MCP_TOKEN || '';
|
|
230
|
+
const baseUrl = (env.XMEMO_URL || env.MEMORY_OS_URL || DEFAULT_SERVICE_URL).replace(/\/$/, '');
|
|
231
|
+
const mcpUrl = `${baseUrl}/mcp`;
|
|
232
|
+
|
|
233
|
+
// Track session state
|
|
234
|
+
let sessionId = null;
|
|
235
|
+
let remoteAvailable = false;
|
|
236
|
+
|
|
237
|
+
// Attempt remote initialize to check availability
|
|
238
|
+
if (token) {
|
|
239
|
+
remoteAvailable = await probeRemote(mcpUrl, token);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const rl = await createLineReader();
|
|
243
|
+
|
|
244
|
+
for await (const line of rl) {
|
|
245
|
+
const trimmed = line.trim();
|
|
246
|
+
if (!trimmed) continue;
|
|
247
|
+
|
|
248
|
+
let request;
|
|
249
|
+
try {
|
|
250
|
+
request = JSON.parse(trimmed);
|
|
251
|
+
} catch {
|
|
252
|
+
writeResponse(makeError(null, -32700, 'Parse error'));
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const response = await handleRequest(request, { token, mcpUrl, remoteAvailable, sessionId });
|
|
257
|
+
if (response) {
|
|
258
|
+
if (response._sessionId) {
|
|
259
|
+
sessionId = response._sessionId;
|
|
260
|
+
delete response._sessionId;
|
|
261
|
+
}
|
|
262
|
+
writeResponse(response);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function createLineReader() {
|
|
268
|
+
const { createInterface } = await import('node:readline');
|
|
269
|
+
return createInterface({ input: process.stdin, terminal: false });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function writeResponse(obj) {
|
|
273
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Request handling
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
async function handleRequest(request, ctx) {
|
|
281
|
+
const { method, id, params } = request;
|
|
282
|
+
|
|
283
|
+
// Notifications (no id) — no response needed
|
|
284
|
+
if (id === undefined || id === null) {
|
|
285
|
+
// Forward notifications to remote if available
|
|
286
|
+
if (ctx.token && ctx.remoteAvailable) {
|
|
287
|
+
forwardToRemote(request, ctx).catch(() => {});
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
switch (method) {
|
|
293
|
+
case 'initialize':
|
|
294
|
+
return handleInitialize(id, params, ctx);
|
|
295
|
+
case 'tools/list':
|
|
296
|
+
return handleToolsList(id, params, ctx);
|
|
297
|
+
case 'tools/call':
|
|
298
|
+
return handleToolsCall(id, params, ctx);
|
|
299
|
+
case 'prompts/list':
|
|
300
|
+
return makeResult(id, { prompts: [] });
|
|
301
|
+
case 'resources/list':
|
|
302
|
+
return makeResult(id, { resources: [] });
|
|
303
|
+
case 'ping':
|
|
304
|
+
return makeResult(id, {});
|
|
305
|
+
default:
|
|
306
|
+
// Try to forward unknown methods to remote
|
|
307
|
+
if (ctx.token && ctx.remoteAvailable) {
|
|
308
|
+
return await forwardToRemote(request, ctx);
|
|
309
|
+
}
|
|
310
|
+
return makeError(id, -32601, `Method not found: ${method}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function handleInitialize(id, params, ctx) {
|
|
315
|
+
const result = {
|
|
316
|
+
protocolVersion: '2024-11-05',
|
|
317
|
+
capabilities: SERVER_CAPABILITIES,
|
|
318
|
+
serverInfo: SERVER_INFO
|
|
319
|
+
};
|
|
320
|
+
return makeResult(id, result);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function handleToolsList(id, params, ctx) {
|
|
324
|
+
// Try remote first
|
|
325
|
+
if (ctx.token && ctx.remoteAvailable) {
|
|
326
|
+
try {
|
|
327
|
+
const remoteResponse = await forwardToRemote(
|
|
328
|
+
{ jsonrpc: '2.0', id, method: 'tools/list', params: params || {} },
|
|
329
|
+
ctx
|
|
330
|
+
);
|
|
331
|
+
if (remoteResponse && remoteResponse.result && !remoteResponse.error) {
|
|
332
|
+
return remoteResponse;
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
// Fall through to static
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Static fallback
|
|
339
|
+
return makeResult(id, { tools: STATIC_TOOLS });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function handleToolsCall(id, params, ctx) {
|
|
343
|
+
if (!ctx.token) {
|
|
344
|
+
return makeError(id, -32002,
|
|
345
|
+
`Authentication required. Set the ${TOKEN_ENV_VAR} environment variable or run: xmemo login`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (!ctx.remoteAvailable) {
|
|
349
|
+
return makeError(id, -32002,
|
|
350
|
+
`Remote XMemo server is not reachable. Run: xmemo doctor`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
// Forward to remote
|
|
354
|
+
return await forwardToRemote(
|
|
355
|
+
{ jsonrpc: '2.0', id, method: 'tools/call', params },
|
|
356
|
+
ctx
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
// Remote communication
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
|
|
364
|
+
async function probeRemote(mcpUrl, token) {
|
|
365
|
+
try {
|
|
366
|
+
const response = await fetch(mcpUrl, {
|
|
367
|
+
method: 'POST',
|
|
368
|
+
headers: {
|
|
369
|
+
'content-type': 'application/json',
|
|
370
|
+
accept: 'application/json, text/event-stream',
|
|
371
|
+
authorization: `Bearer ${token}`,
|
|
372
|
+
'user-agent': `XMemo-MCP-Stdio/${CLI_VERSION}`
|
|
373
|
+
},
|
|
374
|
+
body: JSON.stringify({
|
|
375
|
+
jsonrpc: '2.0',
|
|
376
|
+
id: '__probe__',
|
|
377
|
+
method: 'initialize',
|
|
378
|
+
params: {
|
|
379
|
+
protocolVersion: '2024-11-05',
|
|
380
|
+
capabilities: {},
|
|
381
|
+
clientInfo: { name: 'xmemo-mcp-stdio', version: CLI_VERSION }
|
|
382
|
+
}
|
|
383
|
+
}),
|
|
384
|
+
signal: AbortSignal.timeout(10000)
|
|
385
|
+
});
|
|
386
|
+
return response.ok;
|
|
387
|
+
} catch {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function forwardToRemote(request, ctx) {
|
|
393
|
+
try {
|
|
394
|
+
const headers = {
|
|
395
|
+
'content-type': 'application/json',
|
|
396
|
+
accept: 'application/json, text/event-stream',
|
|
397
|
+
authorization: `Bearer ${ctx.token}`,
|
|
398
|
+
'user-agent': `XMemo-MCP-Stdio/${CLI_VERSION}`
|
|
399
|
+
};
|
|
400
|
+
if (ctx.sessionId) {
|
|
401
|
+
headers['mcp-session-id'] = ctx.sessionId;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const response = await fetch(ctx.mcpUrl, {
|
|
405
|
+
method: 'POST',
|
|
406
|
+
headers,
|
|
407
|
+
body: JSON.stringify(request),
|
|
408
|
+
signal: AbortSignal.timeout(30000)
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
if (!response.ok) {
|
|
412
|
+
if (response.status === 401) {
|
|
413
|
+
ctx.remoteAvailable = false;
|
|
414
|
+
return makeError(request.id, -32002,
|
|
415
|
+
`Authentication failed (HTTP 401). Verify your token: xmemo auth status --verify`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
return makeError(request.id, -32603,
|
|
419
|
+
`Remote server returned HTTP ${response.status}`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Capture session ID from response headers
|
|
424
|
+
const newSessionId = response.headers.get('mcp-session-id');
|
|
425
|
+
|
|
426
|
+
const contentType = response.headers.get('content-type') || '';
|
|
427
|
+
let result;
|
|
428
|
+
if (contentType.includes('text/event-stream')) {
|
|
429
|
+
// Parse SSE — take last data line as the JSON-RPC response
|
|
430
|
+
const text = await response.text();
|
|
431
|
+
const dataLines = text.split('\n')
|
|
432
|
+
.filter(l => l.startsWith('data:'))
|
|
433
|
+
.map(l => l.slice(5).trim())
|
|
434
|
+
.filter(l => l);
|
|
435
|
+
const lastData = dataLines[dataLines.length - 1];
|
|
436
|
+
result = lastData ? JSON.parse(lastData) : makeError(request.id, -32603, 'Empty SSE response');
|
|
437
|
+
} else {
|
|
438
|
+
result = await response.json();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (newSessionId) {
|
|
442
|
+
result._sessionId = newSessionId;
|
|
443
|
+
}
|
|
444
|
+
return result;
|
|
445
|
+
} catch (error) {
|
|
446
|
+
return makeError(request.id, -32603,
|
|
447
|
+
`Remote request failed: ${error.message}`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ---------------------------------------------------------------------------
|
|
453
|
+
// JSON-RPC helpers
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
|
|
456
|
+
function makeResult(id, result) {
|
|
457
|
+
return { jsonrpc: '2.0', id, result };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function makeError(id, code, message) {
|
|
461
|
+
return { jsonrpc: '2.0', id, error: { code, message } };
|
|
462
|
+
}
|