import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import createServer from "../src/server/smithery.js";
import { getServerContext } from "../src/mcp/server.js";
import { mcpConfigSchema, smitheryConfigSchema } from "../src/schema/mcpConfig.js";
import { invokeInitialize } from "./helpers/server.js";
const ORIGINAL_ENV = { ...process.env };
beforeEach(() => {
process.env = { ...ORIGINAL_ENV };
// Clear all ClickUp-related env vars to simulate Smithery scanner
delete process.env.CLICKUP_TOKEN;
delete process.env.CLICKUP_DEFAULT_TEAM_ID;
delete process.env.CLICKUP_PRIMARY_LANGUAGE;
delete process.env.CLICKUP_BASE_URL;
delete process.env.CLICKUP_AUTH_SCHEME;
delete process.env.REQUEST_TIMEOUT_MS;
delete process.env.DEFAULT_HEADERS_JSON;
});
afterEach(() => {
process.env = { ...ORIGINAL_ENV };
});
describe("mcpConfigSchema - Smithery scanner compatibility", () => {
it("schema does not require apiToken or defaultTeamId", () => {
// Verify that the schema itself doesn't have required fields
expect(mcpConfigSchema).toBeDefined();
expect(mcpConfigSchema.type).toBe("object");
// The schema should not have a 'required' property or it should be empty
if ("required" in mcpConfigSchema) {
expect(mcpConfigSchema.required).toBeUndefined();
}
// Verify properties are defined
expect(mcpConfigSchema.properties).toBeDefined();
expect(mcpConfigSchema.properties.apiToken).toBeDefined();
expect(mcpConfigSchema.properties.defaultTeamId).toBeDefined();
});
it("server initializes successfully with empty config (Smithery scanner)", async () => {
// Simulate exactly what Smithery's scanner does: sends empty config {}
const server = await createServer({
config: {},
env: {},
auth: {}
});
try {
// This should NOT throw or return a 500 error
const response = await invokeInitialize(server);
// Verify successful initialization
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
const context = getServerContext(server);
expect(context).toBeDefined();
expect(context.tools.length).toBeGreaterThan(0);
// Session should exist with default values
expect(context.session).toBeDefined();
expect(context.session.apiToken).toBe("");
expect(context.session.defaultTeamId).toBeUndefined();
expect(context.session.baseUrl).toBe("https://api.clickup.com/api/v2");
} finally {
await server.close();
}
});
it("server initializes with only apiToken provided", async () => {
const server = await createServer({
config: {
apiToken: "pk_test_token"
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("pk_test_token");
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await server.close();
}
});
it("server initializes with only defaultTeamId provided", async () => {
const server = await createServer({
config: {
defaultTeamId: 12345
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("");
expect(context.session.defaultTeamId).toBe(12345);
} finally {
await server.close();
}
});
it("server initializes with no config, no env, no auth", async () => {
// The most extreme case: absolutely nothing provided
const server = await createServer({});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
const context = getServerContext(server);
expect(context.tools.length).toBeGreaterThan(0);
} finally {
await server.close();
}
});
});
describe("smitheryConfigSchema transformations", () => {
it("normalises numeric and string inputs", () => {
const parsed = smitheryConfigSchema.parse({
apiToken: 123,
defaultTeamId: "42",
requestTimeoutMs: "5000",
primaryLanguage: 456,
baseUrl: 789,
defaultHeadersJson: 321,
authScheme: "personal_token",
allowTools: ["alpha", "beta"],
denyTools: "gamma"
});
expect(parsed.apiToken).toBeUndefined();
expect(parsed.defaultTeamId).toBe(42);
expect(parsed.requestTimeoutMs).toBe(5000);
expect(parsed.primaryLanguage).toBeUndefined();
expect(parsed.baseUrl).toBeUndefined();
expect(parsed.defaultHeadersJson).toBeUndefined();
expect(parsed.authScheme).toBe("personal_token");
expect(parsed.allowTools).toEqual(["alpha", "beta"]);
expect(parsed.denyTools).toBe("gamma");
});
it("drops invalid enum values", () => {
const parsed = smitheryConfigSchema.parse({ authScheme: "invalid" });
expect(parsed.authScheme).toBeUndefined();
});
});