File size: 3,155 Bytes
5c2a829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { describe, expect, it, vi } from "vitest";
import type { AnyAgentTool } from "../agents/tools/common.js";
import { createPluginToolsMcpServer } from "./plugin-tools-serve.js";

describe("plugin tools MCP client bridge", () => {
  it("lists and calls a plugin tool through a real MCP client", async () => {
    const execute = vi.fn().mockResolvedValue({
      content: [{ type: "text", text: "MCP fact: the codename is ORBIT-9." }],
    });
    const tool = {
      name: "memory_search",
      description: "Search memory",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string" },
          maxResults: { type: "number" },
        },
        required: ["query"],
      },
      execute,
    } as unknown as AnyAgentTool;

    const server = createPluginToolsMcpServer({
      tools: [tool],
    });
    const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
    const client = new Client(
      { name: "plugin-tools-test-client", version: "0.0.0" },
      { capabilities: {} },
    );

    await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);

    try {
      const listed = await client.listTools();
      expect(listed.tools.map((listedTool) => listedTool.name)).toContain("memory_search");

      const result = await client.callTool({
        name: "memory_search",
        arguments: { query: "ORBIT-9 codename", maxResults: 3 },
      });

      expect(execute).toHaveBeenCalledWith(
        expect.stringMatching(
          /^mcp-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u,
        ),
        { query: "ORBIT-9 codename", maxResults: 3 },
        expect.any(AbortSignal),
        undefined,
      );
      expect(JSON.stringify(result.content)).toContain("ORBIT-9");
    } finally {
      await client.close();
      await server.close();
    }
  });

  it("delivers resolved tool failures as MCP errors", async () => {
    const content = [{ type: "text", text: "backend unavailable" }];
    const tool = {
      name: "memory_search",
      description: "Search memory",
      parameters: { type: "object", properties: {} },
      execute: vi.fn().mockResolvedValue({
        content,
        details: { status: "failed", error: "backend unavailable" },
      }),
    } as unknown as AnyAgentTool;
    const server = createPluginToolsMcpServer({
      tools: [tool],
    });
    const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
    const client = new Client(
      { name: "plugin-tools-error-test-client", version: "0.0.0" },
      { capabilities: {} },
    );

    await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
    try {
      const result = await client.callTool({ name: "memory_search", arguments: {} });

      expect(result.content).toEqual(content);
      expect(result.isError).toBe(true);
    } finally {
      await client.close();
      await server.close();
    }
  });
});