98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
import type { CrmService } from "../src/crm-service.js";
|
|
import type { ProjectService } from "../src/project-service.js";
|
|
import { createServer } from "../src/tools.js";
|
|
|
|
function fakeService(): CrmService {
|
|
return new Proxy(
|
|
{},
|
|
{
|
|
get:
|
|
(_target, property) =>
|
|
async (...args: unknown[]) => ({ operation: String(property), args }),
|
|
},
|
|
) as CrmService;
|
|
}
|
|
|
|
async function connected(readonly: boolean): Promise<{
|
|
client: Client;
|
|
close: () => Promise<void>;
|
|
}> {
|
|
const server = createServer({
|
|
crmService: fakeService(),
|
|
projectService: fakeService() as unknown as ProjectService,
|
|
readonly,
|
|
});
|
|
const client = new Client({ name: "test", version: "1" });
|
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
return {
|
|
client,
|
|
close: async () => await Promise.all([client.close(), server.close()]).then(() => undefined),
|
|
};
|
|
}
|
|
|
|
test("read-only server exposes the stable 17-tool contract", async () => {
|
|
const connection = await connected(true);
|
|
try {
|
|
const result = await connection.client.listTools();
|
|
assert.deepEqual(
|
|
new Set(result.tools.map((tool) => tool.name)),
|
|
new Set([
|
|
"crm_health",
|
|
"crm_schema",
|
|
"crm_overview",
|
|
"crm_filter_options",
|
|
"resolve_owner",
|
|
"list_teams",
|
|
"resolve_team",
|
|
"get_team",
|
|
"team_crm_overview",
|
|
"list_customers",
|
|
"get_customer",
|
|
"list_contacts",
|
|
"list_followups",
|
|
"list_projects",
|
|
"get_project",
|
|
"get_project_timeline",
|
|
"list_stale_customers",
|
|
]),
|
|
);
|
|
} finally {
|
|
await connection.close();
|
|
}
|
|
});
|
|
|
|
test("write mode adds only fixed project create and update tools", async () => {
|
|
const connection = await connected(false);
|
|
try {
|
|
const names = (await connection.client.listTools()).tools.map((tool) => tool.name);
|
|
assert.ok(names.includes("create_project"));
|
|
assert.ok(names.includes("update_project"));
|
|
assert.ok(!names.some((name) => name.includes("destroy") || name.includes("delete")));
|
|
} finally {
|
|
await connection.close();
|
|
}
|
|
});
|
|
|
|
test("raw rows require explicit local policy opt-in", async () => {
|
|
const previous = process.env.NOCOBASE_MCP_ENABLE_RAW_DATA;
|
|
delete process.env.NOCOBASE_MCP_ENABLE_RAW_DATA;
|
|
const connection = await connected(true);
|
|
try {
|
|
const result = await connection.client.callTool({
|
|
name: "list_customers",
|
|
arguments: { include_raw: true },
|
|
});
|
|
const structured = result.structuredContent as { error: Record<string, unknown> };
|
|
assert.equal(structured.error.code, "raw_data_disabled");
|
|
} finally {
|
|
if (previous === undefined) delete process.env.NOCOBASE_MCP_ENABLE_RAW_DATA;
|
|
else process.env.NOCOBASE_MCP_ENABLE_RAW_DATA = previous;
|
|
await connection.close();
|
|
}
|
|
});
|