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 };
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 edge cases - defensive programming", () => {
it("handles NaN defaultTeamId gracefully", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: NaN
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
const context = getServerContext(server);
// NaN should be normalized to undefined
expect(context.session.defaultTeamId).toBeUndefined();
expect(context.session.apiToken).toBe("test-token");
} finally {
await server.close();
}
});
it("handles Infinity defaultTeamId gracefully", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: Infinity
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Infinity should be normalized to undefined
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await server.close();
}
});
it("handles negative Infinity defaultTeamId gracefully", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: -Infinity
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// -Infinity should be normalized to undefined
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await server.close();
}
});
it("handles null config values gracefully", async () => {
const server = await createServer({
config: {
apiToken: null as any,
defaultTeamId: null as any,
baseUrl: null as any
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// null values should be normalized
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("handles undefined in Smithery config gracefully", async () => {
const server = await createServer({
config: {
apiToken: undefined,
defaultTeamId: undefined
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("");
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await server.close();
}
});
it("handles whitespace-only strings gracefully", async () => {
const server = await createServer({
config: {
apiToken: " ",
primaryLanguage: "\t\n",
baseUrl: " "
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Whitespace-only strings are preserved (not trimmed by smithery schema)
// but baseUrl normalization still applies
expect(context.session.apiToken).toBe(" ");
expect(context.session.baseUrl).toBe("https://api.clickup.com/api/v2");
} finally {
await server.close();
}
});
it("handles malformed JSON in defaultHeadersJson gracefully", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultHeadersJson: "not valid json {["
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Malformed JSON should result in empty headers
expect(Object.keys(context.session.defaultHeaders).length).toBe(0);
} finally {
await server.close();
}
});
it("initializes with completely empty context object", 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.tools.length).toBeGreaterThan(0);
} finally {
await server.close();
}
});
it("initializes with null context", async () => {
const server = await createServer(undefined);
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("");
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await server.close();
}
});
it("handles negative requestTimeoutMs gracefully", async () => {
const server = await createServer({
config: {
requestTimeoutMs: -5000
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Negative timeout should default to 30 seconds
expect(context.session.requestTimeout).toBe(30);
} finally {
await server.close();
}
});
it("handles zero requestTimeoutMs gracefully", async () => {
const server = await createServer({
config: {
requestTimeoutMs: 0
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Zero timeout should default to 30 seconds
expect(context.session.requestTimeout).toBe(30);
} finally {
await server.close();
}
});
it("handles very large requestTimeoutMs values", async () => {
const server = await createServer({
config: {
requestTimeoutMs: 999999999
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Should convert to seconds (ceiling)
expect(context.session.requestTimeout).toBe(1000000);
} finally {
await server.close();
}
});
it("handles mixed valid and invalid config values", async () => {
const server = await createServer({
config: {
apiToken: "valid-token",
defaultTeamId: NaN,
baseUrl: "",
requestTimeoutMs: -100,
primaryLanguage: "en-US"
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Valid values should be preserved
expect(context.session.apiToken).toBe("valid-token");
expect(context.session.defaultHeaders["Accept-Language"]).toBe("en-US");
// Invalid values should be normalized
expect(context.session.defaultTeamId).toBeUndefined();
expect(context.session.baseUrl).toBe("https://api.clickup.com/api/v2");
expect(context.session.requestTimeout).toBe(30);
} finally {
await server.close();
}
});
it("handles config override with auth context containing invalid numbers", async () => {
const server = await createServer({
auth: {
SOME_VALUE: NaN as any,
ANOTHER_VALUE: Infinity as any,
VALID_VALUE: "valid-string"
},
config: {
apiToken: "config-token"
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Config should still work despite invalid auth values
expect(context.session.apiToken).toBe("config-token");
} finally {
await server.close();
}
});
it("handles environment with invalid CLICKUP_DEFAULT_TEAM_ID values", async () => {
const testCases = ["NaN", "Infinity", "-Infinity", "abc", "", " "];
for (const invalidValue of testCases) {
process.env.CLICKUP_DEFAULT_TEAM_ID = invalidValue;
const server = await createServer({
config: {
apiToken: "test-token"
}
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
// Invalid env value should result in undefined
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await server.close();
}
}
});
it("never throws during initialization regardless of config shape", async () => {
const edgeCases = [
{},
{ config: {} },
{ config: null as any },
{ config: { apiToken: NaN as any } },
{ config: { defaultTeamId: "not-a-number" as any } },
{ env: { INVALID: "value" } },
{ auth: { INVALID: {} as any } }
];
for (const testCase of edgeCases) {
let server: Server | undefined;
try {
server = await createServer(testCase as any);
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
} finally {
if (server) {
await server.close();
}
}
}
});
});