diff --git a/CLAUDE.md b/CLAUDE.md index d147b91..c93fa17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,7 +137,7 @@ Features: - Example tool (`start-notification-stream`) - Example resource (`greeting-resource`) - TypeScript configuration -- Environment variable support for PORT +- Environment variable support for PORT and ALLOWED_HOSTS #### sdk/stateful diff --git a/package-lock.json b/package-lock.json index a27e51c..1d152f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@agentailor/create-mcp-server", - "version": "0.5.2", + "version": "0.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@agentailor/create-mcp-server", - "version": "0.5.2", + "version": "0.5.3", "license": "MIT", "dependencies": { "commander": "^14.0.3", diff --git a/package.json b/package.json index fe23bda..60a0d60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@agentailor/create-mcp-server", - "version": "0.5.2", + "version": "0.5.3", "description": "Create a new MCP (Model Context Protocol) server project", "type": "module", "bin": { diff --git a/src/templates/common/env.example.ts b/src/templates/common/env.example.ts index 166c64d..ab092a5 100644 --- a/src/templates/common/env.example.ts +++ b/src/templates/common/env.example.ts @@ -2,6 +2,14 @@ import type { CommonTemplateOptions } from './types.js'; export function getEnvExampleTemplate(options?: CommonTemplateOptions): string { const withOAuth = options?.withOAuth ?? false; + const isSdk = options?.framework === 'sdk' || !options?.framework; + + const allowedHostsVar = isSdk + ? ` +# Comma-separated list of additional allowed hosts (for deployment behind reverse proxies) +# ALLOWED_HOSTS=my-server.example.com,my-server.us-central1.run.app +` + : ''; const oauthVars = withOAuth ? ` @@ -19,5 +27,5 @@ OAUTH_AUDIENCE=https://your-mcp-server.com : ''; return `PORT=3000 -${oauthVars}`; +${allowedHostsVar}${oauthVars}`; } diff --git a/src/templates/common/templates.test.ts b/src/templates/common/templates.test.ts index 0fd9d8c..54c5733 100644 --- a/src/templates/common/templates.test.ts +++ b/src/templates/common/templates.test.ts @@ -105,5 +105,20 @@ describe('common templates', () => { const template = getEnvExampleTemplate(); expect(template).toContain('PORT='); }); + + it('should include ALLOWED_HOSTS hint for SDK framework', () => { + const template = getEnvExampleTemplate({ framework: 'sdk' }); + expect(template).toContain('ALLOWED_HOSTS'); + }); + + it('should include ALLOWED_HOSTS hint by default (no framework specified)', () => { + const template = getEnvExampleTemplate(); + expect(template).toContain('ALLOWED_HOSTS'); + }); + + it('should NOT include ALLOWED_HOSTS for FastMCP framework', () => { + const template = getEnvExampleTemplate({ framework: 'fastmcp' }); + expect(template).not.toContain('ALLOWED_HOSTS'); + }); }); }); diff --git a/src/templates/sdk/stateful/index.ts b/src/templates/sdk/stateful/index.ts index 94ddee3..51bc379 100644 --- a/src/templates/sdk/stateful/index.ts +++ b/src/templates/sdk/stateful/index.ts @@ -18,14 +18,19 @@ import { getOAuthMetadataUrl, validateOAuthConfig, } from './auth.js';` - : `import { type Request, type Response } from 'express'; + : `import 'dotenv/config'; +import { type Request, type Response } from 'express'; import { randomUUID } from 'node:crypto'; import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; import { getServer } from './server.js';`; - const appSetup = `const app = createMcpExpressApp(); + const appSetup = `const allowedHosts = process.env.ALLOWED_HOSTS?.split(',') ?? []; + +const app = createMcpExpressApp({ + allowedHosts: ['localhost', '127.0.0.1', '[::1]', ...allowedHosts], +}); // Health check endpoint for container orchestration app.get('/health', (_, res) => res.sendStatus(200));`; diff --git a/src/templates/sdk/stateful/readme.ts b/src/templates/sdk/stateful/readme.ts index aa0fcfd..cf8a409 100644 --- a/src/templates/sdk/stateful/readme.ts +++ b/src/templates/sdk/stateful/readme.ts @@ -143,7 +143,7 @@ ${commands.build} ${commands.start} \`\`\` -The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable. +The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable. To allow additional hosts (e.g. when deploying behind a reverse proxy), set \`ALLOWED_HOSTS\` as a comma-separated list. ## Testing with MCP Inspector diff --git a/src/templates/sdk/stateful/templates.test.ts b/src/templates/sdk/stateful/templates.test.ts index 68eab54..ca5548b 100644 --- a/src/templates/sdk/stateful/templates.test.ts +++ b/src/templates/sdk/stateful/templates.test.ts @@ -44,7 +44,7 @@ describe('sdk/stateful templates', () => { it('should use createMcpExpressApp', () => { const template = getIndexTemplate(); expect(template).toContain('createMcpExpressApp'); - expect(template).toContain('const app = createMcpExpressApp()'); + expect(template).toContain('const app = createMcpExpressApp({'); }); it('should configure /mcp endpoint', () => { @@ -56,7 +56,13 @@ describe('sdk/stateful templates', () => { it('should use PORT from environment variable', () => { const template = getIndexTemplate(); - expect(template).toContain('process.env.PORT || 3000'); + expect(template).toContain('process.env.PORT'); + }); + + it('should pass allowedHosts to createMcpExpressApp', () => { + const template = getIndexTemplate(); + expect(template).toContain('allowedHosts'); + expect(template).toContain("ALLOWED_HOSTS?.split(',')"); }); // Stateful-specific tests diff --git a/src/templates/sdk/stateless/index.ts b/src/templates/sdk/stateless/index.ts index b470567..63f639e 100644 --- a/src/templates/sdk/stateless/index.ts +++ b/src/templates/sdk/stateless/index.ts @@ -10,7 +10,11 @@ import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { getServer } from './server.js'; -const app = createMcpExpressApp(); +const allowedHosts = process.env.ALLOWED_HOSTS?.split(',') ?? []; + +const app = createMcpExpressApp({ + allowedHosts: ['localhost', '127.0.0.1', '[::1]', ...allowedHosts], +}); // Health check endpoint for container orchestration app.get('/health', (_, res) => res.sendStatus(200)); diff --git a/src/templates/sdk/stateless/readme.ts b/src/templates/sdk/stateless/readme.ts index cd711fd..0aa3244 100644 --- a/src/templates/sdk/stateless/readme.ts +++ b/src/templates/sdk/stateless/readme.ts @@ -49,7 +49,7 @@ ${commands.build} ${commands.start} \`\`\` -The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable. +The server will start on port 3000 by default. You can change this by setting the \`PORT\` environment variable. To allow additional hosts (e.g. when deploying behind a reverse proxy), set \`ALLOWED_HOSTS\` as a comma-separated list. ## Testing with MCP Inspector diff --git a/src/templates/sdk/stateless/templates.test.ts b/src/templates/sdk/stateless/templates.test.ts index dc0bc6a..d353a7b 100644 --- a/src/templates/sdk/stateless/templates.test.ts +++ b/src/templates/sdk/stateless/templates.test.ts @@ -44,7 +44,7 @@ describe('sdk/stateless templates', () => { it('should use createMcpExpressApp', () => { const template = getIndexTemplate(); expect(template).toContain('createMcpExpressApp'); - expect(template).toContain('const app = createMcpExpressApp()'); + expect(template).toContain('const app = createMcpExpressApp({'); }); it('should configure /mcp endpoint', () => { @@ -56,7 +56,13 @@ describe('sdk/stateless templates', () => { it('should use PORT from environment variable', () => { const template = getIndexTemplate(); - expect(template).toContain('process.env.PORT || 3000'); + expect(template).toContain('process.env.PORT'); + }); + + it('should pass allowedHosts to createMcpExpressApp', () => { + const template = getIndexTemplate(); + expect(template).toContain('allowedHosts'); + expect(template).toContain("ALLOWED_HOSTS?.split(',')"); }); // Stateless-specific tests