import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { InitializeRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import createServer from "../src/server/smithery.js";
import { getServerContext } from "../src/mcp/server.js";
const ORIGINAL_ENV = { ...process.env };
async function invokeInitialize(server: Server): Promise<unknown> {
const handlers = (server as unknown as {
_requestHandlers?: Map<string, unknown>;
})._requestHandlers;
const handler = handlers?.get("initialize");
if (typeof handler !== "function") {
throw new Error("Initialize handler not registered");
}
const request = InitializeRequestSchema.parse({
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: "2024-05-01",
capabilities: {},
clientInfo: { name: "test-suite", version: "1.0.0" }
}
});
const abortController = new AbortController();
return (handler as (
request: typeof request,
extra: {
signal: AbortSignal;
requestId: typeof request.id;
sendNotification: (notification: unknown) => Promise<void>;
sendRequest: (request: unknown) => Promise<unknown>;
}
) => Promise<unknown>)(request, {
signal: abortController.signal,
requestId: request.id,
sendNotification: async () => {},
sendRequest: async () => {
throw new Error("sendRequest is not supported in tests");
}
});
}
beforeEach(() => {
process.env = { ...ORIGINAL_ENV };
// Clear all ClickUp-related env vars
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("config initialization - Python parity", () => {
it("initializes successfully with completely empty config and env", async () => {
const server = await createServer({});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
const context = getServerContext(server);
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");
expect(context.session.authScheme).toBe("auto");
expect(context.tools.length).toBeGreaterThan(0);
} finally {
await server.close();
}
});
it("initializes successfully with only apiToken in config", 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("initializes successfully with only defaultTeamId in config", 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("initializes successfully with empty strings in config", async () => {
const server = await createServer({
config: {
apiToken: "",
primaryLanguage: "",
baseUrl: "",
defaultHeadersJson: ""
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("");
expect(context.session.baseUrl).toBe("https://api.clickup.com/api/v2");
} finally {
await server.close();
}
});
it("initializes successfully with zero/negative values in config", async () => {
const server = await createServer({
config: {
defaultTeamId: 0,
requestTimeoutMs: -100
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.defaultTeamId).toBe(0);
// Negative timeout should be normalized to default
expect(context.session.requestTimeout).toBe(30);
} finally {
await server.close();
}
});
it("initializes successfully with only environment variables", async () => {
process.env.CLICKUP_TOKEN = "env_token";
process.env.CLICKUP_DEFAULT_TEAM_ID = "99999";
process.env.CLICKUP_PRIMARY_LANGUAGE = "fr-FR";
const server = await createServer({});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("env_token");
expect(context.session.defaultTeamId).toBe(99999);
} finally {
await server.close();
}
});
it("initializes successfully with invalid environment variables", async () => {
process.env.CLICKUP_TOKEN = "valid_token";
process.env.CLICKUP_DEFAULT_TEAM_ID = "not-a-number";
process.env.REQUEST_TIMEOUT_MS = "invalid";
const server = await createServer({});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("valid_token");
// Invalid team ID should be undefined
expect(context.session.defaultTeamId).toBeUndefined();
// Invalid timeout should default to 30
expect(context.session.requestTimeout).toBe(30);
} finally {
await server.close();
}
});
it("config values override environment variables", async () => {
process.env.CLICKUP_TOKEN = "env_token";
process.env.CLICKUP_DEFAULT_TEAM_ID = "11111";
const server = await createServer({
config: {
apiToken: "config_token",
defaultTeamId: 22222
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("config_token");
expect(context.session.defaultTeamId).toBe(22222);
} finally {
await server.close();
}
});
it("partial config merges with environment fallback", async () => {
process.env.CLICKUP_TOKEN = "env_token";
process.env.CLICKUP_DEFAULT_TEAM_ID = "33333";
const server = await createServer({
config: {
primaryLanguage: "de-DE",
baseUrl: "https://custom.api.com"
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Should use env values for missing config
expect(context.session.apiToken).toBe("env_token");
expect(context.session.defaultTeamId).toBe(33333);
// Should use config values
expect(context.session.baseUrl).toContain("custom.api.com");
} finally {
await server.close();
}
});
it("auth context is merged with environment and config", async () => {
process.env.CLICKUP_DEFAULT_TEAM_ID = "44444";
const server = await createServer({
auth: {
CLICKUP_TOKEN: "auth_token"
},
config: {
primaryLanguage: "es-ES"
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("auth_token");
expect(context.session.defaultTeamId).toBe(44444);
} finally {
await server.close();
}
});
it("all optional config fields can be omitted without error", async () => {
const server = await createServer({
config: {}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
const context = getServerContext(server);
expect(context.session).toBeDefined();
expect(context.tools.length).toBeGreaterThan(0);
} finally {
await server.close();
}
});
it("initialization never throws with missing credentials", async () => {
// Ensure absolutely no credentials anywhere
const server = await createServer({
config: {},
env: {},
auth: {}
});
try {
// Should not throw
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
const context = getServerContext(server);
expect(context.session.apiToken).toBe("");
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await server.close();
}
});
it("custom baseUrl is normalized correctly", async () => {
const testCases = [
{ input: "api.clickup.com", expected: "https://api.clickup.com/api/v2" },
{ input: "https://custom.api.com", expected: "https://custom.api.com/api/v2" },
{ input: "https://test.com/api", expected: "https://test.com/api/v2" },
{ input: "", expected: "https://api.clickup.com/api/v2" }
];
for (const testCase of testCases) {
const server = await createServer({
config: {
baseUrl: testCase.input
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
expect(context.session.baseUrl).toBe(testCase.expected);
} finally {
await server.close();
}
}
});
it("requestTimeout is normalized correctly", async () => {
const testCases = [
{ input: undefined, expected: 30 },
{ input: 0, expected: 30 },
{ input: -100, expected: 30 },
{ input: 5000, expected: 5 },
{ input: 60000, expected: 60 }
];
for (const testCase of testCases) {
const server = await createServer({
config: {
requestTimeoutMs: testCase.input
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
expect(context.session.requestTimeout).toBe(testCase.expected);
} finally {
await server.close();
}
}
});
});