Merge pull request #1070 from deepseek-harness/agent/memory-mcp-examples
feat(examples): add generic MCP memory examples
This commit is contained in:
@@ -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:^",
|
||||
|
||||
8
apps/cli/tests/fixtures/memory-mcp-base.cordis.yml
vendored
Normal file
8
apps/cli/tests/fixtures/memory-mcp-base.cordis.yml
vendored
Normal file
@@ -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
|
||||
132
apps/cli/tests/memory-mcp-configs.spec.ts
Normal file
132
apps/cli/tests/memory-mcp-configs.spec.ts
Normal file
@@ -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<string, unknown>
|
||||
}
|
||||
|
||||
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<Context>()
|
||||
|
||||
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<void> {
|
||||
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)
|
||||
})
|
||||
Reference in New Issue
Block a user