Skip to content

Commit 8f310bd

Browse files
committed
Added https support
1 parent cb86bbd commit 8f310bd

File tree

6 files changed

+144
-23
lines changed

6 files changed

+144
-23
lines changed

mcp-openapi/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ curl http://localhost:4000/info
232232
- **Banking Examples**: Use the pre-configured banking examples in `./examples/` for testing
233233
- **Authentication Testing**: Set environment variables for token passthrough testing
234234
- **Mode Selection**: Choose stdio for IDE integration, HTTP for web APIs
235+
- **Testing**: ⚠️ **Stop `sample-banking-api` server before running tests** - tests expect specific error conditions
235236

236237
## Usage Modes
237238

@@ -800,8 +801,14 @@ mcp-openapi-server --verbose
800801

801802
The project includes comprehensive unit and integration tests:
802803

804+
⚠️ **Important Testing Requirement**:
805+
**Stop the `sample-banking-api` server before running tests**. The integration tests will fail if the banking API server is running because the tests expect specific error conditions that get masked when the real API responds.
806+
803807
```bash
804-
# Run all tests
808+
# FIRST: Stop the sample-banking-api if it's running
809+
# (In the sample-banking-api directory: Ctrl+C or kill the process)
810+
811+
# THEN: Run all tests
805812
npm test
806813
807814
# Run unit tests only

mcp-openapi/src/cli.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ program
2121
.option('--max-tool-name-length <number>', 'Maximum length for generated tool names', '48')
2222
.option('--max-request-size <size>', 'Maximum size for JSON request bodies', '2mb')
2323
.option('--http', 'Run in HTTP server mode instead of stdio', false)
24+
.option('--https', 'Enable HTTPS mode (requires --key-file and --cert-file or --pfx-file)', false)
25+
.option('--https-port <number>', 'Port for HTTPS server mode', '4443')
26+
.option('--key-file <path>', 'Path to private key file for HTTPS')
27+
.option('--cert-file <path>', 'Path to certificate file for HTTPS')
28+
.option('--pfx-file <path>', 'Path to PFX/PKCS12 file for HTTPS (alternative to key/cert)')
29+
.option('--passphrase <passphrase>', 'Passphrase for encrypted private key')
2430
.option('-v, --verbose', 'Enable verbose logging', true)
2531
.action(async (options) => {
2632
const serverOptions: ServerOptions = {
@@ -31,13 +37,20 @@ program
3137
verbose: options.verbose,
3238
...(options.baseUrl && { baseUrl: options.baseUrl }),
3339
...(options.maxToolNameLength && { maxToolNameLength: parseInt(options.maxToolNameLength) }),
34-
...(options.maxRequestSize && { maxRequestSize: options.maxRequestSize })
40+
...(options.maxRequestSize && { maxRequestSize: options.maxRequestSize }),
41+
// HTTPS options
42+
https: options.https,
43+
...(options.httpsPort && { httpsPort: parseInt(options.httpsPort) }),
44+
...(options.keyFile && { keyFile: options.keyFile }),
45+
...(options.certFile && { certFile: options.certFile }),
46+
...(options.pfxFile && { pfxFile: options.pfxFile }),
47+
...(options.passphrase && { passphrase: options.passphrase })
3548
};
3649

3750
const server = new MCPOpenAPIServer(serverOptions);
3851

3952
try {
40-
if (options.http) {
53+
if (options.http || options.https) {
4154
await server.runHttp();
4255
} else {
4356
await server.runStdio();

mcp-openapi/src/httpSetup.ts

Lines changed: 107 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import express from 'express';
2-
import { MCPTool, MCPResource, PromptSpec, OpenAPISpec } from './types.js';
2+
import https from 'https';
3+
import fs from 'fs';
4+
import { MCPTool, MCPResource, PromptSpec, OpenAPISpec, ServerOptions } from './types.js';
35
import { Telemetry } from './telemetry.js';
46
import { PACKAGE_VERSION } from './package-info.js';
57

@@ -10,6 +12,7 @@ export interface HttpSetupContext {
1012
resources: MCPResource[];
1113
prompts: Map<string, PromptSpec>;
1214
telemetry: Telemetry;
15+
options: ServerOptions;
1316
executeTool: (toolName: string, args: any, userContext?: { token?: string }) => Promise<any>;
1417
readResource: (uri: string, userContext?: { token?: string }, resourceParams?: Record<string, any>) => Promise<any>;
1518
getPrompt: (promptName: string, args: any) => Promise<any>;
@@ -162,23 +165,112 @@ export class HttpSetup {
162165
});
163166
}
164167

165-
startServer(port: number): Promise<void> {
166-
return new Promise((resolve) => {
167-
const server = this.context.app.listen(port, () => {
168-
// HTTP server startup - use info level
169-
const address = server.address();
170-
const host = typeof address === 'object' && address ?
171-
(address.family === 'IPv6' ? `[${address.address}]` : address.address) :
172-
'localhost';
168+
startServer(port?: number): Promise<void> {
169+
return new Promise((resolve, reject) => {
170+
const { options } = this.context;
171+
172+
// Determine if HTTPS is enabled and which port to use
173+
const useHttps = options.https;
174+
const serverPort = useHttps ?
175+
(options.httpsPort || 4443) :
176+
(port || options.port || 4000);
177+
178+
if (useHttps) {
179+
// HTTPS server setup
180+
const httpsOptions = this.createHttpsOptions();
181+
if (!httpsOptions) {
182+
reject(new Error('HTTPS enabled but no valid certificate configuration provided'));
183+
return;
184+
}
173185

174-
this.context.telemetry.info(`🚀 MCP OpenAPI Server running on port ${port}`);
175-
this.context.telemetry.info(`📊 Health check: http://${host}:${port}/health`);
176-
this.context.telemetry.info(`ℹ️ Server info: http://${host}:${port}/info`);
186+
const server = https.createServer(httpsOptions, this.context.app);
187+
server.listen(serverPort, () => {
188+
const address = server.address();
189+
const host = typeof address === 'object' && address ?
190+
(address.family === 'IPv6' ? `[${address.address}]` : address.address) :
191+
'localhost';
192+
193+
this.context.telemetry.info(`🔒 MCP OpenAPI HTTPS Server running on port ${serverPort}`);
194+
this.context.telemetry.info(`📊 Health check: https://${host}:${serverPort}/health`);
195+
this.context.telemetry.info(`ℹ️ Server info: https://${host}:${serverPort}/info`);
196+
197+
this.context.telemetry.debug(`📋 Loaded ${this.context.specs.size} specs, ${this.context.tools.length} tools, ${this.context.resources.length} resources, ${this.context.prompts.size} prompts`);
198+
199+
resolve();
200+
});
177201

