Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- passthrough-auth option to pass through auth info in MCP request headers to the API, as specified by the openapi spec.

## [3.2.0] - 2025-08-24

### Added
Expand Down
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,18 @@ openapi-mcp-generator --input path/to/openapi.json --output path/to/output/dir -

### CLI Options

| Option | Alias | Description | Default |
| ------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
| `--input` | `-i` | Path or URL to OpenAPI specification (YAML or JSON) | **Required** |
| `--output` | `-o` | Directory to output the generated MCP project | **Required** |
| `--server-name` | `-n` | Name of the MCP server (`package.json:name`) | OpenAPI title or `mcp-api-server` |
| `--server-version` | `-v` | Version of the MCP server (`package.json:version`) | OpenAPI version or `1.0.0` |
| `--base-url` | `-b` | Base URL for API requests. Required if OpenAPI `servers` missing or ambiguous. | Auto-detected if possible |
| `--transport` | `-t` | Transport mode: `"stdio"` (default), `"web"`, or `"streamable-http"` | `"stdio"` |
| `--port` | `-p` | Port for web-based transports | `3000` |
| `--default-include` | | Default behavior for x-mcp filtering. Accepts `true` or `false` (case-insensitive). `true` = include by default, `false` = exclude by default. | `true` |
| `--force` | | Overwrite existing files in the output directory without confirmation | `false` |
| Option | Alias | Description | Default |
| -------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
| `--input` | `-i` | Path or URL to OpenAPI specification (YAML or JSON) | **Required** |
| `--output` | `-o` | Directory to output the generated MCP project | **Required** |
| `--server-name` | `-n` | Name of the MCP server (`package.json:name`) | OpenAPI title or `mcp-api-server` |
| `--server-version` | `-v` | Version of the MCP server (`package.json:version`) | OpenAPI version or `1.0.0` |
| `--base-url` | `-b` | Base URL for API requests. Required if OpenAPI `servers` missing or ambiguous. | Auto-detected if possible |
| `--transport` | `-t` | Transport mode: `"stdio"` (default), `"web"`, or `"streamable-http"` | `"stdio"` |
| `--port` | `-p` | Port for web-based transports | `3000` |
| `--default-include` | | Default behavior for x-mcp filtering. Accepts `true` or `false` (case-insensitive). `true` = include by default, `false` = exclude by default. | `true` |
| `--force` | | Overwrite existing files in the output directory without confirmation | `false` |
| `--passthrough-auth` | | pass through auth info in MCP request headers to the API, as specified by the openapi spec. | `false` |

## 📦 Programmatic API

Expand Down
38 changes: 29 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"typescript": "^5.9.2"
},
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.10.2",
"@modelcontextprotocol/sdk": "^1.17.4",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

SDK peer bump looks good; ensure version consistency across the repo and generated projects.

Upgrading the peer dependency to @modelcontextprotocol/sdk ^1.17.4 aligns with the passthrough-auth changes that rely on newer SDK types. No issues spotted here.

Run this to verify all references now target ^1.17.4 and avoid mixed versions:


🏁 Script executed:

#!/bin/bash
# Find all occurrences of modelcontextprotocol/sdk versions
rg -nP "@modelcontextprotocol/sdk[^\\n]*" -S -C2

Length of output: 12152


Ensure example projects also bump to @modelcontextprotocol/sdk ^1.17.4

The root package.json and generator’s package-json.ts correctly use ^1.17.4, but several example projects still depend on older versions (v1.9.0 and v1.10.0). To maintain consistency across the repo and avoid confusing version drift, please update the following:

• examples/pet-store-streamable-http/package.json
– Change "@modelcontextprotocol/sdk": "^1.10.0" to "^1.17.4"

• examples/pet-store-sse/package.json
– Change "@modelcontextprotocol/sdk": "^1.10.0" to "^1.17.4"

• examples/petstore-mcp/package.json
– Change "@modelcontextprotocol/sdk": "^1.9.0" to "^1.17.4"

After bumping, regenerate or commit updated lockfiles in each example directory to ensure that all transitive references align with SDK v1.17.4.

