server-context.spec.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. import { afterEach, describe, expect, it } from 'vitest'
  2. import { Context } from '@deepseek-ai/cordis'
  3. import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
  4. import ToolRuntime from '@deepseek-ai/dsh-tools'
  5. import { ToolCallId } from '@deepseek-ai/dsh-llm'
  6. import McpResources from '@deepseek-ai/dsh-mcp-resources'
  7. import { createScope } from '@deepseek-ai/dsh-scope'
  8. import { registerServerContext } from '../src/server-context.ts'
  9. const roots: Context[] = []
  10. afterEach(async () => { await Promise.all(roots.splice(0).map(ctx => ctx.fiber.dispose())) })
  11. async function setup() {
  12. const ctx = new Context()
  13. roots.push(ctx)
  14. await ctx.plugin(SystemPrompt)
  15. await ctx.plugin(ToolRuntime)
  16. await ctx.plugin(McpResources)
  17. return ctx
  18. }
  19. describe('MCP server context', () => {
  20. it('publishes literal instructions and withdraws the prompt and resource provider together', async () => {
  21. const ctx = await setup()
  22. let instructions = 'MCP server: docs\nKeep {{server.template}} literal.'
  23. const fiber = await ctx.plugin({ apply(inner: Context) {
  24. registerServerContext(inner, 'docs', {
  25. resources: { request: async () => ({ resources: [] }) },
  26. instructions: () => instructions,
  27. })
  28. } })
  29. expect(renderPrompt(await ctx.systemPrompt.assemble())).toContain(instructions)
  30. instructions = 'MCP server: docs\nUpdated instructions.'
  31. expect(renderPrompt(await ctx.systemPrompt.assemble())).toContain(instructions)
  32. await fiber.dispose()
  33. expect(renderPrompt(await ctx.systemPrompt.assemble())).not.toContain('MCP server: docs')
  34. const result = await ctx.tools.execute({
  35. name: 'list_mcp_resources', arguments: { server: 'docs' },
  36. callId: ToolCallId('disposed-resource'), signal: new AbortController().signal,
  37. })
  38. expect(result.isError).toBe(true)
  39. })
  40. it('shows scoped server instructions only to the owning scope', async () => {
  41. const ctx = await setup()
  42. const scopeKey = {}
  43. await ctx.plugin({ apply(inner: Context) {
  44. const scoped = createScope(inner, scopeKey)
  45. registerServerContext(scoped.ctx, 'private', {
  46. resources: { request: async () => ({ resources: [] }) },
  47. instructions: () => 'Private server instructions.',
  48. })
  49. } })
  50. expect(renderPrompt(await ctx.systemPrompt.assemble({ scope: scopeKey }))).toContain('Private server instructions.')
  51. expect(renderPrompt(await ctx.systemPrompt.assemble())).not.toContain('Private server instructions.')
  52. })
  53. })