import { afterAll, beforeEach, describe, expect, it } from "vitest";
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { ErrorCode, InitializeRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
import createServer, {
createServerFromSmithery,
configSchema as smitheryConfigSchema
} 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;
});
afterAll(() => {
process.env = { ...ORIGINAL_ENV };
});
describe("smithery command context", () => {
it("prefers auth secrets when available", async () => {
const server = await createServer({
auth: {
CLICKUP_TOKEN: "token-from-auth",
},
});
const context = getServerContext(server);
expect(context.session.apiToken).toBe("token-from-auth");
await server.close();
});
it("unwraps structured secret values", async () => {
const server = await createServer({
auth: {
CLICKUP_TOKEN: { value: "structured-token" },
CLICKUP_DEFAULT_TEAM_ID: 345,
},
});
const context = getServerContext(server);
expect(context.session.apiToken).toBe("structured-token");
expect(context.session.defaultTeamId).toBe(345);
await server.close();
});
it("accepts auth scheme overrides from Smithery config", async () => {
const server = await createServer({
auth: {
CLICKUP_TOKEN: "token-from-auth",
},
config: {
apiToken: "config-token",
defaultTeamId: 321,
},
});
const context = getServerContext(server);
expect(context.session.defaultTeamId).toBe(321);
await server.close();
});
it("ignores unknown Smithery config entries", async () => {
const server = await createServer({
config: {
apiToken: "config-token",
defaultTeamId: 55,
unexpected: "value",
} as Record<string, unknown>,
});
const context = getServerContext(server);
expect(context.session.apiToken).toBe("config-token");
await server.close();
});
it("accepts invalid config values and normalizes them instead of throwing", async () => {
// Defensive: server should accept and normalize invalid config, not throw
const server = await createServer({
config: {
apiToken: "token",
defaultTeamId: "{{TEAM_ID}}", // Invalid placeholder string
} as Record<string, unknown>,
});
try {
const context = getServerContext(server);
// Invalid defaultTeamId should be normalized to undefined
expect(context.session.apiToken).toBe("token");
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await server.close();
}
});
it("handles invalid application config gracefully by ignoring bad values", async () => {
const server = await createServer({
env: {
CLICKUP_DEFAULT_TEAM_ID: "not-a-number",
},
});
const context = getServerContext(server);
// Invalid team ID should be ignored and set to undefined
expect(context.session.defaultTeamId).toBeUndefined();
await server.close();
});
it("exposes the configuration schema on the Smithery entry point", () => {
expect(createServerFromSmithery.configSchema).toBe(smitheryConfigSchema);
});
it("accepts empty Smithery config and initialize succeeds with warnings", async () => {
const server = await createServer({
config: {} as Record<string, unknown>,
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
} finally {
await server.close();
}
});
it("accepts missing API token and initialize succeeds with warning", async () => {
const server = await createServer({
config: {
defaultTeamId: 123,
} as Record<string, unknown>,
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
} finally {
await server.close();
}
});
it("accepts missing default team id and initialize succeeds with warning", async () => {
const server = await createServer({
config: {
apiToken: "token",
} as Record<string, unknown>,
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
expect(response).toHaveProperty("protocolVersion");
} finally {
await server.close();
}
});
it("accepts Smithery config when required credentials are provided", async () => {
const server = await createServer({
config: {
apiToken: "config-token",
defaultTeamId: 987,
},
});
const context = getServerContext(server);
expect(context.session.apiToken).toBe("config-token");
expect(context.session.defaultTeamId).toBe(987);
await server.close();
});
it("accepts empty string apiToken without validation error", async () => {
const server = await createServer({
config: {
apiToken: "",
defaultTeamId: 123,
},
});
const context = getServerContext(server);
expect(context.session.apiToken).toBe("");
expect(context.session.defaultTeamId).toBe(123);
await server.close();
});
it("accepts zero defaultTeamId without validation error", async () => {
const server = await createServer({
config: {
apiToken: "token",
defaultTeamId: 0,
},
});
const context = getServerContext(server);
expect(context.session.apiToken).toBe("token");
expect(context.session.defaultTeamId).toBe(0);
await server.close();
});
it("accepts empty string for optional fields without validation error", async () => {
const server = await createServer({
config: {
apiToken: "token",
primaryLanguage: "",
baseUrl: "",
defaultHeadersJson: "",
},
});
const context = getServerContext(server);
expect(context.session.apiToken).toBe("token");
await server.close();
});
it("accepts negative requestTimeoutMs without validation error", async () => {
const server = await createServer({
config: {
apiToken: "token",
requestTimeoutMs: -100,
},
});
const context = getServerContext(server);
expect(context.session.apiToken).toBe("token");
await server.close();
});
});