🤖 Prompt for AI Agents
In package.json (around line 65) and the three example package.json files
(examples/pet-store-streamable-http/package.json,
examples/pet-store-sse/package.json, examples/petstore-mcp/package.json), update
the @modelcontextprotocol/sdk dependency from the older versions (^1.10.0 and
^1.9.0) to "^1.17.4"; then run the package manager in each example directory
(npm install / pnpm install / yarn install as appropriate) to regenerate the
lockfile and commit the updated package.json and corresponding lockfile(s) so
transitive dependencies align with SDK v1.17.4.

"json-schema-to-zod": "^2.6.1",
"zod": "^3.24.3"
}
Expand Down
2 changes: 1 addition & 1 deletion src/generator/package-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function generatePackageJson(
node: '>=20.0.0',
},
dependencies: {
'@modelcontextprotocol/sdk': '^1.10.0',
'@modelcontextprotocol/sdk': '^1.17.4',
axios: '^1.9.0',
dotenv: '^16.4.5',
zod: '^3.24.3',
Expand Down
8 changes: 6 additions & 2 deletions src/generator/server-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function generateMcpServerCode(
);

// Generate code for request handlers
const callToolHandlerCode = generateCallToolHandler();
const callToolHandlerCode = generateCallToolHandler(options.passthroughAuth);
const listToolsHandlerCode = generateListToolsHandler();

// Determine which transport to include
Expand Down Expand Up @@ -99,8 +99,12 @@ import {
ListToolsRequestSchema,
type Tool,
type CallToolResult,
type CallToolRequest
type CallToolRequest,
ServerRequest,
ServerNotification,
IsomorphicHeaders
} from "@modelcontextprotocol/sdk/types.js";${transportImport}
import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';

import { z, ZodError } from 'zod';
import { jsonSchemaToZod } from 'json-schema-to-zod';
Expand Down
6 changes: 3 additions & 3 deletions src/generator/web-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { serve } from '@hono/node-server';
import { streamSSE } from 'hono/streaming';
import { v4 as uuid } from 'uuid';
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { JSONRPCMessage, JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
import { JSONRPCMessage, JSONRPCMessageSchema, MessageExtraInfo } from "@modelcontextprotocol/sdk/types.js";
import type { Context } from 'hono';
import type { SSEStreamingApi } from 'hono/streaming';
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
Expand All @@ -37,7 +37,7 @@ private messageUrl: string;

onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;

constructor(messageUrl: string, stream: SSEStreamingApi) {
this._sessionId = uuid();
Expand Down Expand Up @@ -105,7 +105,7 @@ async handlePostMessage(c: Context): Promise<Response> {

// Forward to the message handler
if (this.onmessage) {
this.onmessage(parsedMessage);
this.onmessage(parsedMessage, {requestInfo: {headers: c.req.header()}});
return c.text('Accepted', 202);
} else {
return c.text('No message handler defined', 500);
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ program
},
true
)
.option('--passthrough-auth', 'Pass through authentication headers to the API')
.option('--force', 'Overwrite existing files without prompting')
.version(pkg.version) // Match package.json version
.action((options) => {
Expand Down
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface CliOptions {
* false = exclude by default unless x-mcp explicitly enables.
*/
defaultInclude?: boolean;
/** Whether to pass through authentication headers to the API */
passthroughAuth?: boolean;
}

/**
Expand Down
7 changes: 4 additions & 3 deletions src/utils/code-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
*
* @returns Generated code for the call tool handler
*/
export function generateCallToolHandler(): string {
export function generateCallToolHandler(passthroughAuth: boolean | undefined): string {
return `
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest): Promise<CallToolResult> => {
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest, extra: RequestHandlerExtra<ServerRequest, ServerNotification>): Promise<CallToolResult> => {
const { name: toolName, arguments: toolArgs } = request.params;
const toolDefinition = toolDefinitionMap.get(toolName);
if (!toolDefinition) {
console.error(\`Error: Unknown tool requested: \${toolName}\`);
return { content: [{ type: "text", text: \`Error: Unknown tool requested: \${toolName}\` }] };
}
return await executeApiTool(toolName, toolDefinition, toolArgs ?? {}, securitySchemes);
let sessionHeaders = ${passthroughAuth ? 'extra.requestInfo?.headers' : 'undefined'};
return await executeApiTool(toolName, toolDefinition, toolArgs ?? {}, securitySchemes, sessionHeaders);
});
`;
}
Expand Down
30 changes: 23 additions & 7 deletions src/utils/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ export function generateExecuteApiToolFunction(

// Generate security handling code for checking, applying security
const securityCode = `
function getHeaderValue(headers: IsomorphicHeaders | undefined, key: string): string | undefined {
const value = headers?.[key];
return Array.isArray(value) ? value[0] : value;
}

// Apply security requirements if available
// Security requirements use OR between array items and AND within each object
const appliedSecurity = definition.securityRequirements?.find(req => {
Expand All @@ -218,17 +223,18 @@ export function generateExecuteApiToolFunction(

// API Key security (header, query, cookie)
if (scheme.type === 'apiKey') {
return !!process.env[\`API_KEY_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
return !!(process.env[\`API_KEY_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`] || getHeaderValue(sessionHeaders,scheme.name.toLowerCase()));
}

// HTTP security (basic, bearer)
if (scheme.type === 'http') {
if (scheme.scheme?.toLowerCase() === 'bearer') {
return !!process.env[\`BEARER_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
return !!(process.env[\`BEARER_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`] || getHeaderValue(sessionHeaders,'authorization')?.startsWith('Bearer '));
}
else if (scheme.scheme?.toLowerCase() === 'basic') {
return !!process.env[\`BASIC_USERNAME_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`] &&
!!process.env[\`BASIC_PASSWORD_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
return (!!(process.env[\`BASIC_USERNAME_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`] &&
!!process.env[\`BASIC_PASSWORD_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`]) ||
getHeaderValue(sessionHeaders,'authorization')?.startsWith('Basic '));
}
}

Expand All @@ -253,7 +259,7 @@ export function generateExecuteApiToolFunction(

// OpenID Connect
if (scheme.type === 'openIdConnect') {
return !!process.env[\`OPENID_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
return !!(process.env[\`OPENID_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`] || getHeaderValue(sessionHeaders,'authorization')?.startsWith('Bearer '));
}

return false;
Expand All @@ -268,7 +274,7 @@ export function generateExecuteApiToolFunction(

// API Key security
if (scheme?.type === 'apiKey') {
const apiKey = process.env[\`API_KEY_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
const apiKey = (process.env[\`API_KEY_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`] || getHeaderValue(sessionHeaders,scheme.name.toLowerCase()));
if (apiKey) {
if (scheme.in === 'header') {
headers[scheme.name.toLowerCase()] = apiKey;
Expand All @@ -292,6 +298,9 @@ export function generateExecuteApiToolFunction(
if (token) {
headers['authorization'] = \`Bearer \${token}\`;
console.error(\`Applied Bearer token for '\${schemeName}'\`);
} else if (getHeaderValue(sessionHeaders,'authorization')?.startsWith('Bearer ')) {
headers['authorization'] = getHeaderValue(sessionHeaders,'authorization')!;
console.error(\`Applied Bearer token for '\${schemeName}' from session headers\`);
}
}
else if (scheme.scheme?.toLowerCase() === 'basic') {
Expand All @@ -300,6 +309,9 @@ export function generateExecuteApiToolFunction(
if (username && password) {
headers['authorization'] = \`Basic \${Buffer.from(\`\${username}:\${password}\`).toString('base64')}\`;
console.error(\`Applied Basic authentication for '\${schemeName}'\`);
} else if (getHeaderValue(sessionHeaders,'authorization')?.startsWith('Basic ')) {
headers['authorization'] = getHeaderValue(sessionHeaders,'authorization')!;
console.error(\`Applied Basic authentication for '\${schemeName}' from session headers\`);
}
}
}
Expand Down Expand Up @@ -338,6 +350,9 @@ export function generateExecuteApiToolFunction(
if (scopes && scopes.length > 0) {
console.error(\`Requested scopes: \${scopes.join(', ')}\`);
}
} else if (getHeaderValue(sessionHeaders,'authorization')?.startsWith('Bearer ')) {
headers['authorization'] = getHeaderValue(sessionHeaders,'authorization')!;
console.error(\`Applied OpenID Connect token for '\${schemeName}' from session headers\`);
}
}
}
Expand Down Expand Up @@ -379,7 +394,8 @@ async function executeApiTool(
toolName: string,
definition: McpToolDefinition,
toolArgs: JsonObject,
allSecuritySchemes: Record<string, any>
allSecuritySchemes: Record<string, any>,
sessionHeaders: IsomorphicHeaders | undefined
): Promise<CallToolResult> {
try {
// Validate arguments against the input schema
Expand Down