178-
this.context.telemetry.debug(`📋 Loaded ${this.context.specs.size} specs, ${this.context.tools.length} tools, ${this.context.resources.length} resources, ${this.context.prompts.size} prompts`);
202+
server.on('error', (error) => {
203+
reject(error);
204+
});
205+
} else {
206+
// HTTP server setup (default)
207+
const server = this.context.app.listen(serverPort, () => {
208+
const address = server.address();
209+
const host = typeof address === 'object' && address ?
210+
(address.family === 'IPv6' ? `[${address.address}]` : address.address) :
211+
'localhost';
212+
213+
this.context.telemetry.info(`🚀 MCP OpenAPI Server running on port ${serverPort}`);
214+
this.context.telemetry.info(`📊 Health check: http://${host}:${serverPort}/health`);
215+
this.context.telemetry.info(`ℹ️ Server info: http://${host}:${serverPort}/info`);
216+
217+
this.context.telemetry.debug(`📋 Loaded ${this.context.specs.size} specs, ${this.context.tools.length} tools, ${this.context.resources.length} resources, ${this.context.prompts.size} prompts`);
218+
219+
resolve();
220+
});
179221

180-
resolve();
181-
});
222+
server.on('error', (error) => {
223+
reject(error);
224+
});
225+
}
182226
});
183227
}
228+
229+
private createHttpsOptions(): https.ServerOptions | null {
230+
const { options } = this.context;
231+
232+
try {
233+
// Option 1: PFX/PKCS12 file
234+
if (options.pfxFile) {
235+
if (!fs.existsSync(options.pfxFile)) {
236+
this.context.telemetry.warn(`⚠️ PFX file not found: ${options.pfxFile}`);
237+
return null;
238+
}
239+
240+
const pfx = fs.readFileSync(options.pfxFile);
241+
return {
242+
pfx,
243+
...(options.passphrase && { passphrase: options.passphrase })
244+
};
245+
}
246+
247+
// Option 2: Separate key and certificate files
248+
if (options.keyFile && options.certFile) {
249+
if (!fs.existsSync(options.keyFile)) {
250+
this.context.telemetry.warn(`⚠️ Private key file not found: ${options.keyFile}`);
251+
return null;
252+
}
253+
254+
if (!fs.existsSync(options.certFile)) {
255+
this.context.telemetry.warn(`⚠️ Certificate file not found: ${options.certFile}`);
256+
return null;
257+
}
258+
259+
const key = fs.readFileSync(options.keyFile);
260+
const cert = fs.readFileSync(options.certFile);
261+
262+
return {
263+
key,
264+
cert,
265+
...(options.passphrase && { passphrase: options.passphrase })
266+
};
267+
}
268+
269+
this.context.telemetry.warn('⚠️ HTTPS enabled but no certificate files provided. Use --key-file and --cert-file, or --pfx-file');
270+
return null;
271+
} catch (error) {
272+
this.context.telemetry.warn(`⚠️ Error reading HTTPS certificate files: ${(error as Error).message}`);
273+
return null;
274+
}
275+
}
184276
}

