
Security News
New React Server Components Vulnerabilities: DoS and Source Code Exposure
New DoS and source code exposure bugs in React Server Components and Next.js: whatβs affected and how to update safely.
@tscodex/mcp-sdk
Advanced tools
TypeScript SDK for creating MCP (Model Context Protocol) servers with seamless Extension integration
@tscodex/mcp-sdk is a production-ready TypeScript SDK for building MCP servers with built-in support for:
npm install @tscodex/mcp-sdk
Requirements:
Server name must:
Valid examples: my-server, mcp_images, server123, MyServer
Invalid examples: @tscodex/mcp-images (contains @ and /), 123server (starts with number), my server (contains space)
import { McpServer, Type } from '@tscodex/mcp-sdk';
const server = new McpServer({
name: 'hello-server',
version: '1.0.0',
description: 'Simple hello world MCP server'
});
// Define schema using TypeBox
const HelloSchema = Type.Object({
name: Type.Optional(Type.String({
description: 'Name to greet',
default: 'World'
}))
});
// Register tool with automatic type inference
server.addTool({
name: 'hello-world',
description: 'Greet someone with a personalized message',
schema: HelloSchema,
handler: async (params, context) => {
// params is automatically typed as { name?: string }
const name = params.name || 'World';
return {
content: [{
type: 'text',
text: `Hello, ${name}!`
}]
};
}
});
// Initialize and start
await server.initialize();
await server.start();
console.log(`Server running on port ${server.serverPort}`);
import { McpServer, Type, Static } from '@tscodex/mcp-sdk';
const ConfigSchema = Type.Object({
apiKey: Type.String({ minLength: 10 }),
timeout: Type.Number({ default: 5000 }),
enabled: Type.Boolean({ default: true })
});
type Config = Static<typeof ConfigSchema>;
const server = new McpServer<Config>({
name: 'api-server',
version: '1.0.0',
description: 'API integration server',
configSchema: ConfigSchema,
loadConfig: async () => {
// Extension config is passed via process.env.MCP_CONFIG
const extensionConfig = process.env.MCP_CONFIG
? JSON.parse(process.env.MCP_CONFIG)
: {};
return {
timeout: 5000,
enabled: true,
...extensionConfig // Extension config takes priority
};
}
});
// Access configuration in handlers
server.addTool({
name: 'api-call',
schema: Type.Object({}),
handler: async (params, context) => {
// context.config contains full config including secrets
const timeout = context.config.timeout;
const apiKey = context.config.apiKey; // Full access to all config
// If you need to return config in result, filter public parameters
import { filterMcpPublicConfig } from '@tscodex/mcp-sdk';
const publicConfig = filterMcpPublicConfig(context.config);
// Only 'mcp_*' keys will be in publicConfig
// ...
}
});
import { McpServer, Type, Static } from '@tscodex/mcp-sdk';
enum Roles {
ADMIN = 'admin',
USER = 'user'
}
const SessionSchema = Type.Object({
email: Type.String({ format: 'email' }),
role: Type.Enum(Roles)
});
type Session = Static<typeof SessionSchema>;
const server = new McpServer<Config, Roles, Session>({
name: 'secure-server',
version: '1.0.0',
description: 'Server with role-based access',
auth: {
roles: {
admin: (session, context) => {
// Access to loaded configuration
const allowedAdmins = context.config.adminEmails || [];
return session.role === Roles.ADMIN &&
allowedAdmins.includes(session.email);
},
user: async (session, context) => {
// Async checks supported
return session.role === Roles.USER;
}
},
sessionSchema: SessionSchema,
requireSession: true,
// Transform token from MCP_AUTH_TOKEN into full session
loadSession: async (token, context) => {
// Validate token and fetch user data
const response = await fetch(`${context.config.apiUrl}/validate-token`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
return await response.json() as Session;
}
}
});
// Tools with access control
server.addTool({
name: 'delete-file',
description: 'Delete a file (admin only)',
schema: Type.Object({ path: Type.String() }),
access: [Roles.ADMIN], // Only admins can use this tool
handler: async (params, context) => {
// context.session is typed as Session
console.log(`Admin ${context.session.email} deleted ${params.path}`);
// ...
}
});
// Register resource (URI automatically prefixed with server ID)
server.addResource({
uri: 'about', // Becomes: hello-server://about
name: 'About',
description: 'Server information',
handler: async (uri, context) => {
return {
contents: [{
uri, // Use normalized URI
mimeType: 'text/plain',
text: 'Server information...'
}]
};
}
});
// Register prompt
server.addPrompt({
name: 'explain-topic',
description: 'Explain a topic',
arguments: Type.Object({
topic: Type.String({ description: 'Topic to explain' })
}),
handler: async (params, context) => {
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: `Explain ${params.topic}`
}
}]
};
}
});
import type { ErrorHandler } from '@tscodex/mcp-sdk';
const errorHandler: ErrorHandler = (error, context) => {
if (error instanceof FileNotFoundError) {
return `File not found: ${error.path}. Please check the file path.`;
}
if (error instanceof PermissionError) {
return `Access denied. Please contact administrator.`;
}
// Fallback to default message
return `An error occurred while executing ${context.type} "${context.name}": ${error.message}`;
};
const server = new McpServer({
name: 'my-server',
version: '1.0.0',
description: 'Server with custom error handling',
errorHandler
});
import { RateLimiter } from '@tscodex/mcp-sdk';
const server = new McpServer({
name: 'secure-server',
version: '1.0.0',
description: 'Server with security features',
securityOptions: {
rateLimit: {
maxRequests: 100,
windowMs: 60000, // 1 minute
message: 'Too many requests'
},
maxRequestBodySize: 10 * 1024 * 1024, // 10MB
validateRequestSize: true
},
httpOptions: {
requestTimeout: 30000,
keepAliveTimeout: 5000
}
});
const server = new McpServer({
name: 'my-server',
version: '1.0.0',
description: 'Server with custom logger',
logger: {
info: (msg, ...args) => console.log(`[INFO] ${msg}`, ...args),
error: (msg, ...args) => console.error(`[ERROR] ${msg}`, ...args),
warn: (msg, ...args) => console.warn(`[WARN] ${msg}`, ...args),
debug: (msg, ...args) => console.debug(`[DEBUG] ${msg}`, ...args)
}
});
McpServer<TConfig, TRoles, TSession>Main server class.
interface McpServerOptions<TConfig, TRoles, TSession> {
// REQUIRED
name: string; // Unique server name (must start with Latin letter,
// contain only letters, numbers, hyphens, underscores)
version: string; // Version (semver)
description: string; // Server description
// OPTIONAL
id?: string; // Server ID for resource prefix (auto-generated from name)
configSchema?: TSchema; // TypeBox schema for configuration
loadConfig?: () => Promise<TConfig>; // Load local configuration
auth?: AuthConfig<TRoles, TSession, TConfig>; // Authentication config
mcpPath?: string; // MCP endpoint path (default: '/mcp')
corsOptions?: CorsOptions; // CORS configuration
httpOptions?: ServerHttpOptions; // HTTP server options
securityOptions?: ServerSecurityOptions; // Security options
handlerOptions?: ServerHandlerOptions; // Handler timeout options
errorHandler?: ErrorHandler<TConfig, TSession>; // Error handler
logger?: Logger; // Custom logger
}
// Initialization
await server.initialize(): Promise<void>;
await server.start(): Promise<void>;
await server.stop(): Promise<void>;
// Tool registration
server.addTool<TSchemaType>(config: ToolConfig<TSchemaType, TConfig, TRoles, TSession>): void;
// Resource registration
server.addResource(config: ResourceConfig<TConfig, TRoles, TSession>): void;
// Prompt registration
server.addPrompt<TSchemaType>(config: PromptConfig<TSchemaType, TConfig, TRoles, TSession>): void;
// Access methods
server.getConfig(): TConfig;
server.getProjectRoot(): string | undefined;
server.getSession(): TSession | undefined;
server.getTools(): string[];
server.getResources(): string[];
server.getPrompts(): string[];
server.getMetadata(): ServerMetadata; // Get server metadata (tools, resources, prompts, config schema)
// Properties
server.serverId: string; // Server ID (resource prefix)
server.serverPort: number; // Server port
server.serverHost: string; // Server host
server.running: boolean; // Is server running
server.on('initialized', () => {});
server.on('started', (port: number, host: string) => {});
server.on('stopped', () => {});
server.on('error', (error: Error) => {});
server.on('toolRegistered', (name: string) => {});
server.on('toolCalled', (name: string, params: any, result: any) => {});
server.on('toolError', (name: string, params: any, error: Error) => {});
server.on('resourceRegistered', (uri: string) => {});
server.on('resourceRead', (uri: string, result: any) => {});
server.on('resourceError', (uri: string, error: Error) => {});
server.on('promptRegistered', (name: string) => {});
server.on('promptCalled', (name: string, params: any, result: any) => {});
server.on('promptError', (name: string, params: any, error: Error) => {});
server.on('projectRootChanged', (newRoot: string, previousRoot: string) => {});
The SDK is designed to work seamlessly with Cursor/VSCode Extensions.
--meta flag)SDK supports metadata mode for Extension integration. When started with --meta or --metadata flag:
stdout (no logs)stderrUsage:
node dist/index.js --meta
# or
node dist/index.js --metadata
Programmatic usage:
await server.initialize();
const metadata = server.getMetadata();
console.log(JSON.stringify(metadata, null, 2));
Extension automatically passes configuration via environment variables:
MCP_PORT - Server port (default: 3848)MCP_HOST - Server host (default: '0.0.0.0')MCP_PROJECT_ROOT - Workspace root directoryMCP_CONFIG - Configuration as JSON stringMCP_AUTH_TOKEN - Authentication token/key (for auth-enabled servers)MCP_PATH - MCP endpoint path (default: '/mcp')Fallback Support: SDK supports fallback to non-prefixed environment variables for server settings:
MCP_HOST β HOST (if MCP_HOST is not set)MCP_PORT β PORT (if MCP_PORT is not set)MCP_PROJECT_ROOT β CURSOR_WORKSPACE β PROJECT_ROOT (if MCP_PROJECT_ROOT is not set)Priority order: MCP_* env vars β non-prefixed env vars β CLI arguments β defaults
Important: Only environment variables with MCP_ prefix are loaded into application configuration (via loadConfig). This prevents accidental exposure of system environment variables. For example, use MCP_TIMEOUT=5000 instead of TIMEOUT=5000. However, server settings (host, port, project root) support fallback to non-prefixed variables for convenience.
SDK automatically creates endpoints for Extension:
GET /health - Health check with server informationGET /gateway/metadata - Get server metadata (tools, resources, prompts, config schema)POST /gateway/config/project-root - Update project rootGET /gateway/config/current - Get current configuration (public config only)POST /gateway/config - Update configuration dynamically (deep merge)Important: Extension configuration is updated only by restarting the process with new environment variables. The SDK provides read-only access via getConfig().
Handlers receive full configuration (including secrets) in context.config for use in code. SDK provides filterMcpPublicConfig() utility to help handlers return only public MCP parameters (mcp_* keys) in their results.
Example:
import { filterMcpPublicConfig } from '@tscodex/mcp-sdk';
// In your config schema, define public MCP parameters with 'mcp_' prefix
const ConfigSchema = Type.Object({
// Public MCP parameters (safe to return in results)
mcp_timeout: Type.Number({ default: 5000 }),
mcp_api_url: Type.String({ default: 'https://api.example.com' }),
// Private parameters (secrets - use in code but don't return in results)
apiKey: Type.String(), // Secret - use in code, filter before returning
databasePassword: Type.String() // Secret - use in code, filter before returning
});
// In handler
handler: async (params, context) => {
// Full config access (including secrets) - use freely in code
const timeout = context.config.timeout; // β
Available
const apiKey = context.config.apiKey; // β
Available - use for API calls
// If returning config in result, filter to public parameters only
const publicConfig = filterMcpPublicConfig(context.config);
// publicConfig contains only 'mcp_*' keys
return {
content: [{
type: 'text',
text: JSON.stringify(publicConfig) // Safe - only public params
}]
};
}
This design ensures that:
mcp_* keys) can be safely returned in results using filterMcpPublicConfig()When multiple workspaces share a single MCP server process, the SDK supports per-request context via HTTP headers. This allows each request to have its own projectRoot and workspaceId.
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Workspace A β β MCP Gateway β β MCP Server β
β /projects/foo ββββββΆβ (Proxy Layer) ββββββΆβ (Shared) β
βββββββββββββββββββ β β β β
β Adds headers: β β Reads headers β
βββββββββββββββββββ β X-MCP-Project- β β via AsyncLocal β
β Workspace B ββββββΆβ Root β β Storage β
β /projects/bar β β X-MCP-Workspaceβ β β
βββββββββββββββββββ β -Id β β β
βββββββββββββββββββ βββββββββββββββββββ
The SDK recognizes these headers for per-request context:
| Header | Description | Priority |
|---|---|---|
X-MCP-Project-Root | Workspace project root path | Overrides MCP_PROJECT_ROOT env |
X-MCP-Workspace-Id | Workspace identifier (optional) | Informational |
projectRoot is resolved with the following priority:
X-MCP-Project-Root) - highest priorityMCP_PROJECT_ROOT)server.addTool({
name: 'list-files',
schema: Type.Object({}),
handler: async (params, context) => {
// projectRoot automatically reflects per-request header
// or falls back to server-level MCP_PROJECT_ROOT
const root = context.projectRoot;
// workspaceId is available for logging/caching (optional)
const wsId = context.workspaceId;
if (!root) {
return { content: [{ type: 'text', text: 'No project root configured' }] };
}
// Files are resolved relative to the correct workspace
const files = await fs.readdir(root);
return {
content: [{ type: 'text', text: files.join('\n') }]
};
}
});
The SDK uses Node.js AsyncLocalStorage to propagate request context through the async call stack. This allows handlers to access per-request headers even though the official MCP SDK doesn't support custom context in handlers.
// Available exports for advanced usage
import {
getRequestContext, // Get current request context
extractRequestContext, // Extract context from HTTP request
requestContextStorage // AsyncLocalStorage instance
} from '@tscodex/mcp-sdk';
// In custom middleware or transport
const reqContext = extractRequestContext(httpRequest);
// reqContext = { projectRoot?: string, workspaceId?: string }
projectRoot falls back to MCP_PROJECT_ROOT environment variableworkspaceId is undefined when not providedimport { safePath, isPathSafe, sanitizeFilename, RateLimiter } from '@tscodex/mcp-sdk';
// Safe path resolution
const safe = safePath('/workspace', userPath); // Prevents path traversal
// Path validation
if (isPathSafe(userPath)) {
// Safe to use
}
// Filename sanitization
const safe = sanitizeFilename(userFilename);
// Rate limiter
const limiter = new RateLimiter({
maxRequests: 100,
windowMs: 60000
});
import { validateConfig, updateConfig } from '@tscodex/mcp-sdk';
// Validate configuration against schema
const config = validateConfig<Config>(data, schema);
// Deep merge configurations
const merged = updateConfig(defaultConfig, extensionConfig);
Check the examples/ directory for complete examples:
Run examples:
tsx examples/basic-server.ts
tsx examples/with-config.ts
tsx examples/with-auth.ts
1. Extension starts process
β (env vars: MCP_PORT, MCP_HOST, MCP_PROJECT_ROOT, MCP_CONFIG, MCP_AUTH_TOKEN)
2. new McpServer(options)
- Reads port/host/projectRoot from env vars
- Creates HTTP Server
- Creates MCP Server instance
3. server.initialize()
- Loads configuration from MCP_CONFIG
- Calls loadConfig() for local settings
- Merges configurations (Extension takes priority)
- Validates via configSchema
- Loads session if auth is configured
- Filters tools/resources/prompts by access
- Sets up Extension endpoints
- Registers MCP handlers
4. server.addTool/addResource/addPrompt
- Register functionality
5. server.start()
- Starts HTTP Server
- Sets up graceful shutdown handlers
6. Server running
- Handles MCP requests
- Provides Extension endpoints
- Configuration and session available via context
@tscodex/mcp-sdk/
βββ src/
β βββ server.ts # McpServer class
β βββ types.ts # TypeScript types
β βββ config.ts # Configuration management
β βββ transport.ts # HTTP transport
β βββ security.ts # Security utilities
β βββ extension.ts # Extension types
β βββ index.ts # Main exports
βββ examples/ # Example servers
βββ dist/ # Compiled output
safePath() for file operations to prevent path traversalsanitizeFilename()MIT Β© unbywyd
Contributions are welcome! Please feel free to submit a Pull Request.
Version: 0.2.0
Status: Production Ready
FAQs
SDK for creating MCP servers with Extension support
The npm package @tscodex/mcp-sdk receives a total of 401 weekly downloads. As such, @tscodex/mcp-sdk popularity was classified as not popular.
We found that @tscodex/mcp-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
New DoS and source code exposure bugs in React Server Components and Next.js: whatβs affected and how to update safely.

Security News
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.

Security News
GitHub has revoked npm classic tokens for publishing; maintainers must migrate, but OpenJS warns OIDC trusted publishing still has risky gaps for critical projects.