|
|
@@ -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)
|
|
|
+})
|