Skip to main content
Glama

create_config

Create a configuration defining routing, caching, retry, and targets that activates immediately when referenced by a key or prompt.

Instructions

Create a config that defines routing, cache, retry, and targets for requests; use update_config to modify an existing one and list_config_versions for history. At least one setting is required, new configs become active immediately once referenced by a key or prompt, and the call returns the new id and version_id.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYesName for the new configuration
workspace_idNoWorkspace ID to create config in
cache_modeNoCache mode: 'simple' or 'semantic'
cache_max_ageNoCache max age in seconds
retry_attemptsNoNumber of retry attempts (1-5)
retry_on_status_codesNoHTTP status codes to retry on (e.g., [429, 500, 502, 503])
strategy_modeNoRouting strategy: 'loadbalance' or 'fallback'
targetsNoArray of target providers with virtual keys

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
okYesWhether the tool call succeeded and returned structured data
dataNoStructured success payload when ok is true
errorNoStructured error payload when ok is false

Implementation Reference

  • The MCP tool handler for 'create_config' — registers the tool with the MCP server, builds the config payload, validates at least one config setting is present, calls the service layer, and returns the created config's id and version_id.
    // Phase 1: Create configuration tool
    server.tool(
    	"create_config",
    	"Create a config that defines routing, cache, retry, and targets for requests; use update_config to modify an existing one and list_config_versions for history. At least one setting is required, new configs become active immediately once referenced by a key or prompt, and the call returns the new id and version_id.",
    	CONFIGS_TOOL_SCHEMAS.createConfig,
    	async (params) => {
    		const config = buildConfigPayload(params);
    
    		// API requires config to have at least one non-empty setting
    		if (!hasConfigSettings(config)) {
    			return {
    				content: [
    					{
    						type: "text",
    						text: "Error creating configuration: At least one config setting (cache_mode, retry_attempts, strategy_mode, or targets) must be provided",
    					},
    				],
    				isError: true,
    			};
    		}
    
    		const result = await service.configs.createConfig({
    			name: params.name,
    			config,
    			workspace_id: params.workspace_id,
    		});
    
    		return {
    			content: [
    				{
    					type: "text",
    					text: JSON.stringify(
    						{
    							message: `Successfully created configuration "${params.name}"`,
    							id: result.id,
    							version_id: result.version_id,
    						},
    						null,
    						2,
    					),
    				},
    			],
    		};
    	},
    );
  • Zod input schema for createConfig — defines the user-provided parameters (name, workspace_id, cache_mode, cache_max_age, retry_attempts, retry_on_status_codes, strategy_mode, targets) with descriptions.
    createConfig: {
    	name: z.string().describe("Name for the new configuration"),
    	workspace_id: z
    		.string()
    		.optional()
    		.describe("Workspace ID to create config in"),
    	cache_mode: z
    		.enum(["simple", "semantic"])
    		.optional()
    		.describe("Cache mode: 'simple' or 'semantic'"),
    	cache_max_age: z.coerce
    		.number()
    		.positive()
    		.optional()
    		.describe("Cache max age in seconds"),
    	retry_attempts: z.coerce
    		.number()
    		.positive()
    		.max(5)
    		.optional()
    		.describe("Number of retry attempts (1-5)"),
    	retry_on_status_codes: z
    		.array(z.coerce.number())
    		.optional()
    		.describe("HTTP status codes to retry on (e.g., [429, 500, 502, 503])"),
    	strategy_mode: z
    		.enum(["loadbalance", "fallback"])
    		.optional()
    		.describe("Routing strategy: 'loadbalance' or 'fallback'"),
    	targets: z
    		.array(
    			z
    				.object({
    					provider: z.string().optional(),
    					virtual_key: z.string().optional(),
    				})
    				.refine((t) => t.provider || t.virtual_key, {
    					message: "Each target must have at least provider or virtual_key",
    				}),
    		)
    		.optional()
    		.describe("Array of target providers with virtual keys"),
    },
  • Registration of registerConfigsTools in the tool registry (imported, added to TOOL_DOMAIN_REGISTRARS, and re-exported).
    import { registerConfigsTools } from "./configs.tools.js";
    import { registerGuardrailsTools } from "./guardrails.tools.js";
    import { registerIntegrationsTools } from "./integrations.tools.js";
    import { registerKeysTools } from "./keys.tools.js";
    import { registerLabelsTools } from "./labels.tools.js";
    import { registerLimitsTools } from "./limits.tools.js";
    import { registerLoggingTools } from "./logging.tools.js";
    import { registerMcpIntegrationsTools } from "./mcp-integrations.tools.js";
    import { registerMcpServersTools } from "./mcp-servers.tools.js";
    import { registerPartialsTools } from "./partials.tools.js";
    import { registerPromptsTools } from "./prompts.tools.js";
    import { registerProvidersTools } from "./providers.tools.js";
    import { registerTracingTools } from "./tracing.tools.js";
    import { registerUsersTools } from "./users.tools.js";
    import { registerWorkspacesTools } from "./workspaces.tools.js";
    
    type ToolRegistrar = (server: McpServer, service: PortkeyService) => void;
    
    const TOOL_DOMAIN_REGISTRARS = [
    	["users", registerUsersTools],
    	["workspaces", registerWorkspacesTools],
    	["configs", registerConfigsTools],
    	["keys", registerKeysTools],
    	["collections", registerCollectionsTools],
    	["prompts", registerPromptsTools],
    	["analytics", registerAnalyticsTools],
    	["guardrails", registerGuardrailsTools],
    	["limits", registerLimitsTools],
    	["audit", registerAuditTools],
    	["labels", registerLabelsTools],
    	["partials", registerPartialsTools],
    	["tracing", registerTracingTools],
    	["logging", registerLoggingTools],
    	["providers", registerProvidersTools],
    	["integrations", registerIntegrationsTools],
    	["mcp-integrations", registerMcpIntegrationsTools],
    	["mcp-servers", registerMcpServersTools],
    ] as const satisfies readonly (readonly [string, ToolRegistrar])[];
    
    export type ToolDomain = (typeof TOOL_DOMAIN_REGISTRARS)[number][0];
    
    export interface RegisterAllToolsOptions {
    	domains?: readonly ToolDomain[];
    }
    
    export const TOOL_DOMAIN_NAMES = TOOL_DOMAIN_REGISTRARS.map(
    	([domain]) => domain,
    ) as ToolDomain[];
    
    const TOOL_DOMAIN_NAME_SET = new Set<string>(TOOL_DOMAIN_NAMES);
    
    export function isToolDomain(value: string): value is ToolDomain {
    	return TOOL_DOMAIN_NAME_SET.has(value);
    }
    
    export function normalizeToolDomains(
    	domains: Iterable<ToolDomain>,
    ): ToolDomain[] {
    	const selectedDomains = new Set(domains);
    
    	return TOOL_DOMAIN_REGISTRARS.filter(([domain]) =>
    		selectedDomains.has(domain),
    	).map(([domain]) => domain);
    }
    
    type ToolAnnotations = {
    	title?: string;
    	readOnlyHint: boolean;
    	destructiveHint: boolean;
    	idempotentHint: boolean;
    	openWorldHint: boolean;
    };
    
    const TOOL_ANNOTATION_KEYS = new Set([
    	"title",
    	"readOnlyHint",
    	"destructiveHint",
    	"idempotentHint",
    	"openWorldHint",
    ]);
    
    const READ_ONLY_IDEMPOTENT_TOOL_PREFIXES = [
    	"get_",
    	"list_",
    	"validate_",
    	"render_",
    	"download_",
    ] as const;
    
    const READ_ONLY_NON_IDEMPOTENT_TOOL_PREFIXES = ["run_", "test_"] as const;
    
    const DESTRUCTIVE_TOOL_PREFIXES = [
    	"delete_",
    	"remove_",
    	"cancel_",
    	"reset_",
    ] as const;
    
    const ENTERPRISE_GATED_TOOL_NAMES = new Set([
    	"get_cost_analytics",
    	"get_request_analytics",
    	"get_token_analytics",
    	"get_latency_analytics",
    	"get_error_analytics",
    	"get_error_rate_analytics",
    	"get_cache_hit_latency",
    	"get_cache_hit_rate",
    	"get_users_analytics",
    	"get_error_stacks_analytics",
    	"get_error_status_codes_analytics",
    	"get_user_requests_analytics",
    	"get_rescued_requests_analytics",
    	"get_feedback_analytics",
    	"get_feedback_models_analytics",
    	"get_feedback_scores_analytics",
    	"get_feedback_weighted_analytics",
    	"get_analytics_group_users",
    	"get_analytics_group_models",
    	"get_analytics_group_metadata",
    	"list_audit_logs",
    	"get_integration",
    	"list_integration_models",
    	"list_integration_workspaces",
    	"list_all_users",
    	"get_user",
    	"list_user_invites",
    	"get_user_stats",
    ]);
    
    const ENTERPRISE_GATED_DESCRIPTION_NOTE =
    	"Enterprise-gated. Returns 403 on non-Enterprise Portkey plans.";
    
    function isToolAnnotationsLike(
    	value: unknown,
    ): value is Partial<ToolAnnotations> {
    	if (!isRecord(value)) {
    		return false;
    	}
    
    	const keys = Object.keys(value);
    	if (
    		keys.length === 0 ||
    		!keys.every((key) => TOOL_ANNOTATION_KEYS.has(key))
    	) {
    		return false;
    	}
    
    	return Object.values(value).every(
    		(entry) =>
    			entry === undefined ||
    			typeof entry === "boolean" ||
    			typeof entry === "string",
    	);
    }
    
    function inferToolAnnotations(toolName: string): ToolAnnotations {
    	if (
    		READ_ONLY_IDEMPOTENT_TOOL_PREFIXES.some((prefix) =>
    			toolName.startsWith(prefix),
    		)
    	) {
    		return {
    			readOnlyHint: true,
    			destructiveHint: false,
    			idempotentHint: true,
    			openWorldHint: true,
    		};
    	}
    
    	if (
    		READ_ONLY_NON_IDEMPOTENT_TOOL_PREFIXES.some((prefix) =>
    			toolName.startsWith(prefix),
    		)
    	) {
    		return {
    			readOnlyHint: true,
    			destructiveHint: false,
    			idempotentHint: false,
    			openWorldHint: true,
    		};
    	}
    
    	if (DESTRUCTIVE_TOOL_PREFIXES.some((prefix) => toolName.startsWith(prefix))) {
    		return {
    			readOnlyHint: false,
    			destructiveHint: true,
    			idempotentHint: false,
    			openWorldHint: true,
    		};
    	}
    
    	return {
    		readOnlyHint: false,
    		destructiveHint: false,
    		idempotentHint: false,
    		openWorldHint: true,
    	};
    }
    
    function augmentToolDescription(
    	toolName: string,
    	description: string | undefined,
    ): string | undefined {
    	if (!description || !ENTERPRISE_GATED_TOOL_NAMES.has(toolName)) {
    		return description;
    	}
    
    	if (description.includes(ENTERPRISE_GATED_DESCRIPTION_NOTE)) {
    		return description;
    	}
    
    	return `${description} ${ENTERPRISE_GATED_DESCRIPTION_NOTE}`;
    }
    
    function getToolErrorMessage(error: unknown): string {
    	if (error instanceof Error && error.message) {
    		return error.message;
    	}
    
    	return String(error);
    }
    
    type StandardToolSuccessEnvelope = {
    	ok: true;
    	data: unknown;
    };
    
    type StandardToolErrorEnvelope = {
    	ok: false;
    	error: {
    		message: string;
    		details?: unknown;
    	};
    };
    
    type StandardToolEnvelope =
    	| StandardToolSuccessEnvelope
    	| StandardToolErrorEnvelope;
    
    const STANDARD_TOOL_OUTPUT_SCHEMA = {
    	ok: z
    		.boolean()
    		.describe("Whether the tool call succeeded and returned structured data"),
    	data: z
    		.unknown()
    		.optional()
    		.describe("Structured success payload when ok is true"),
    	error: z
    		.object({
    			message: z.string().describe("Human-readable error message"),
    			details: z
    				.unknown()
    				.optional()
    				.describe("Optional structured error details"),
    		})
    		.optional()
    		.describe("Structured error payload when ok is false"),
    } as const;
    
    function isRecord(value: unknown): value is Record<string, unknown> {
    	return typeof value === "object" && value !== null;
    }
    
    function isStandardToolEnvelope(value: unknown): value is StandardToolEnvelope {
    	if (!isRecord(value) || typeof value.ok !== "boolean") {
    		return false;
    	}
    
    	if (value.ok) {
    		return "data" in value;
    	}
    
    	return isRecord(value.error) && typeof value.error.message === "string";
    }
    
    function tryParseJson(text: string): unknown {
    	try {
    		return JSON.parse(text);
    	} catch {
    		return undefined;
    	}
    }
    
    function getFirstTextContent(result: CallToolResult): string | undefined {
    	const first = result.content?.[0];
    	return first?.type === "text" ? first.text : undefined;
    }
    
    function getSuccessData(result: CallToolResult): unknown {
    	const text = getFirstTextContent(result);
    	if (text === undefined) {
    		return null;
    	}
    
    	const parsed = tryParseJson(text);
    	return parsed !== undefined ? parsed : { message: text };
    }
    
    function getErrorDetails(result: CallToolResult): {
    	message: string;
    	details?: unknown;
    } {
    	const text = getFirstTextContent(result);
    	if (text === undefined) {
    		return { message: "Tool execution failed" };
    	}
    
    	const parsed = tryParseJson(text);
    	if (parsed === undefined) {
    		return { message: text };
    	}
    
    	if (isRecord(parsed) && typeof parsed.message === "string") {
    		return { message: parsed.message, details: parsed };
    	}
    
    	return { message: text, details: parsed };
    }
    
    function formatToolEnvelope(envelope: StandardToolEnvelope): string {
    	return JSON.stringify(envelope, null, 2);
    }
    
    function normalizeToolResult(result: CallToolResult): CallToolResult {
    	const envelope = isStandardToolEnvelope(result.structuredContent)
    		? result.structuredContent
    		: result.isError
    			? ({
    					ok: false,
    					error: getErrorDetails(result),
    				} satisfies StandardToolErrorEnvelope)
    			: ({
    					ok: true,
    					data: getSuccessData(result),
    				} satisfies StandardToolSuccessEnvelope);
    
    	return {
    		...result,
    		content: [{ type: "text", text: formatToolEnvelope(envelope) }],
    		structuredContent: envelope,
    	};
    }
    
    function wrapToolCallback(
    	toolName: string,
    	callback: ToolCallback,
    ): ToolCallback {
    	return async (...args) => {
    		try {
    			return normalizeToolResult((await callback(...args)) as CallToolResult);
    		} catch (error) {
    			const message = getToolErrorMessage(error);
    
    			Logger.error("Tool callback failed", {
    				error: message,
    				metadata: { toolName },
    			});
    
    			return normalizeToolResult({
    				content: [
    					{ type: "text", text: `Tool "${toolName}" failed: ${message}` },
    				],
    				isError: true,
    			} satisfies CallToolResult);
    		}
    	};
    }
    
    function buildToolRegistration(
    	name: string,
    	rest: unknown[],
    ): {
    	description?: string;
    	inputSchema?: unknown;
    	annotations: ToolAnnotations;
    	callback: ToolCallback;
    } {
    	const maybeCallback = rest.at(-1);
    	const wrappedCallback = wrapToolCallback(name, maybeCallback as ToolCallback);
    	const inferredAnnotations = inferToolAnnotations(name);
    	const args = [...rest.slice(0, -1)];
    
    	let description: string | undefined;
    	if (typeof args[0] === "string") {
    		description = args.shift() as string;
    	}
    	description = augmentToolDescription(name, description);
    
    	let inputSchema: unknown;
    	let annotations = inferredAnnotations;
    
    	if (args.length === 1) {
    		if (isToolAnnotationsLike(args[0])) {
    			annotations = {
    				...inferredAnnotations,
    				...args[0],
    			};
    		} else {
    			inputSchema = args[0];
    		}
    	} else if (args.length >= 2) {
    		inputSchema = args[0];
    		if (isToolAnnotationsLike(args[1])) {
    			annotations = {
    				...inferredAnnotations,
    				...args[1],
    			};
    		}
    	}
    
    	return {
    		description,
    		inputSchema,
    		annotations,
    		callback: wrappedCallback,
    	};
    }
    
    function createSafeToolServer(server: McpServer): McpServer {
    	const originalTool = server.tool.bind(server);
    	const originalRegisterTool =
    		"registerTool" in server && typeof server.registerTool === "function"
    			? server.registerTool.bind(server)
    			: undefined;
    	const safeServer = Object.create(server) as McpServer;
    	const callOriginalTool = (...args: [string, ...unknown[]]) =>
    		Reflect.apply(
    			originalTool as (...toolArgs: [string, ...unknown[]]) => unknown,
    			server,
    			args,
    		);
    	const callOriginalRegisterTool = (
    		...args: [string, Record<string, unknown>, ToolCallback]
    	) => {
    		if (!originalRegisterTool) {
    			throw new Error("registerTool is not available on the MCP server");
    		}
    
    		return Reflect.apply(
    			originalRegisterTool as (
    				...toolArgs: [string, Record<string, unknown>, ToolCallback]
    			) => unknown,
    			server,
    			args,
    		);
    	};
    
    	safeServer.tool = ((name: string, ...rest: unknown[]) => {
    		const maybeCallback = rest.at(-1);
    		if (typeof maybeCallback !== "function") {
    			return callOriginalTool(name, ...rest);
    		}
    
    		const registration = buildToolRegistration(name, rest);
    
    		if (originalRegisterTool) {
    			return callOriginalRegisterTool(
    				name,
    				{
    					...(registration.description
    						? { description: registration.description }
    						: {}),
    					...(registration.inputSchema !== undefined
    						? { inputSchema: registration.inputSchema }
    						: {}),
    					outputSchema: STANDARD_TOOL_OUTPUT_SCHEMA,
    					annotations: registration.annotations,
    				},
    				registration.callback,
    			);
    		}
    
    		const wrappedRest =
    			registration.inputSchema !== undefined
    				? registration.description !== undefined
    					? [
    							registration.description,
    							registration.inputSchema,
    							registration.annotations,
    							registration.callback,
    						]
    					: [
    							registration.inputSchema,
    							registration.annotations,
    							registration.callback,
    						]
    				: registration.description !== undefined
    					? [
    							registration.description,
    							registration.annotations,
    							registration.callback,
    						]
    					: [registration.annotations, registration.callback];
    
    		return callOriginalTool(name, ...wrappedRest);
    	}) as McpServer["tool"];
    
    	return safeServer;
    }
    
    /**
     * Register all Admin API tools on the MCP server
     * @param server - The MCP server instance
     * @param service - The PortkeyService facade
     */
    export function registerAllTools(
    	server: McpServer,
    	service: PortkeyService,
    	options: RegisterAllToolsOptions = {},
    ): void {
    	const safeServer = createSafeToolServer(server);
    	const selectedDomains = options.domains
    		? new Set(normalizeToolDomains(options.domains))
    		: undefined;
    
    	for (const [domain, registerTools] of TOOL_DOMAIN_REGISTRARS) {
    		if (!selectedDomains || selectedDomains.has(domain)) {
    			registerTools(safeServer, service);
    		}
    	}
    }
    
    // Re-export individual registration functions for selective use
    export {
    	registerAnalyticsTools,
    	registerAuditTools,
    	registerCollectionsTools,
    	registerConfigsTools,
  • Helper function buildConfigPayload — transforms tool parameters (cache_mode, retry_attempts, strategy_mode, targets) into the API config payload structure.
    function buildConfigPayload(params: ConfigToolParams) {
    	const cache =
    		params.cache_mode !== undefined || params.cache_max_age !== undefined
    			? {
    					...(params.cache_mode !== undefined
    						? { mode: params.cache_mode }
    						: {}),
    					...(params.cache_max_age !== undefined
    						? { max_age: params.cache_max_age }
    						: {}),
    				}
    			: undefined;
    	const retry =
    		params.retry_attempts !== undefined ||
    		params.retry_on_status_codes !== undefined
    			? {
    					...(params.retry_attempts !== undefined
    						? { attempts: params.retry_attempts }
    						: {}),
    					...(params.retry_on_status_codes !== undefined
    						? { on_status_codes: params.retry_on_status_codes }
    						: {}),
    				}
    			: undefined;
    
    	return {
    		cache,
    		retry,
    		strategy:
    			params.strategy_mode !== undefined
    				? { mode: params.strategy_mode }
    				: undefined,
    		targets: params.targets,
    	};
    }
  • Helper function hasConfigSettings — checks if the built config payload has at least one non-empty setting.
    function hasConfigSettings(
    	config: ReturnType<typeof buildConfigPayload>,
    ): boolean {
    	return (
    		config.cache !== undefined ||
    		config.retry !== undefined ||
    		config.strategy !== undefined ||
    		config.targets !== undefined
    	);
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide basic safety hints (non-readOnly, non-destructive). The description adds significant behavioral context: configs become active immediately upon reference, and the return value includes id and version_id. No contradictions with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences, front-loaded with main purpose, no wasted words. Every sentence provides value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers purpose, alternatives, immediate activation, and return values. Output schema exists to explain return structure. Could mention error conditions or permissions but overall adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description summarizes parameter categories ('routing, cache, retry, targets') but adds little beyond schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Create') and resource ('config'), and explicitly distinguishes from siblings by naming 'update_config' and 'list_config_versions' for alternative actions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clear context on when to use this tool vs alternatives: 'use update_config to modify...' and 'list_config_versions for history'. Also explains activation behavior, but could more explicitly state when NOT to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/s-b-e-n-s-o-n/portkey-admin-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server