const SMITHERY_API_BASE_URL = (process.env.SMITHERY_API_URL || "https://api.smithery.ai").replace(/\/+$/, "");
export class SmitheryConnectError extends Error {
status: number;
constructor(message: string, status: number) {
super(message);
this.name = "SmitheryConnectError";
this.status = status;
}
}
type SmitheryNamespace = {
name: string;
};
type SmitheryNamespacesResponse = {
namespaces?: SmitheryNamespace[];
};
type SmitheryConnectionStatus =
| { state: "connected" }
| { state: "auth_required"; authorizationUrl?: string }
| { state: "error"; message: string }
| { state: string; [key: string]: unknown };
export type SmitheryConnection = {
connectionId: string;
mcpUrl: string;
name: string;
status?: SmitheryConnectionStatus;
createdAt?: string;
};
type SmitheryConnectionsResponse = {
connections?: SmitheryConnection[];
nextCursor?: string | null;
};
function buildAuthHeaders(apiKey: string): Headers {
const headers = new Headers();
headers.set("Authorization", `Bearer ${apiKey}`);
headers.set("Content-Type", "application/json");
return headers;
}
function toApiUrl(path: string): string {
return `${SMITHERY_API_BASE_URL}${path}`;
}
async function expectOk(response: Response, context: string): Promise<void> {
if (response.ok) return;
const responseText = await response.text().catch(() => "");
const suffix = responseText ? `: ${responseText}` : "";
throw new SmitheryConnectError(`${context}: ${response.status} ${response.statusText}${suffix}`, response.status);
}
export function getSmitheryApiBaseUrl(): string {
return SMITHERY_API_BASE_URL;
}
export async function listSmitheryNamespaces(apiKey: string): Promise<SmitheryNamespace[]> {
const response = await fetch(toApiUrl("/namespaces"), {
headers: buildAuthHeaders(apiKey),
});
await expectOk(response, "Failed to list Smithery namespaces");
const payload = (await response.json()) as SmitheryNamespacesResponse;
return payload.namespaces ?? [];
}
export async function createSmitheryNamespace(apiKey: string): Promise<SmitheryNamespace> {
const response = await fetch(toApiUrl("/namespaces"), {
method: "POST",
headers: buildAuthHeaders(apiKey),
});
await expectOk(response, "Failed to create Smithery namespace");
return (await response.json()) as SmitheryNamespace;
}
export async function resolveSmitheryNamespace(apiKey: string): Promise<string> {
const namespaces = await listSmitheryNamespaces(apiKey);
if (namespaces.length > 0) {
return namespaces[0]?.name ?? "";
}
const created = await createSmitheryNamespace(apiKey);
return created.name;
}
export async function listSmitheryConnectionsByUrl(
apiKey: string,
namespace: string,
mcpUrl: string,
): Promise<SmitheryConnection[]> {
const endpoint = new URL(toApiUrl(`/connect/${encodeURIComponent(namespace)}`));
endpoint.searchParams.set("mcpUrl", mcpUrl);
const response = await fetch(endpoint.toString(), {
headers: buildAuthHeaders(apiKey),
});
await expectOk(response, "Failed to list Smithery connections");
const payload = (await response.json()) as SmitheryConnectionsResponse;
return payload.connections ?? [];
}
export async function createSmitheryConnection(
apiKey: string,
namespace: string,
params: { mcpUrl: string; name?: string },
): Promise<SmitheryConnection> {
const response = await fetch(toApiUrl(`/connect/${encodeURIComponent(namespace)}`), {
method: "POST",
headers: buildAuthHeaders(apiKey),
body: JSON.stringify({
mcpUrl: params.mcpUrl,
name: params.name,
}),
});
await expectOk(response, "Failed to create Smithery connection");
return (await response.json()) as SmitheryConnection;
}
export async function getSmitheryConnection(
apiKey: string,
namespace: string,
connectionId: string,
): Promise<SmitheryConnection> {
const response = await fetch(
toApiUrl(`/connect/${encodeURIComponent(namespace)}/${encodeURIComponent(connectionId)}`),
{
headers: buildAuthHeaders(apiKey),
},
);
await expectOk(response, "Failed to get Smithery connection");
return (await response.json()) as SmitheryConnection;
}
export async function deleteSmitheryConnection(apiKey: string, namespace: string, connectionId: string): Promise<void> {
const response = await fetch(
toApiUrl(`/connect/${encodeURIComponent(namespace)}/${encodeURIComponent(connectionId)}`),
{
method: "DELETE",
headers: buildAuthHeaders(apiKey),
},
);
await expectOk(response, "Failed to delete Smithery connection");
}