Configuration & Arguments
Learn how to configure your MCP server using command-line arguments and environment variables from the client configuration.
How Configuration Works
When you configure an MCP server in Claude Desktop (or other MCP clients), you can pass arguments and environment variables:
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/path/to/server.js", "--api-key", "secret123", "--debug"],
"env": {
"DATABASE_URL": "postgresql://localhost/mydb",
"LOG_LEVEL": "debug"
}
}
}
}
Your server receives:
- Command-line arguments via
process.argv - Environment variables via
process.env
Parsing Command-Line Arguments
Simple Manual Parsing
import 'reflect-metadata';
import { createServer, MCPServer, Tool, Param } from '@mcpkit-dev/core';
function parseArgs() {
const args = process.argv.slice(2);
const config: Record<string, string | boolean> = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg.startsWith('--')) {
const key = arg.slice(2);
const nextArg = args[i + 1];
if (nextArg && !nextArg.startsWith('--')) {
config[key] = nextArg;
i++;
} else {
config[key] = true;
}
}
}
return config;
}
const config = parseArgs();
@MCPServer({
name: 'my-server',
version: '1.0.0',
})
class MyServer {
private apiKey = config['api-key'] as string;
@Tool({ description: 'Call external API' })
async callApi(@Param({ name: 'query' }) query: string) {
return `Called API with key: ${this.apiKey ? '***' : 'none'}`;
}
}
const server = createServer(MyServer);
await server.listen();
Using Constructor Parameters
For better encapsulation, pass configuration through the constructor:
import 'reflect-metadata';
import { createServer, MCPServer, Tool, Param } from '@mcpkit-dev/core';
interface ServerConfig {
apiKey: string;
debug: boolean;
baseUrl?: string;
}
@MCPServer({
name: 'my-server',
version: '1.0.0',
})
class MyServer {
constructor(private config: ServerConfig) {
if (config.debug) {
console.error('[DEBUG] Server initialized with config');
}
}
@Tool({ description: 'Fetch data from API' })
async fetchData(@Param({ name: 'endpoint' }) endpoint: string) {
const url = `${this.config.baseUrl ?? 'https://api.example.com'}/${endpoint}`;
// Use this.config.apiKey for authentication
return { url, authenticated: !!this.config.apiKey };
}
@Tool({ description: 'Get current config (redacted)' })
async getConfig() {
return {
apiKey: this.config.apiKey ? '***' : 'not set',
debug: this.config.debug,
baseUrl: this.config.baseUrl ?? 'default',
};
}
}
// Parse arguments
const args = process.argv.slice(2);
const config: ServerConfig = {
apiKey: '',
debug: false,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--api-key':
config.apiKey = args[++i];
break;
case '--debug':
config.debug = true;
break;
case '--base-url':
config.baseUrl = args[++i];
break;
}
}
// Pass config to createServer
const server = createServer(MyServer, config);
await server.listen();
Using Commander.js
For more complex argument parsing, use a library like Commander:
npm install commander
import 'reflect-metadata';
import { createServer, MCPServer, Tool, Param } from '@mcpkit-dev/core';
import { program } from 'commander';
program
.option('--api-key <key>', 'API key for external service')
.option('--debug', 'Enable debug mode', false)
.option('--base-url <url>', 'Base URL for API', 'https://api.example.com')
.option('--timeout <ms>', 'Request timeout in milliseconds', '5000')
.parse();
const options = program.opts();
@MCPServer({
name: 'my-server',
version: '1.0.0',
})
class MyServer {
@Tool({ description: 'Check server configuration' })
async checkConfig() {
return {
debug: options.debug,
baseUrl: options.baseUrl,
timeout: parseInt(options.timeout),
hasApiKey: !!options.apiKey,
};
}
}
const server = createServer(MyServer);
await server.listen();
Environment Variables
Environment variables are often preferred for secrets since they don't appear in process listings.
Client Configuration
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/path/to/server.js"],
"env": {
"API_KEY": "secret123",
"DATABASE_URL": "postgresql://localhost/mydb",
"DEBUG": "true",
"LOG_LEVEL": "info"
}
}
}
}
Server Implementation
import 'reflect-metadata';
import { createServer, MCPServer, Tool, Param } from '@mcpkit-dev/core';
interface Config {
apiKey: string;
databaseUrl: string;
debug: boolean;
logLevel: 'debug' | 'info' | 'warn' | 'error';
}
function loadConfig(): Config {
return {
apiKey: process.env.API_KEY ?? '',
databaseUrl: process.env.DATABASE_URL ?? '',
debug: process.env.DEBUG === 'true',
logLevel: (process.env.LOG_LEVEL as Config['logLevel']) ?? 'info',
};
}
const config = loadConfig();
// Validate required config
if (!config.apiKey) {
console.error('ERROR: API_KEY environment variable is required');
process.exit(1);
}
@MCPServer({
name: 'my-server',
version: '1.0.0',
})
class MyServer {
@Tool({ description: 'Query the database' })
async queryDatabase(@Param({ name: 'query' }) query: string) {
if (config.debug) {
console.error(`[DEBUG] Executing query: ${query}`);
}
// Use config.databaseUrl to connect
return { success: true, query };
}
}
const server = createServer(MyServer);
await server.listen();
Combining Arguments and Environment Variables
A common pattern is to use environment variables as defaults that can be overridden by command-line arguments:
import 'reflect-metadata';
import { createServer, MCPServer, Tool } from '@mcpkit-dev/core';
interface Config {
apiKey: string;
baseUrl: string;
debug: boolean;
maxRetries: number;
}
function loadConfig(): Config {
const args = process.argv.slice(2);
// Start with environment variables as defaults
const config: Config = {
apiKey: process.env.API_KEY ?? '',
baseUrl: process.env.BASE_URL ?? 'https://api.example.com',
debug: process.env.DEBUG === 'true',
maxRetries: parseInt(process.env.MAX_RETRIES ?? '3'),
};
// Override with command-line arguments
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--api-key':
config.apiKey = args[++i];
break;
case '--base-url':
config.baseUrl = args[++i];
break;
case '--debug':
config.debug = true;
break;
case '--max-retries':
config.maxRetries = parseInt(args[++i]);
break;
}
}
return config;
}
const config = loadConfig();
@MCPServer({
name: 'my-server',
version: '1.0.0',
})
class MyServer {
@Tool({ description: 'Show current configuration' })
async showConfig() {
return {
baseUrl: config.baseUrl,
debug: config.debug,
maxRetries: config.maxRetries,
hasApiKey: !!config.apiKey,
};
}
}
const server = createServer(MyServer);
await server.listen();
Configuration with Validation
Use Zod to validate your configuration:
import 'reflect-metadata';
import { createServer, MCPServer, Tool } from '@mcpkit-dev/core';
import { z } from 'zod';
const ConfigSchema = z.object({
apiKey: z.string().min(1, 'API_KEY is required'),
baseUrl: z.string().url().default('https://api.example.com'),
debug: z.boolean().default(false),
maxRetries: z.number().int().min(0).max(10).default(3),
timeout: z.number().int().min(1000).max(30000).default(5000),
});
type Config = z.infer<typeof ConfigSchema>;
function loadConfig(): Config {
const raw = {
apiKey: process.env.API_KEY,
baseUrl: process.env.BASE_URL,
debug: process.env.DEBUG === 'true',
maxRetries: process.env.MAX_RETRIES ? parseInt(process.env.MAX_RETRIES) : undefined,
timeout: process.env.TIMEOUT ? parseInt(process.env.TIMEOUT) : undefined,
};
const result = ConfigSchema.safeParse(raw);
if (!result.success) {
console.error('Configuration error:');
result.error.issues.forEach(issue => {
console.error(` - ${issue.path.join('.')}: ${issue.message}`);
});
process.exit(1);
}
return result.data;
}
const config = loadConfig();
@MCPServer({
name: 'my-server',
version: '1.0.0',
})
class MyServer {
@Tool({ description: 'Get API status' })
async getStatus() {
return { configured: true, baseUrl: config.baseUrl };
}
}
const server = createServer(MyServer);
await server.listen();
Security Best Practices
- Use environment variables for secrets - They don't appear in
psoutput - Never log sensitive values - Redact API keys in logs
- Validate configuration on startup - Fail fast with clear error messages
- Use default values wisely - Don't default sensitive values
- Document required configuration - Help users configure correctly
// Good: Validate and fail fast
if (!config.apiKey) {
console.error('ERROR: API_KEY is required. Set it in your MCP client config:');
console.error(' "env": { "API_KEY": "your-key-here" }');
process.exit(1);
}
// Good: Redact in logs
console.error(`Connecting with API key: ${config.apiKey.slice(0, 4)}***`);
// Bad: Never do this
console.error(`API key: ${config.apiKey}`);