runtime.spec.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { describe, expect, it, vi } from "vitest"
  2. import { buildMcpRuntime, defaultUnavailableMcpCaller } from "./runtime"
  3. import type { McpConfig } from "./config"
  4. import type { McpToolDescriptor } from "./types"
  5. function descriptor(operation: McpToolDescriptor["operation"], name = "query_graph"): McpToolDescriptor {
  6. return {
  7. serverId: "graph",
  8. serverName: "Knowledge Graph",
  9. name,
  10. description: "Query graph",
  11. operation,
  12. inputSchema: {
  13. type: "object",
  14. properties: { query: { type: "string", description: "Query" } },
  15. required: ["query"],
  16. },
  17. }
  18. }
  19. describe("buildMcpRuntime", () => {
  20. it("ignores disabled servers", () => {
  21. const config: McpConfig = {
  22. servers: [{ id: "graph", name: "Graph", enabled: false, tools: [descriptor("read")] }],
  23. }
  24. const runtime = buildMcpRuntime(config)
  25. expect(runtime.mcpTools).toEqual([])
  26. expect(runtime.mcpCapabilities).toEqual([])
  27. expect(runtime.warnings).toEqual([])
  28. })
  29. it("builds tools and capabilities for enabled read and analysis MCP descriptors", () => {
  30. const config: McpConfig = {
  31. servers: [{
  32. id: "graph",
  33. name: "Graph",
  34. enabled: true,
  35. tools: [descriptor("read"), descriptor("analysis", "analyze_relation")],
  36. }],
  37. }
  38. const runtime = buildMcpRuntime(config)
  39. expect(runtime.mcpTools.map((tool) => tool.name)).toEqual([
  40. "mcp_graph_query_graph",
  41. "mcp_graph_analyze_relation",
  42. ])
  43. expect(runtime.mcpCapabilities).toContainEqual(expect.objectContaining({
  44. kind: "mcp_tool",
  45. toolName: "mcp_graph_query_graph",
  46. source: "mcp",
  47. }))
  48. expect(runtime.mcpCapabilities).toContainEqual(expect.objectContaining({
  49. kind: "mcp_tool",
  50. toolName: "mcp_graph_analyze_relation",
  51. source: "mcp",
  52. }))
  53. })
  54. it("rejects destructive and unsupported MCP descriptors with Chinese warnings", () => {
  55. const badSchema: McpToolDescriptor = {
  56. ...descriptor("read", "bad"),
  57. inputSchema: {
  58. type: "object",
  59. properties: { cb: { type: "function" } },
  60. },
  61. }
  62. const config: McpConfig = {
  63. servers: [{
  64. id: "graph",
  65. name: "Graph",
  66. enabled: true,
  67. tools: [descriptor("delete"), descriptor("overwrite"), badSchema],
  68. }],
  69. }
  70. const runtime = buildMcpRuntime(config)
  71. expect(runtime.mcpTools).toEqual([])
  72. expect(runtime.mcpCapabilities).toEqual([])
  73. expect(runtime.warnings.join("\n")).toContain("MCP")
  74. expect(runtime.warnings.join("\n")).toContain("未启用")
  75. })
  76. it("default caller returns Chinese unavailable degradation", async () => {
  77. const result = await defaultUnavailableMcpCaller({
  78. serverId: "graph",
  79. serverName: "Knowledge Graph",
  80. toolName: "query_graph",
  81. qmaiToolName: "mcp_graph_query_graph",
  82. }, { query: "主角关系" })
  83. expect(result.status).toBe("error")
  84. expect(result.message).toContain("MCP")
  85. expect(result.message).toContain("未连接")
  86. })
  87. it("server 配置 command 且提供 realConnector 时使用真实 caller", async () => {
  88. const realCaller = vi.fn(async () => ({
  89. status: "ok" as const,
  90. content: "真实结果",
  91. summary: "真实结果",
  92. }))
  93. const config: McpConfig = {
  94. servers: [{
  95. id: "graph",
  96. name: "Graph",
  97. enabled: true,
  98. command: "node",
  99. tools: [descriptor("read")],
  100. }],
  101. }
  102. const runtime = buildMcpRuntime(config, defaultUnavailableMcpCaller, { caller: realCaller } as any)
  103. await runtime.mcpTools[0].execute({ query: "主角" })
  104. expect(realCaller).toHaveBeenCalledWith(
  105. expect.objectContaining({ serverId: "graph", toolName: "query_graph" }),
  106. { query: "主角" },
  107. undefined,
  108. )
  109. })
  110. })