mcp-openapi/src/server.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -835,8 +835,11 @@ export class MCPOpenAPIServer {
835835
}
836836
}
837837

838-
839-
838+
/**
839+
* Reads an MCP resource by URI, handling special server info resource and
840+
* parameter substitution. Supports both exact and template-based resource URIs.
841+
* Returns the resource content as JSON.
842+
*/
840843
private async readResource(uri: string, userContext?: { token?: string }, resourceParams?: Record<string, any>) {
841844
// Handle special server info resource
842845
if (uri === 'mcp-openapi://server/info') {
@@ -861,8 +864,6 @@ export class MCPOpenAPIServer {
861864
throw new Error(`Resource ${uri} not found`);
862865
}
863866

864-
865-
866867
// Parse URI to get spec and path
867868
const [specId, pathAfterProtocol] = uri.split('://');
868869

@@ -1159,6 +1160,7 @@ export class MCPOpenAPIServer {
11591160
resources: this.resources,
11601161
prompts: this.prompts,
11611162
telemetry: this.telemetry,
1163+
options: this.options,
11621164
executeTool: this.executeTool.bind(this),
11631165
readResource: this.readResource.bind(this),
11641166
getPrompt: this.getPrompt.bind(this),

mcp-openapi/src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,11 @@ export interface ServerOptions {
8787
baseUrl?: string;
8888
maxToolNameLength?: number;
8989
maxRequestSize?: string;
90+
// HTTPS configuration
91+
https?: boolean;
92+
httpsPort?: number;
93+
keyFile?: string;
94+
certFile?: string;
95+
pfxFile?: string;
96+
passphrase?: string;
9097
}

mcp-test-client/test-data/prompts.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"id": "fraud_investigation",
55
"name": "Fraud Investigation - Power Company Payment",
66
"description": "Investigate a high-value utility payment using real sample data",
7-
"userPrompt": "I need to investigate a $750 payment made to 'Power Company Australia' (payee_9n8m7l6k5j4i3h2g) from account 9876543210. Can you help me analyze this for potential fraud using the fraud analysis tools?"
7+
"userPrompt": "I need to investigate a $750 payment made to 'Power Company Australia' (payee_9n8m7l6k5j4i3h2g) from account 9876543210 for potential fraud."
88
},
99
{
1010
"id": "loan_recommendation",

0 commit comments

Comments
 (0)