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 { invokeInitialize } from "./helpers/server.js";
const ORIGINAL_ENV = { ...process.env };
beforeEach(() => {
process.env = { ...ORIGINAL_ENV };
// Clear all ClickUp-related env vars to simulate Smithery empty config
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("Smithery initialization - Python parity", () => {
it("should initialize successfully with completely empty config (Smithery sends {})", async () => {
// Simulate exactly what Smithery does when it sends an empty config object
const server = await createServer({});
try {
// Initialize should not throw even with no credentials
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("should initialize successfully with minimal config (Smithery partial credentials)", async () => {
// Simulate Smithery providing only apiToken
const server = await createServer({
config: {
apiToken: "pk_smithery_test_token"
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
const context = getServerContext(server);
expect(context.session.apiToken).toBe("pk_smithery_test_token");
expect(context.session.defaultTeamId).toBeUndefined();
expect(context.tools.length).toBeGreaterThan(0);
} finally {
await server.close();
}
});
it("should never throw during initialization regardless of config", async () => {
const invalidConfigs = [
{}, // Empty
{ config: {} }, // Empty config
{ config: { apiToken: "" } }, // Empty string
{ config: { defaultTeamId: 0 } }, // Zero
{ config: { defaultTeamId: -1 } }, // Negative
{ config: { requestTimeoutMs: -100 } }, // Negative timeout
{ config: { baseUrl: "" } }, // Empty URL
];
for (const invalidConfig of invalidConfigs) {
const server = await createServer(invalidConfig);
try {
// None of these should throw
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();
}
}
});
it("should log warnings for missing credentials but not throw", async () => {
// This test verifies that missing credentials only produce warnings, not errors
const server = await createServer({
config: {},
env: {},
auth: {}
});
try {
// Should complete successfully
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("");
expect(context.session.defaultTeamId).toBeUndefined();
// Server should be functional, just with missing credentials
expect(context.tools.length).toBeGreaterThan(0);
} finally {
await server.close();
}
});
it("should handle Smithery auth context correctly", async () => {
// Smithery may provide credentials via auth context
const server = await createServer({
auth: {
CLICKUP_TOKEN: "auth_context_token",
CLICKUP_DEFAULT_TEAM_ID: 12345
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("auth_context_token");
expect(context.session.defaultTeamId).toBe(12345);
} finally {
await server.close();
}
});
it("should merge config, env, and auth correctly per Smithery precedence", async () => {
// Set env vars (always strings in process.env)
process.env.CLICKUP_TOKEN = "env_token";
process.env.CLICKUP_DEFAULT_TEAM_ID = "99999";
const server = await createServer({
config: {
apiToken: "config_token"
},
auth: {
// Auth provides numeric value which should override env string
CLICKUP_DEFAULT_TEAM_ID: 88888
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Config should override env
expect(context.session.apiToken).toBe("config_token");
// Auth should override env
expect(context.session.defaultTeamId).toBe(88888);
} finally {
await server.close();
}
});
});