976bde37cc
与 REST/OpenAPI 并存,按实例权限自动 tools/list,免手填工具。 Co-authored-by: Cursor <cursoragent@cursor.com>
64 lines
2.8 KiB
TypeScript
64 lines
2.8 KiB
TypeScript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
import http from 'node:http';
|
|
import type { AddressInfo } from 'node:net';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { createWecomPluginMcpServer, WecomPluginMcpFactory } from './wecom-plugin-mcp.factory';
|
|
import type { WecomPluginQueryService } from './wecom-plugin-query.service';
|
|
import type { WecomPluginRuntime } from './wecom-plugin.types';
|
|
|
|
const plugin: WecomPluginRuntime = {
|
|
id: '9',
|
|
name: 'ops-readonly',
|
|
permissions: ['order.read', 'metrics.read'],
|
|
};
|
|
|
|
async function listen(server: http.Server): Promise<string> {
|
|
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
const { port } = server.address() as AddressInfo;
|
|
return `http://127.0.0.1:${port}/mcp`;
|
|
}
|
|
|
|
describe('WecomPluginMcpFactory Streamable HTTP', () => {
|
|
it('lists only permitted tools and calls query_orders', async () => {
|
|
const query = {
|
|
queryOrders: vi.fn().mockResolvedValue({ total: 1, items: [{ orderNo: 'DK1' }] }),
|
|
} as unknown as WecomPluginQueryService;
|
|
const factory = new WecomPluginMcpFactory(query);
|
|
const httpServer = http.createServer((req, res) => {
|
|
const chunks: Buffer[] = [];
|
|
req.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
req.on('end', () => {
|
|
const raw = Buffer.concat(chunks).toString('utf8');
|
|
(req as http.IncomingMessage & { body?: unknown }).body = raw ? JSON.parse(raw) : undefined;
|
|
void factory.handle(req, res, plugin);
|
|
});
|
|
});
|
|
|
|
const url = await listen(httpServer);
|
|
const client = new Client({ name: 'wecom-plugin-test', version: '1.0.0' });
|
|
const transport = new StreamableHTTPClientTransport(new URL(url));
|
|
try {
|
|
await client.connect(transport);
|
|
const listed = await client.listTools();
|
|
expect(listed.tools.map((t) => t.name).sort()).toEqual(['query_metrics', 'query_orders']);
|
|
const called = await client.callTool({ name: 'query_orders', arguments: { q: 'DK' } });
|
|
expect(called.isError).toBeFalsy();
|
|
const text = called.content.find((c) => c.type === 'text');
|
|
expect(text && 'text' in text ? JSON.parse(text.text) : null).toEqual({
|
|
total: 1,
|
|
items: [{ orderNo: 'DK1' }],
|
|
});
|
|
} finally {
|
|
await client.close().catch(() => undefined);
|
|
await new Promise<void>((resolve, reject) => httpServer.close((err) => (err ? reject(err) : resolve())));
|
|
}
|
|
});
|
|
|
|
it('createWecomPluginMcpServer does not register unauthorized tools', () => {
|
|
const query = {} as WecomPluginQueryService;
|
|
const server = createWecomPluginMcpServer(query, plugin, 'alice');
|
|
expect(server).toBeTruthy();
|
|
});
|
|
});
|