diff --git a/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.i18n.yaml new file mode 100644 index 0000000000..0eddfa1740 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.md +2026-07-31-third-party-memory-mcp-examples.md: e82d65a3a5a60cafcc47df5a6873cf5768cd8b8f +2026-07-31-third-party-memory-mcp-examples.zh.md: ee0e9f2e1378f9787e09e32cc05dd13a3648254c diff --git a/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.md b/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.md new file mode 100644 index 0000000000..e82d65a3a5 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.md @@ -0,0 +1,78 @@ +# Agent Note: Third-party memory MCP examples + +Status: implemented + +English | [中文](2026-07-31-third-party-memory-mcp-examples.zh.md) + +## Problem + +A direct vendor integration made one provider's API, configuration, health behavior, and tool semantics part of DSH. That was too much product surface for a capability already expressible through MCP, and it would require repeating the same adaptation for every memory system. Users instead need a small, inspectable way to opt into one external memory server while preserving the generic MCP boundary. + +The acceptance bar is stronger than "the socket connects": each reference must support a real DSH write in session A, recall from the provider in a fresh DSH session B, and use of the recalled value. At the same time, provider downloads, accounts, models, embeddings, storage initialization, and separate HTTP processes must remain upstream responsibilities. + +## Decision + +Ship three default-off Cordis overlay examples under `examples/mcp-memory`: Memorix, MCP Reference Memory, and Engram. Every file inserts exactly one `@deepseek-ai/dsh-mcp-client` row. None is referenced by the shipped composition, and the CLI declares the generic bridge only so an explicitly selected overlay can resolve it. + +These third-party configurations are provided as interoperability examples only. Their inclusion does not imply endorsement, recommendation, partnership, or ongoing support by DeepSeek. There is no memory preset registry, vendor-specific DSH plugin, universal memory service, installation UI, migration layer, health checker, or reconnect controller. Another memory MCP server uses the same documented stdio or Streamable HTTP row. + +## Responsibility boundary + +| Concern | DSH | Upstream provider or user | +|---|---|---| +| Parse selected overlay | Yes | Select one file | +| Start stdio command and stop it on plugin disposal | Yes | Install the pinned executable | +| Connect to Streamable HTTP and discover tools | Yes | Run and supervise the HTTP service | +| Register tools as `mcp____` | Yes | Define tool schemas and behavior | +| Account, auth, model, embedding, storage initialization | No | Yes | +| Vendor data migration, retry, crash recovery | No | Yes | + +The generic stdio transport scrubs ambient credential-shaped and `DSH_*` variables while inheriting other ambient variables. Baseline examples add only required overrides; optional provider secrets must be added to `config.env` or configured in the provider's own files. + +## Pins, storage, and identity + +| Provider | Tested contract | +|---|---| +| Memorix | npm `1.3.0`, tag commit `500792cad3144142293bfbb20acb4841c9f7fcfa` | +| MCP Reference Memory | npm `2026.7.4`, package commit `6dd0a683e198783e30feabf7abaf42f925bd18b1` | +| Engram | tag `v1.20.0`, commit `ba9e46ced152c37a7cb9e576153c41995873e2fc` | + +Storage remains provider-owned. Memorix uses `~/.memorix/data` and Engram uses `~/.engram` by default. The Reference Memory example sets a stable `$HOME/.dsh-mcp-reference-memory.jsonl` path instead of writing into the installed npm package directory. Each provider's own environment variable can override these locations before DSH starts. + +Project identity remains provider-owned: Memorix and Engram use the DSH working directory's Git project, with Engram optionally accepting `ENGRAM_PROJECT`. + +## Model guidance + +The examples do not patch `@deepseek-ai/dsh-system-prompt`: a config patch replaces a row's complete config and could erase an existing persona. The README instead offers one optional additive instruction: + +> When the user asks you to remember something, call a memory write tool. When historical information may be relevant, search memory and use relevant results. + +Provider tool descriptions remain authoritative. + +## Validation contract + +Remote CI never contacts third-party services or consumes secrets. The keyless suite parses all three overlay files, checks their generic bridge and secret boundary, replaces the upstream endpoint with the package-owned MCP fixture server, boots the real Cordis Loader, and proves tool discovery. + +Before merge, manual evidence for every pinned provider must separately show: + +1. DSH session A calls a write tool and receives success for a unique value. +2. Fresh DSH session B, under the same provider storage scope, calls search or recall and returns that value without session A's transcript. +3. Session B uses the recalled value in a subsequent answer. + +"Fresh session" means a new DSH session in the same Host. No Host restart is required. The generic MCP client discovers asynchronously and has no automatic reconnect after a child or HTTP transport closes; validation waits for tools before the first turn and uses HMR or a Host restart only after a crash. + +## Alternatives considered + +**One DSH plugin per provider.** Rejected because it repeats auth, configuration, lifecycle, and tool wrappers that MCP already standardizes and expands ownership for every added provider. + +**A memory-provider preset registry.** Rejected because a registry would make third-party versions and recommendations look like a supported DSH product surface. Copyable overlays keep ownership and drift visible. + +**Run `npx` or `go run` inside the MCP row.** Rejected after probes showed first-run npm downloads can exceed the MCP initialization timeout and an interrupted `npx` cache can become unusable. DSH starts a server process; it is not the provider package manager. Pinned installation commands are explicit prerequisites. + +**Inject the common instruction from the generic MCP client.** Rejected because the bridge serves non-memory MCP servers too, and generic prompt mutation would reintroduce provider semantics into shared runtime code. + +## Consequences + +Selecting a file gives the model the provider's complete discovered MCP tool surface, with schema/token cost determined by that provider. Removing `--config` removes the memory server. Users accept each upstream license, data policy, cloud cost, and operational model directly. + +The earlier vendor-specific change is superseded by this generic path. Future provider drift is handled by updating and revalidating a small example pin rather than adding runtime branches to DSH. diff --git a/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.zh.md b/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.zh.md new file mode 100644 index 0000000000..ee0e9f2e13 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-07-31-third-party-memory-mcp-examples.zh.md @@ -0,0 +1,78 @@ +# Agent Note: 第三方记忆 MCP 示例 + +Status: implemented + +[English](2026-07-31-third-party-memory-mcp-examples.md) | 中文 + +## 问题 + +直接集成某个提供方会使该提供方的 API、配置、健康状态行为和工具语义成为 DSH 的一部分。对于已经可以通过 MCP 表达的功能,这会让产品接口过于庞大,而且每接入一个记忆系统都需要重复同样的适配工作。用户需要的是一种精简、可检查的方式,在保留通用 MCP 边界的同时,选择启用一个外部记忆服务器。 + +验收标准不止于「套接字可以连接」:每份参考配置都必须支持 DSH 在会话 A 中实际写入,在新的 DSH 会话 B 中从提供方召回,并使用召回的值。与此同时,提供方下载、账户、模型、embedding、存储初始化和独立 HTTP 进程仍由上游负责。 + +## 决策 + +在 `examples/mcp-memory` 下交付三份默认关闭的 Cordis overlay 示例:Memorix、MCP Reference Memory 和 Engram。每个文件只插入一个 `@deepseek-ai/dsh-mcp-client` 配置项。交付组合不会引用这些文件;CLI(命令行界面)仅声明通用桥接器,使用户显式选择 overlay 时可以解析它。 + +这些第三方配置仅作为互操作参考;收录不代表 DeepSeek 的认可、推荐、合作关系或持续支持承诺。系统没有记忆预设注册表、提供方专属 DSH 插件、通用记忆服务、安装 UI、迁移层、健康检查器或重连控制器。其他记忆 MCP 服务器可以使用同一份文档中的 stdio 或 Streamable HTTP 配置项。 + +## 职责边界 + +| 事项 | DSH | 上游提供方或用户 | +|---|---|---| +| 解析选中的 overlay | 是 | 选择一个文件 | +| 启动 stdio 命令,并在插件 dispose(资源释放)时将其停止 | 是 | 安装固定版本的可执行文件 | +| 连接 Streamable HTTP 并发现工具 | 是 | 运行并监管 HTTP 服务 | +| 以 `mcp____` 注册工具 | 是 | 定义工具 schema 和行为 | +| 账户、认证、模型、embedding、存储初始化 | 否 | 是 | +| 提供方数据迁移、重试、崩溃恢复 | 否 | 是 | + +通用 stdio 传输会清除环境中名称类似凭据的变量和 `DSH_*` 变量,同时继承其他环境变量。基线示例仅添加必需的覆盖项;可选的提供方密钥必须添加到 `config.env`,或配置在提供方自己的文件中。 + +## 版本固定、存储与身份 + +| 提供方 | 已测试契约 | +|---|---| +| Memorix | npm `1.3.0`,tag commit `500792cad3144142293bfbb20acb4841c9f7fcfa` | +| MCP Reference Memory | npm `2026.7.4`,package commit `6dd0a683e198783e30feabf7abaf42f925bd18b1` | +| Engram | tag `v1.20.0`,commit `ba9e46ced152c37a7cb9e576153c41995873e2fc` | + +存储仍由提供方负责。Memorix 默认使用 `~/.memorix/data`,Engram 默认使用 `~/.engram`。Reference Memory 示例设置稳定的 `$HOME/.dsh-mcp-reference-memory.jsonl` 路径,而不是写入已安装的 npm 包(package)目录。每个提供方自己的环境变量都可以在 DSH 启动前覆盖这些位置。 + +项目身份仍由提供方负责:Memorix 和 Engram 使用 DSH 工作目录中的 Git 项目,其中 Engram 还可以选择接受 `ENGRAM_PROJECT`。 + +## 模型指导 + +示例不会修改 `@deepseek-ai/dsh-system-prompt`:配置 patch 会替换某个配置项的完整配置,可能抹除已有 persona。README 改为提供一条可选的附加指令: + +> 用户要求记住时调用写入工具;涉及历史信息时,主动检索并使用相关记忆。 + +提供方的工具描述仍然是权威定义。 + +## 验证契约 + +远程 CI 不会访问第三方服务或消耗密钥。无密钥套件解析全部三份 overlay 文件,检查其通用桥接器和密钥边界,将上游端点替换为包自带的 MCP fixture(测试前置数据)服务器,通过真实 Cordis Loader 启动,并验证工具发现。 + +合并前,每个固定版本的提供方都必须分别提供以下人工证据: + +1. DSH 会话 A 调用写入工具,为一个唯一值写入记忆,并收到成功结果。 +2. 新的 DSH 会话 B 在相同的提供方存储范围下调用搜索或召回,不借助会话 A 的 transcript(文本记录)便可返回该值。 +3. 会话 B 在后续回答中使用该召回值。 + +「新会话」是指同一个 Host 中新建的 DSH 会话,不需要重启 Host。通用 MCP 客户端以异步方式发现工具,子进程或 HTTP 传输关闭后不会自动重连;验证会在第一轮之前等待工具出现,并且只在崩溃后使用 HMR 或重启 Host。 + +## 考虑过的替代方案 + +**每个提供方使用一个 DSH 插件。** 不予采纳,因为这会重复 MCP 已经标准化的认证、配置、生命周期和工具包装层,并随着每增加一个提供方而扩大维护范围。 + +**记忆提供方预设注册表。** 不予采纳,因为注册表会让第三方版本和推荐看起来像受支持的 DSH 产品接口。可复制的 overlay 让所有权和版本偏移保持可见。 + +**在 MCP 配置项内运行 `npx` 或 `go run`。** 不予采纳,因为探测表明首次 npm 下载可能超过 MCP 初始化超时,而中断的 `npx` 缓存可能变得不可用。DSH 负责启动服务器进程,不是提供方的包管理器。固定版本的安装命令属于显式前置条件。 + +**由通用 MCP 客户端注入共用指令。** 不予采纳,因为该桥接器也服务于非记忆类 MCP 服务器,而且通用提示词变更会把提供方语义重新带入共享运行时代码。 + +## 后果 + +选择一个文件后,模型可以使用提供方发现到的完整 MCP 工具接口;工具 schema 和 token 成本由提供方决定。移除 `--config` 就会移除记忆服务器。用户直接接受各上游的许可证、数据政策、云服务费用和运维模式。 + +通用方案取代了早期针对特定提供方的改动。未来出现提供方版本偏移时,只需更新并重新验证一份小型示例的固定版本,不必向 DSH 添加运行时分支。 diff --git a/apps/cli/package.json b/apps/cli/package.json index 154403fc0a..997cea8e5f 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -68,6 +68,7 @@ "@deepseek-ai/dsh-llm-deepseek": "workspace:^", "@deepseek-ai/dsh-llm-pi-ai": "workspace:^", "@deepseek-ai/dsh-llm-retry": "workspace:^", + "@deepseek-ai/dsh-mcp-client": "workspace:^", "@deepseek-ai/dsh-paths": "workspace:^", "@deepseek-ai/dsh-permission": "workspace:^", "@deepseek-ai/dsh-plan-mode": "workspace:^", diff --git a/apps/cli/tests/fixtures/memory-mcp-base.cordis.yml b/apps/cli/tests/fixtures/memory-mcp-base.cordis.yml new file mode 100644 index 0000000000..00878d6c98 --- /dev/null +++ b/apps/cli/tests/fixtures/memory-mcp-base.cordis.yml @@ -0,0 +1,8 @@ +# Minimal keyless composition for loading example MCP overlays against the +# package-owned fixture server in memory-mcp-configs.spec.ts. Source builtins +# keep this unit test independent of prebuilt workspace artifacts. +- id: system-prompt + name: cordis:memory-test-system-prompt + +- id: tools + name: cordis:memory-test-tools diff --git a/apps/cli/tests/memory-mcp-configs.spec.ts b/apps/cli/tests/memory-mcp-configs.spec.ts new file mode 100644 index 0000000000..7069b22738 --- /dev/null +++ b/apps/cli/tests/memory-mcp-configs.spec.ts @@ -0,0 +1,132 @@ +/** + * The third-party memory examples stay config-only. This suite parses every + * checked-in overlay, verifies its pin/transport/secret boundary, then replaces + * only the upstream endpoint with the package-owned keyless MCP fixture and + * proves the real Cordis Loader discovers a tool through the generic bridge. + */ + +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import type { Context } from 'cordis' +import type { PatchOptions } from '@cordisjs/plugin-include' +import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot' +import SystemPrompt from '@deepseek-ai/dsh-system-prompt' +import ToolRegistry from '@deepseek-ai/dsh-tools' +import * as McpClient from '@deepseek-ai/dsh-mcp-client/src/index.ts' + +interface ExampleContract { + file: string + id: string + serverName: string + transport: 'stdio' | 'streamable-http' + pin: string +} + +interface InsertedRow { + id?: string + name?: string + config?: Record +} + +const root = resolve(import.meta.dirname, '../../..') +const exampleDir = resolve(root, 'examples/mcp-memory') +const baseConfig = resolve(import.meta.dirname, 'fixtures/memory-mcp-base.cordis.yml') +const fixtureServer = resolve(root, 'packages/mcp/mcp-client/tests/fixture-server.ts') + +const examples: ExampleContract[] = [ + { + file: 'memorix.cordis.yml', + id: 'memory-memorix', + serverName: 'memorix', + transport: 'stdio', + pin: '1.3.0', + }, + { + file: 'mcp-reference-memory.cordis.yml', + id: 'memory-mcp-reference', + serverName: 'reference_memory', + transport: 'stdio', + pin: '2026.7.4', + }, + { + file: 'engram.cordis.yml', + id: 'memory-engram', + serverName: 'engram', + transport: 'stdio', + pin: '1.20.0', + }, +] + +const liveContexts = new Set() + +afterEach(async () => { + await Promise.all([...liveContexts].map(async ctx => ctx.fiber.dispose())) + liveContexts.clear() +}) + +function insertedRow(patches: PatchOptions[]): InsertedRow { + expect(patches).toHaveLength(1) + const insert = patches[0]?.insert + expect(insert).toHaveLength(1) + return insert?.[0] as InsertedRow +} + +async function waitForTool(ctx: Context, name: string): Promise { + const deadline = Date.now() + 10_000 + while (!ctx.tools.schemas().some(schema => schema.name === name)) { + if (Date.now() >= deadline) throw new Error(`timed out waiting for ${name}`) + await new Promise(resolveWait => setTimeout(resolveWait, 25)) + } +} + +describe('third-party memory MCP example overlays', () => { + it.each(examples)('parses $file with the documented generic boundary', (contract) => { + const file = resolve(exampleDir, contract.file) + const source = readFileSync(file, 'utf8') + const row = insertedRow(loadOverlayPatches('memory-mcp-config-test', file)) + + expect(row.id).toBe(contract.id) + expect(row.name).toBe('@deepseek-ai/dsh-mcp-client') + expect(row.config?.serverName).toBe(contract.serverName) + expect(row.config?.transport).toBe(contract.transport) + expect(source.split('\n', 1)[0]).toContain(contract.pin) + expect(source).not.toMatch(/\bsk-[A-Za-z0-9_-]{8,}\b/) + expect(source).not.toContain('DEEPSEEK_API_KEY') + }) + + it.each(examples)('loads $file and discovers a keyless fixture tool', async (contract) => { + const patches = loadOverlayPatches( + 'memory-mcp-config-test', + resolve(exampleDir, contract.file), + ) + // The static config gate verifies the checked-in bare package specifier. + // The unit test maps it to the source module so a clean checkout needs no + // prebuilt `lib/` artifacts before proving the Loader/MCP behavior. + insertedRow(patches).name = 'cordis:memory-test-mcp-client' + const fixturePatch: PatchOptions = { + id: contract.id, + config: { + serverName: contract.serverName, + transport: 'stdio', + command: process.execPath, + args: [fixtureServer], + env: {}, + cwd: root, + toolCallTimeoutMs: 5_000, + }, + } + const ctx = await boot( + 'memory-mcp-config-test', + baseConfig, + [...patches, fixturePatch], + (ctx) => { + liveContexts.add(ctx) + ctx.loader.builtins['memory-test-system-prompt'] = SystemPrompt + ctx.loader.builtins['memory-test-tools'] = ToolRegistry + ctx.loader.builtins['memory-test-mcp-client'] = McpClient + }, + ) + await waitForTool(ctx, `mcp__${contract.serverName}__greet`) + }, 15_000) +}) diff --git a/examples/README.i18n.yaml b/examples/README.i18n.yaml index 1956f0fd5e..ead468e816 100644 --- a/examples/README.i18n.yaml +++ b/examples/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write examples/README.md -README.md: a502f34128da497586d593f64d0ce1c05f68a067 -README.zh.md: e3c111bb6a8f67899b6f434345baa3b47640b7ee +README.md: 64e9804eb69367588e926791039c453b0a9aede9 +README.zh.md: dd26b9e3f35c2d3350da77ce04bd77b4660ced1b diff --git a/examples/README.md b/examples/README.md index a502f34128..64e9804eb6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,6 +4,10 @@ English | [中文](README.zh.md) Runnable demos (not workspaces) that showcase how the harness is wired. Each example is a **thin leaf**: either a `cordis.yml` tree that picks swappable backends and loads one app package, or an **overlay** — a patch list `dsh --config` applies over the shipped composition ([`apps/cli/config/base.cordis.yml`](../apps/cli/config/base.cordis.yml) plus a surface overlay). Bundled compositions live in [`@deepseek-ai/dsh-cli-demo`](../packages/examples/cli-demo), [`@deepseek-ai/dsh-acp-demo`](../packages/examples/acp-demo), and their shared [`@deepseek-ai/dsh-agent-spine-demo`](../packages/examples/agent-spine-demo) bundle; the `dsh` surfaces use flat config trees instead. There is no `start.ts`; the terminal `demo:*` scripts boot through the [`dsh`](../apps/cli/README.md) CLI, and the headless/ACP scripts invoke the `cli-demo`/`acp-demo` bins. +## mcp-memory + +Three default-off reference overlays connect a memory MCP server through the generic MCP client. Pick one file and pass it to `dsh --config`; DSH does not install or configure the upstream memory system. See [mcp-memory/README.md](mcp-memory/README.md) for pinned prerequisites, identity mapping, the shared optional prompt, and the write → fresh-session recall → use verification recipe. + ## headless-agent A non-interactive agent demo that accepts one positional task, runs one complete model/tool turn on the `@deepseek-ai/dsh-cli-demo` app, persists a fresh session, prints `text`, `json`, or `stream-json`, and exits. diff --git a/examples/README.zh.md b/examples/README.zh.md index e3c111bb6a..dd26b9e3f3 100644 --- a/examples/README.zh.md +++ b/examples/README.zh.md @@ -4,6 +4,10 @@ 展示 harness 如何接线的可运行演示(不是 workspace)。每个示例都是一个 **轻量叶节点**:要么是一份选择可替换后端、加载一个应用包(package)的 `cordis.yml` 配置树,要么是一个 **overlay**——由 `dsh --config` 叠加到交付组合([`apps/cli/config/base.cordis.yml`](../apps/cli/config/base.cordis.yml) 加一份 surface overlay)之上的 patch 列表。成组的组合位于 [`@deepseek-ai/dsh-cli-demo`](../packages/examples/cli-demo)、[`@deepseek-ai/dsh-acp-demo`](../packages/examples/acp-demo) 及它们共享的 [`@deepseek-ai/dsh-agent-spine-demo`](../packages/examples/agent-spine-demo) 组合包中;`dsh` 的各 surface 则改用平铺 config tree。没有 `start.ts`;终端 `demo:*` 脚本通过 [`dsh`](../apps/cli/README.md) CLI(命令行界面)启动,无头/ACP(Agent Client Protocol)脚本则调用 `cli-demo`/`acp-demo` bin。 +## mcp-memory + +三份默认关闭的参考 overlay 通过通用 MCP 客户端连接一个记忆 MCP 服务器。选择其中一份文件传给 `dsh --config`;DSH 不负责安装或配置上游记忆系统。版本固定的前置条件、身份映射、可选的共用提示词,以及「写入 → 新会话召回 → 使用」验证流程详见 [mcp-memory/README.md](mcp-memory/README.md)。 + ## headless-agent 非交互式 agent(智能体)演示:接受一个位置任务,在 `@deepseek-ai/dsh-cli-demo` 应用上运行一个完整模型/工具轮次,持久化新会话,打印 `text`、`json` 或 `stream-json`,然后退出。 diff --git a/examples/mcp-memory/README.i18n.yaml b/examples/mcp-memory/README.i18n.yaml new file mode 100644 index 0000000000..def44e65e3 --- /dev/null +++ b/examples/mcp-memory/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write examples/mcp-memory/README.md +README.md: b5dd7ffc4ad248d38e108d9aa28c7c26e0c76913 +README.zh.md: 1249ae40bb344fc81836cb49d71dd5656457b1b3 diff --git a/examples/mcp-memory/README.md b/examples/mcp-memory/README.md new file mode 100644 index 0000000000..b5dd7ffc4a --- /dev/null +++ b/examples/mcp-memory/README.md @@ -0,0 +1,113 @@ +# Third-party memory MCP examples + +English | [中文](README.zh.md) + +These three **default-off reference configurations** connect one memory system to DSH through [`@deepseek-ai/dsh-mcp-client`](../../packages/mcp/mcp-client/README.md). Pick one, or copy the same generic MCP row for another server. + +These third-party configurations are provided as interoperability examples only. Their inclusion does not imply endorsement, recommendation, partnership, or ongoing support by DeepSeek. + +## What DSH does + +DSH parses the selected Cordis overlay, starts a configured stdio command or connects to a configured Streamable HTTP URL, discovers MCP tools, and exposes them as `mcp____`. DSH does **not** download the server, initialize its database, choose its model or embedding provider, create a cloud account, migrate vendor data, or supervise a separate HTTP service. For stdio, the generic client launches and stops the child with the DSH plugin lifecycle; for HTTP, the upstream service must already be running. + +The stdio bridge deliberately removes ambient credential-shaped and `DSH_*` variables before launching a child; other ambient variables remain inherited. Each example adds only the baseline override it needs. If an optional upstream feature needs another secret, add that variable to the row's `config.env` instead of putting the secret directly in YAML. + +## Choose one + +| System | Tested pin | Transport | Upstream prerequisite | +|---|---:|---|---| +| [Memorix](https://github.com/AVIDS2/memorix) | `memorix@1.3.0` (`500792cad3144142293bfbb20acb4841c9f7fcfa`) | stdio | Node 22.18+ and `npm install --global memorix@1.3.0` | +| [MCP Reference Memory](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) | `@modelcontextprotocol/server-memory@2026.7.4` (`6dd0a683e198783e30feabf7abaf42f925bd18b1`) | stdio | `npm install --global @modelcontextprotocol/server-memory@2026.7.4` | +| [Engram](https://github.com/Gentleman-Programming/engram) | `v1.20.0` (`ba9e46ced152c37a7cb9e576153c41995873e2fc`) | stdio | Go 1.25.10+ and `go install github.com/Gentleman-Programming/engram/cmd/engram@v1.20.0`, or the matching release binary | + +## Enable one + +Pass one overlay to DSH: + +```sh +dsh --config "$PWD/examples/mcp-memory/memorix.cordis.yml" +``` + +Replace the filename with `mcp-reference-memory.cordis.yml` or `engram.cordis.yml`. The path may point to a copied file anywhere on disk. No memory server is present in the shipped composition, so omitting `--config` keeps all three disabled. + +Without a repository checkout, download the selected overlay directly: + +```sh +mkdir -p "${DSH_HOME:-$HOME/.dsh}" +curl --fail --location \ + --output "${DSH_HOME:-$HOME/.dsh}/memory.cordis.yml" \ + https://raw.githubusercontent.com/deepseek-harness/deepseek-harness/master/examples/mcp-memory/memorix.cordis.yml +dsh --config "${DSH_HOME:-$HOME/.dsh}/memory.cordis.yml" +``` + +Replace `memorix.cordis.yml` in the URL with either of the other filenames to select it. Review a downloaded overlay before running it: Cordis configuration can contain executable `!!js` expressions. + +To keep the selection in personal configuration, merge the chosen file's single `insert` patch into `$DSH_HOME/config.yaml` (normally `~/.dsh/config.yaml`). Do not copy over an existing file: it may already contain unrelated personal patches. + +## Provider setup + +### Memorix + +```sh +npm install --global memorix@1.3.0 +dsh --config "$PWD/examples/mcp-memory/memorix.cordis.yml" +``` + +Memorix works in local heuristic mode without an LLM or embedding service. Configure optional providers in Memorix's own `~/.memorix/config.toml` or project `memorix.toml`. The example keeps Memorix's Git-project identity from the DSH working directory and uses Memorix's own `~/.memorix/data` default. Set `MEMORIX_DATA_DIR` before starting DSH to override it. + +### MCP Reference Memory + +```sh +npm install --global @modelcontextprotocol/server-memory@2026.7.4 +dsh --config "$PWD/examples/mcp-memory/mcp-reference-memory.cordis.yml" +``` + +This reference server stores a local knowledge graph and exposes entity, relation, observation, read, search, and open tools. It needs no model or embedding service. The example stores its JSONL at `$HOME/.dsh-mcp-reference-memory.jsonl` instead of the installed npm package directory. Set `MEMORY_FILE_PATH` before starting DSH to override it. + +Search is case-insensitive substring matching over entity names, types, and observations, not semantic retrieval. The server does not add embeddings, automatic summarization, conflict resolution, or a forgetting policy. + +### Engram + +```sh +go install github.com/Gentleman-Programming/engram/cmd/engram@v1.20.0 +dsh --config "$PWD/examples/mcp-memory/engram.cordis.yml" +``` + +Engram owns storage and project selection: it uses `~/.engram` by default, detects the Git project from the DSH working directory, and accepts `ENGRAM_DATA_DIR` or `ENGRAM_PROJECT` as ambient overrides. + +## Optional shared model instruction + +Add this short, vendor-neutral instruction to your existing model instructions if the server's tool descriptions do not trigger memory use reliably: + +> When the user asks you to remember something, call a memory write tool. When historical information may be relevant, search memory and use relevant results. + +This is additive guidance only. The examples do not replace DSH's system-prompt persona. + +## Verify write, fresh-session recall, and use + +Use one unique value and keep the provider's storage scope unchanged throughout: + +1. In DSH session A, ask: `Remember that my validation drink is lapsang-.` Confirm the model called the provider's write tool and the tool returned success. +2. Create DSH session B in the same running Host. Do not copy session A's conversation. Ask: `What is my validation drink? Check memory.` Confirm the model called the provider's search or recall tool and returned the value. +3. Still in session B, ask: `Use that preference to suggest one drink for the meeting.` Confirm the answer uses the recalled value. + +A new DSH session is required; a Host restart is not. Restart or HMR is needed only after an MCP child crashes because the current generic client does not auto-reconnect; its tool registrations remain until plugin disposal or a successful re-sync, and calls can fail against the closed transport. Initial discovery is asynchronous, so wait for the provider's `mcp__...` tools before sending the first validation prompt. + +## Bring another MCP server + +Copy the same generic shape and use a unique `id` and `serverName`: + +```yaml +- insert: + - id: memory-my-server + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: my-memory + transport: stdio + command: my-memory-mcp + args: [] + env: {} + cwd: !!js process.cwd() +``` + +For a remote server, use `transport: streamable-http`, `url`, and `headers` instead. Provider-specific installation, identity, authentication, models, embeddings, persistence, and licensing remain the provider's responsibility. diff --git a/examples/mcp-memory/README.zh.md b/examples/mcp-memory/README.zh.md new file mode 100644 index 0000000000..1249ae40bb --- /dev/null +++ b/examples/mcp-memory/README.zh.md @@ -0,0 +1,113 @@ +# 第三方记忆 MCP 示例 + +[English](README.md) | 中文 + +这三份**默认关闭的参考配置** 通过 [`@deepseek-ai/dsh-mcp-client`](../../packages/mcp/mcp-client/README.md) 将一个记忆系统连接到 DSH。请选择其中一份,或复制相同的通用 MCP 配置项来连接其他服务器。 + +这些第三方配置仅作为互操作参考;收录不代表 DeepSeek 的认可、推荐、合作关系或持续支持承诺。 + +## DSH 负责什么 + +DSH 解析选中的 Cordis overlay,启动已配置的 stdio 命令或连接已配置的 Streamable HTTP URL,发现 MCP 工具,并以 `mcp____` 的形式公开这些工具。DSH **不负责** 下载服务器、初始化其数据库、选择模型或 embedding 提供方、创建云端账户、迁移提供方数据,也不监管独立的 HTTP 服务。对于 stdio,通用客户端会随 DSH 插件生命周期启动和停止子进程;对于 HTTP,上游服务必须已经运行。 + +stdio 桥接器在启动子进程前会主动移除环境中名称类似凭据的变量和 `DSH_*` 变量;其余环境变量仍会继承。每份示例仅添加其基线所需的覆盖项。如果某个可选的上游功能还需要其他密钥,请将该变量添加到配置项的 `config.env`,不要把密钥直接写进 YAML。 + +## 选择一个 + +| 系统 | 已测试版本 | 传输方式 | 上游前置条件 | +|---|---:|---|---| +| [Memorix](https://github.com/AVIDS2/memorix) | `memorix@1.3.0`(`500792cad3144142293bfbb20acb4841c9f7fcfa`) | stdio | Node 22.18+,并执行 `npm install --global memorix@1.3.0` | +| [MCP Reference Memory](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) | `@modelcontextprotocol/server-memory@2026.7.4`(`6dd0a683e198783e30feabf7abaf42f925bd18b1`) | stdio | `npm install --global @modelcontextprotocol/server-memory@2026.7.4` | +| [Engram](https://github.com/Gentleman-Programming/engram) | `v1.20.0`(`ba9e46ced152c37a7cb9e576153c41995873e2fc`) | stdio | Go 1.25.10+,并执行 `go install github.com/Gentleman-Programming/engram/cmd/engram@v1.20.0`,或安装匹配的发布版二进制文件 | + +## 启用一个 + +将一份 overlay 传给 DSH: + +```sh +dsh --config "$PWD/examples/mcp-memory/memorix.cordis.yml" +``` + +请将文件名替换为 `mcp-reference-memory.cordis.yml` 或 `engram.cordis.yml`。该路径可以指向磁盘任意位置的一份复制文件。交付组合不包含任何记忆服务器,因此不传 `--config` 就会让这三项全部保持关闭。 + +如果本地没有仓库 checkout,可直接下载所选 overlay: + +```sh +mkdir -p "${DSH_HOME:-$HOME/.dsh}" +curl --fail --location \ + --output "${DSH_HOME:-$HOME/.dsh}/memory.cordis.yml" \ + https://raw.githubusercontent.com/deepseek-harness/deepseek-harness/master/examples/mcp-memory/memorix.cordis.yml +dsh --config "${DSH_HOME:-$HOME/.dsh}/memory.cordis.yml" +``` + +若要选择另外任一配置,请将 URL 中的 `memorix.cordis.yml` 替换为对应文件名。运行下载的 overlay 前,请先审阅其内容:Cordis 配置可以包含可执行的 `!!js` 表达式。 + +如果要把所选配置保存在个人配置中,请将对应文件中的单个 `insert` patch 合并到 `$DSH_HOME/config.yaml`(通常是 `~/.dsh/config.yaml`)。不要覆盖已有文件,其中可能已经包含无关的个人 patch。 + +## 提供方设置 + +### Memorix + +```sh +npm install --global memorix@1.3.0 +dsh --config "$PWD/examples/mcp-memory/memorix.cordis.yml" +``` + +Memorix 无需 LLM(大语言模型)或 embedding 服务,即可在本地启发式模式下运行。请在 Memorix 自己的 `~/.memorix/config.toml` 或项目 `memorix.toml` 中配置可选提供方。该示例沿用 DSH 工作目录中的 Git 项目标识,并使用 Memorix 自身的默认目录 `~/.memorix/data`。若要覆盖该目录,请在启动 DSH 前设置 `MEMORIX_DATA_DIR`。 + +### MCP Reference Memory + +```sh +npm install --global @modelcontextprotocol/server-memory@2026.7.4 +dsh --config "$PWD/examples/mcp-memory/mcp-reference-memory.cordis.yml" +``` + +该参考服务器存储本地知识图谱,并公开实体、关系、观察、读取、搜索和打开工具。它不需要模型或 embedding 服务。该示例将 JSONL 存储在 `$HOME/.dsh-mcp-reference-memory.jsonl`,而不是已安装的 npm 包(package)目录中。若要覆盖该路径,请在启动 DSH 前设置 `MEMORY_FILE_PATH`。 + +搜索只对实体名称、类型和 observation 进行不区分大小写的子字符串匹配,不是语义检索。该服务器不提供 embedding、自动摘要、冲突消解或遗忘策略。 + +### Engram + +```sh +go install github.com/Gentleman-Programming/engram/cmd/engram@v1.20.0 +dsh --config "$PWD/examples/mcp-memory/engram.cordis.yml" +``` + +Engram 负责存储和项目选择:它默认使用 `~/.engram`,从 DSH 工作目录检测 Git 项目,并接受 `ENGRAM_DATA_DIR` 或 `ENGRAM_PROJECT` 作为环境覆盖项。 + +## 可选的共用模型指令 + +如果服务器的工具描述无法可靠触发记忆使用,请将以下简短、与提供方无关的指令添加到你现有的模型指令中: + +> 用户要求记住时调用写入工具;涉及历史信息时,主动检索并使用相关记忆。 + +这只是附加指导。示例不会替换 DSH 系统提示词中的 persona。 + +## 验证写入、新会话召回和使用 + +请在整个过程中使用一个唯一值,并保持提供方的存储范围不变: + +1. 在 DSH 会话 A 中提出:`Remember that my validation drink is lapsang-.`。确认模型调用了提供方的写入工具,并且工具返回成功。 +2. 在同一个仍在运行的 Host 中创建 DSH 会话 B。不要复制会话 A 的对话。提出:`What is my validation drink? Check memory.`。确认模型调用了提供方的搜索或召回工具,并返回该值。 +3. 继续在会话 B 中提出:`Use that preference to suggest one drink for the meeting.`。确认回答使用了召回的值。 + +必须新建 DSH 会话,但不需要重启 Host。只有 MCP 子进程崩溃后才需要重启或执行 HMR(热模块替换),因为当前的通用客户端不会自动重连;其工具注册会一直保留到插件完成资源释放或成功重新同步,针对已关闭传输的调用可能失败。初始发现过程是异步的,因此发送第一条验证提示词前,请等待提供方的 `mcp__...` 工具出现。 + +## 接入其他 MCP 服务器 + +复制相同的通用结构,并使用唯一的 `id` 和 `serverName`: + +```yaml +- insert: + - id: memory-my-server + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: my-memory + transport: stdio + command: my-memory-mcp + args: [] + env: {} + cwd: !!js process.cwd() +``` + +对于远程服务器,请改用 `transport: streamable-http`、`url` 和 `headers`。提供方专属的安装、身份、认证、模型、embedding、持久化和许可仍由提供方负责。 diff --git a/examples/mcp-memory/engram.cordis.yml b/examples/mcp-memory/engram.cordis.yml new file mode 100644 index 0000000000..018df88b6f --- /dev/null +++ b/examples/mcp-memory/engram.cordis.yml @@ -0,0 +1,11 @@ +# Opt-in reference for Engram 1.20.0. Install the pinned `engram` executable +# first; storage and project selection remain Engram-owned. +- insert: + - id: memory-engram + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: engram + transport: stdio + command: engram + args: [mcp] + cwd: !!js process.cwd() diff --git a/examples/mcp-memory/mcp-reference-memory.cordis.yml b/examples/mcp-memory/mcp-reference-memory.cordis.yml new file mode 100644 index 0000000000..d89fbe2673 --- /dev/null +++ b/examples/mcp-memory/mcp-reference-memory.cordis.yml @@ -0,0 +1,13 @@ +# Opt-in reference for @modelcontextprotocol/server-memory 2026.7.4. Install +# the pinned executable first; DSH starts it but does not run a package manager. +- insert: + - id: memory-mcp-reference + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: reference_memory + transport: stdio + command: mcp-server-memory + cwd: !!js process.cwd() + env: + MEMORY_FILE_PATH: !!js >- + process.env.MEMORY_FILE_PATH?.trim() || process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:os').homedir(), '.dsh-mcp-reference-memory.jsonl') diff --git a/examples/mcp-memory/memorix.cordis.yml b/examples/mcp-memory/memorix.cordis.yml new file mode 100644 index 0000000000..c993581eae --- /dev/null +++ b/examples/mcp-memory/memorix.cordis.yml @@ -0,0 +1,11 @@ +# Opt-in reference for Memorix 1.3.0. Install the pinned `memorix` executable +# first; DSH starts it but does not run a package manager. +- insert: + - id: memory-memorix + name: '@deepseek-ai/dsh-mcp-client' + config: + serverName: memorix + transport: stdio + command: memorix + args: [serve] + cwd: !!js process.cwd() diff --git a/packages/mcp/mcp-client/README.i18n.yaml b/packages/mcp/mcp-client/README.i18n.yaml index 487742d907..9fa4504cca 100644 --- a/packages/mcp/mcp-client/README.i18n.yaml +++ b/packages/mcp/mcp-client/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/mcp/mcp-client/README.md -README.md: 82d974635cb35878d6f0365b1aa7a9745436240e -README.zh.md: e687c620158955f954dcfebf682503225ee710de +README.md: d7966595c68ff1ec4a288caf5d9fe4b0bf580cc5 +README.zh.md: eb9e0dbdb48423cc4bc698fda355e973e42bc7a3 diff --git a/packages/mcp/mcp-client/README.md b/packages/mcp/mcp-client/README.md index 82d974635c..d7966595c6 100644 --- a/packages/mcp/mcp-client/README.md +++ b/packages/mcp/mcp-client/README.md @@ -61,7 +61,7 @@ Every MCP tool has two names: the raw MCP name (sent on the wire in `tools/call` - Tool execute: `client.callTool({ name: rawName, arguments }, { signal })` with timeout + abort support—the public name is never sent to the server. - Canonical success is `{ content: JsonValue[], structuredContent? }`; complete JSON MCP blocks survive for programmatic callers. A supported advertised `outputSchema` validates `structuredContent`; unsupported schema vocabulary falls back to unconstrained `JsonValue`. - Native/model rendering keeps the existing text projection: text blocks join with newlines while image, audio, resource, and unsupported blocks become placeholders. -- On disconnect/crash: all tools are unregistered; no auto-reconnect. +- On disconnect/crash: no auto-reconnect. Registered tools remain until plugin disposal or a successful re-sync, and calls can fail against the closed transport; reload with HMR or restart the Host to reconnect. ## Services consumed @@ -103,6 +103,6 @@ Append-only; newly visible content follows the reusable request prefix and does - **Initial discovery is asynchronous** — plugin load does not wait for connection and `listTools()`, so a turn started immediately after boot or HMR can assemble before the MCP tools are registered. - **Tools are the only bridged MCP capability** — Resources and Prompts have no harness consumption surface and are deferred. -- **Crash recovery is manual** — transport closure unregisters the server's tools, but reconnect requires an HMR reload or harness restart. +- **Crash recovery is manual** — transport closure does not auto-reconnect; registered tools can remain visible but fail against the closed transport until an HMR reload or Host restart. - **Native non-text rendering is lossy** — image, audio, and resource payloads become placeholders in model context even though the execution-local canonical value preserves their JSON blocks. Richer Native multimedia projection is deferred. - **Unsupported MCP output schemas are not enforced** — `structuredContent` falls back to `JsonValue` when the advertised schema uses vocabulary outside the harness subset. diff --git a/packages/mcp/mcp-client/README.zh.md b/packages/mcp/mcp-client/README.zh.md index e687c62015..eb9e0dbdb4 100644 --- a/packages/mcp/mcp-client/README.zh.md +++ b/packages/mcp/mcp-client/README.zh.md @@ -61,7 +61,7 @@ MCP 客户端桥接插件:连接外部 [Model Context Protocol](https://modelc - 工具执行:`client.callTool({ name: rawName, arguments }, { signal })`,支持超时 + 中止;公开名称绝不会发给服务器。 - 规范成功值是 `{ content: JsonValue[], structuredContent? }`;完整的 JSON MCP 块会保留给编程调用方。受支持且已声明的 `outputSchema` 会验证 `structuredContent`;不受支持的 schema 词汇会回退为不受约束的 `JsonValue`。 - Native/模型渲染保留现有文本投影:文本块以换行连接,图片、音频、资源和不受支持的块会变成占位符。 -- 断开/崩溃时:注销所有工具;不自动重新连接。 +- 断开/崩溃时:不自动重新连接。已注册工具会一直保留到插件完成资源释放或成功重新同步,针对已关闭传输的调用可能失败;请通过 HMR 重新加载或重启 Host 来重新连接。 ## 消费的服务 @@ -103,6 +103,6 @@ MCP 客户端桥接插件:连接外部 [Model Context Protocol](https://modelc - **初始发现是异步的**:插件加载不会等待连接和 `listTools()`,因此在启动或 HMR 后立即开始的轮次可能在 MCP 工具注册前完成组装。 - **只桥接 MCP 的工具能力**:资源和提示词没有 harness 消费接口,暂缓实现。 -- **崩溃恢复需要手动触发**:传输关闭会注销服务器工具,但重新连接需要 HMR 重载或重启 harness。 +- **崩溃恢复需要手动触发**:传输关闭后不会自动重新连接;已注册工具可能仍然可见,但会因传输已关闭而调用失败,直到 HMR 重载或重启 Host。 - **Native 非文本渲染有损**:图片、音频与资源载荷在模型上下文中会变成占位符,即使执行局部的规范值保留了其 JSON 块。更丰富的 Native 多媒体投影暂缓实现。 - **不强制执行不受支持的 MCP 输出 schema**:已声明 schema 使用 harness 子集之外的词汇时,`structuredContent` 会回退到 `JsonValue`。 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c94f70a0ce..d75e8918dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -288,6 +288,9 @@ importers: '@deepseek-ai/dsh-llm-retry': specifier: workspace:^ version: link:../../packages/llm/llm-retry + '@deepseek-ai/dsh-mcp-client': + specifier: workspace:^ + version: link:../../packages/mcp/mcp-client '@deepseek-ai/dsh-paths': specifier: workspace:^ version: link:../../packages/util/paths diff --git a/scripts/verify-cordis-config.ts b/scripts/verify-cordis-config.ts index 582da9dc65..bdb020a6a0 100644 --- a/scripts/verify-cordis-config.ts +++ b/scripts/verify-cordis-config.ts @@ -31,7 +31,10 @@ interface PluginReference { const root = resolve(import.meta.dirname, '..') // These example files are overlays consumed by the built dsh app, so their bare // specifiers resolve from apps/cli rather than the examples workspace. -const appOverlayFiles = new Set(['examples/web-cordis/cordis.yml']) +const appOverlayFiles = new Set([ + 'examples/web-cordis/cordis.yml', + ...globSync('examples/mcp-memory/*.cordis.yml', { cwd: root }), +]) const metadataFields = ['id', 'name', 'group', 'disabled', 'inject', 'intercept', 'isolate'] as const /** The adaptive directory-picker chooser package (mounts a backend row at boot). */