import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
import { createConfiguredServer } from "../mcp/server.js";
import { fromEnv, toSessionConfig, type AppConfig } from "../shared/config/schema.js";
import { loadRuntimeConfig } from "../config/runtime.js";
import type { ToolGateInit } from "../shared/config/toolGate.js";
import { smitheryConfigSchema, type SmitheryConfig } from "../schema/mcpConfig.js";
export const configSchema = smitheryConfigSchema;
export type SmitheryCommandContext = {
config?: unknown;
env?: Record<string, string | undefined>;
auth?: unknown;
};
function toInvalidConfigError(error: unknown, source: "smithery" | "application"): McpError {
const reason = error instanceof Error ? error.message : String(error);
const data = error && typeof error === "object" && "issues" in error
? { issues: (error as { issues: unknown }).issues }
: undefined;
return new McpError(ErrorCode.InvalidParams, `Invalid ${source} configuration: ${reason}`, data);
}
function normaliseAuthSource(auth: unknown): Record<string, string | undefined> {
if (!auth || typeof auth !== "object") return {};
const out: Record<string, string | undefined> = {};
try {
for (const [k, v] of Object.entries(auth as Record<string, unknown>)) {
if (typeof v === "string") {
out[k] = v;
} else if (typeof v === "number" && Number.isFinite(v)) {
out[k] = String(v);
} else if (v && typeof v === "object" && "value" in v) {
const unwrapped = (v as { value: unknown }).value;
if (typeof unwrapped === "string") {
out[k] = unwrapped;
} else if (typeof unwrapped === "number" && Number.isFinite(unwrapped)) {
out[k] = String(unwrapped);
}
}
}
} catch {
return {};
}
return out;
}
function mergeAppConfig(base: AppConfig, overrides?: SmitheryConfig): AppConfig {
if (!overrides) return base;
const { allowTools: _allowTools, denyTools: _denyTools, ...rest } = overrides;
const merged: AppConfig = { ...base };
if (rest.apiToken !== undefined) {
merged.apiToken = rest.apiToken;
}
if (rest.defaultTeamId !== undefined) {
merged.defaultTeamId = rest.defaultTeamId;
}
if (rest.primaryLanguage !== undefined) {
merged.primaryLanguage = rest.primaryLanguage;
}
if (rest.baseUrl !== undefined) {
merged.baseUrl = rest.baseUrl;
}
if (rest.requestTimeoutMs !== undefined) {
merged.requestTimeoutMs = rest.requestTimeoutMs;
}
if (rest.defaultHeadersJson !== undefined) {
merged.defaultHeadersJson = rest.defaultHeadersJson;
}
if (rest.authScheme !== undefined) {
merged.authScheme = rest.authScheme;
}
console.log("config_merged", {
message: "Merged Smithery config with base config",
apiTokenSource: rest.apiToken ? "smithery" : base.apiToken ? "env" : "none",
defaultTeamIdSource:
rest.defaultTeamId !== undefined
? "smithery"
: base.defaultTeamId !== undefined
? "env"
: "none",
baseUrlSource: rest.baseUrl ? "smithery" : base.baseUrl ? "env" : "default",
primaryLanguageSource: rest.primaryLanguage ? "smithery" : base.primaryLanguage ? "env" : "none"
});
return merged;
}
async function createSmitheryServer(context?: SmitheryCommandContext): Promise<Server> {
const envSource = {
...process.env,
...(context?.env ?? {}),
...normaliseAuthSource(context?.auth)
};
let overrides: SmitheryConfig | undefined;
if (context?.config) {
try {
overrides = smitheryConfigSchema.parse(context.config);
console.log("config_normalized", {
message: "Successfully parsed Smithery config",
hasApiToken: !!overrides.apiToken,
hasDefaultTeamId: overrides.defaultTeamId !== undefined,
hasPrimaryLanguage: !!overrides.primaryLanguage,
hasBaseUrl: !!overrides.baseUrl,
hasAllowTools: !!overrides.allowTools,
hasDenyTools: !!overrides.denyTools
});
} catch (err) {
const errorDetails = err instanceof Error ? err.message : String(err);
const zodIssues = err && typeof err === "object" && "issues" in err
? (err as { issues: unknown }).issues
: undefined;
console.warn("config_normalization_warning", {
message: "Config parsing failed, using base config from environment",
error: errorDetails,
issues: zodIssues
});
overrides = undefined;
}
}
let baseConfig: AppConfig;
try {
baseConfig = fromEnv(envSource);
} catch (error) {
throw toInvalidConfigError(error, "application");
}
const config = mergeAppConfig(baseConfig, overrides);
const session = toSessionConfig(config);
const runtime = loadRuntimeConfig();
const gateInit: ToolGateInit = {
overrides: {
allow: overrides?.allowTools,
deny: overrides?.denyTools
}
};
try {
const { server } = await createConfiguredServer(runtime, session, gateInit);
return server;
} catch (error) {
throw toInvalidConfigError(error, "smithery");
}
}
const createServerFromSmithery: (context?: SmitheryCommandContext) => Promise<Server> = Object.assign(
async (context?: SmitheryCommandContext) => createSmitheryServer(context),
{ configSchema }
);
const createServerWithSchema = Object.assign(
async (context?: SmitheryCommandContext) => createSmitheryServer(context),
{ configSchema }
);
export { createServerFromSmithery };
export default createServerWithSchema;