@ivotoby/openapi-mcp-server 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -2
- package/bin/mcp-server.js +1 -1
- package/dist/bundle.js +60 -8
- package/dist/cli.js +24145 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,11 +6,186 @@ A Model Context Protocol (MCP) server that exposes OpenAPI endpoints as MCP reso
|
|
|
6
6
|
|
|
7
7
|
## Overview
|
|
8
8
|
|
|
9
|
-
This MCP server
|
|
9
|
+
This MCP server can be used in two ways:
|
|
10
|
+
|
|
11
|
+
1. **CLI Tool**: Use `npx @ivotoby/openapi-mcp-server` directly with command-line arguments for quick setup
|
|
12
|
+
2. **Library**: Import and use the `OpenAPIServer` class in your own Node.js applications for custom implementations
|
|
13
|
+
|
|
14
|
+
The server supports two transport methods:
|
|
10
15
|
|
|
11
16
|
1. **Stdio Transport** (default): For direct integration with AI systems like Claude Desktop that manage MCP connections through standard input/output.
|
|
12
17
|
2. **Streamable HTTP Transport**: For connecting to the server over HTTP, allowing web clients and other HTTP-capable systems to use the MCP protocol.
|
|
13
18
|
|
|
19
|
+
## 🚀 Using as a Library
|
|
20
|
+
|
|
21
|
+
Create dedicated MCP servers for specific APIs by importing and configuring the `OpenAPIServer` class. This approach is ideal for:
|
|
22
|
+
|
|
23
|
+
- **Custom Authentication**: Implement complex authentication patterns with the `AuthProvider` interface
|
|
24
|
+
- **API-Specific Optimizations**: Filter endpoints, customize error handling, and optimize for specific use cases
|
|
25
|
+
- **Distribution**: Package your server as a standalone npm module for easy sharing
|
|
26
|
+
- **Integration**: Embed the server in larger applications or add custom middleware
|
|
27
|
+
|
|
28
|
+
### Basic Library Usage
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { OpenAPIServer } from "@ivotoby/openapi-mcp-server"
|
|
32
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
33
|
+
|
|
34
|
+
const config = {
|
|
35
|
+
name: "my-api-server",
|
|
36
|
+
version: "1.0.0",
|
|
37
|
+
apiBaseUrl: "https://api.example.com",
|
|
38
|
+
openApiSpec: "https://api.example.com/openapi.json",
|
|
39
|
+
specInputMethod: "url" as const,
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: "Bearer your-token",
|
|
42
|
+
"X-API-Key": "your-api-key",
|
|
43
|
+
},
|
|
44
|
+
transportType: "stdio" as const,
|
|
45
|
+
toolsMode: "all" as const,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const server = new OpenAPIServer(config)
|
|
49
|
+
const transport = new StdioServerTransport()
|
|
50
|
+
await server.start(transport)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Advanced Authentication with AuthProvider
|
|
54
|
+
|
|
55
|
+
For APIs with token expiration, refresh requirements, or complex authentication:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { OpenAPIServer, AuthProvider } from "@ivotoby/openapi-mcp-server"
|
|
59
|
+
import { AxiosError } from "axios"
|
|
60
|
+
|
|
61
|
+
class MyAuthProvider implements AuthProvider {
|
|
62
|
+
async getAuthHeaders(): Promise<Record<string, string>> {
|
|
63
|
+
// Called before each request - return fresh headers
|
|
64
|
+
if (this.isTokenExpired()) {
|
|
65
|
+
await this.refreshToken()
|
|
66
|
+
}
|
|
67
|
+
return { Authorization: `Bearer ${this.token}` }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async handleAuthError(error: AxiosError): Promise<boolean> {
|
|
71
|
+
// Called on 401/403 errors - return true to retry
|
|
72
|
+
if (error.response?.status === 401) {
|
|
73
|
+
await this.refreshToken()
|
|
74
|
+
return true // Retry the request
|
|
75
|
+
}
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const authProvider = new MyAuthProvider()
|
|
81
|
+
const config = {
|
|
82
|
+
// ... other config
|
|
83
|
+
authProvider: authProvider, // Use AuthProvider instead of static headers
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**📁 See the [examples/](./examples/) directory for complete, runnable examples including:**
|
|
88
|
+
|
|
89
|
+
- Basic library usage with static authentication
|
|
90
|
+
- AuthProvider implementations for different scenarios
|
|
91
|
+
- Real-world Beatport API integration
|
|
92
|
+
- Production-ready packaging patterns
|
|
93
|
+
|
|
94
|
+
## 🔐 Dynamic Authentication with AuthProvider
|
|
95
|
+
|
|
96
|
+
The `AuthProvider` interface enables sophisticated authentication scenarios that static headers cannot handle:
|
|
97
|
+
|
|
98
|
+
### Key Features
|
|
99
|
+
|
|
100
|
+
- **Dynamic Headers**: Fresh authentication headers for each request
|
|
101
|
+
- **Token Expiration Handling**: Automatic detection and handling of expired tokens
|
|
102
|
+
- **Authentication Error Recovery**: Retry logic for recoverable authentication failures
|
|
103
|
+
- **Custom Error Messages**: Provide clear, actionable guidance to users
|
|
104
|
+
|
|
105
|
+
### AuthProvider Interface
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
interface AuthProvider {
|
|
109
|
+
/**
|
|
110
|
+
* Get authentication headers for the current request
|
|
111
|
+
* Called before each API request to get fresh headers
|
|
112
|
+
*/
|
|
113
|
+
getAuthHeaders(): Promise<Record<string, string>>
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Handle authentication errors from API responses
|
|
117
|
+
* Called when the API returns 401 or 403 errors
|
|
118
|
+
* Return true to retry the request, false otherwise
|
|
119
|
+
*/
|
|
120
|
+
handleAuthError(error: AxiosError): Promise<boolean>
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Common Patterns
|
|
125
|
+
|
|
126
|
+
#### Automatic Token Refresh
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
class RefreshableAuthProvider implements AuthProvider {
|
|
130
|
+
async getAuthHeaders(): Promise<Record<string, string>> {
|
|
131
|
+
if (this.isTokenExpired()) {
|
|
132
|
+
await this.refreshToken()
|
|
133
|
+
}
|
|
134
|
+
return { Authorization: `Bearer ${this.accessToken}` }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async handleAuthError(error: AxiosError): Promise<boolean> {
|
|
138
|
+
if (error.response?.status === 401) {
|
|
139
|
+
await this.refreshToken()
|
|
140
|
+
return true // Retry with fresh token
|
|
141
|
+
}
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Manual Token Management (e.g., Beatport)
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
class ManualTokenAuthProvider implements AuthProvider {
|
|
151
|
+
async getAuthHeaders(): Promise<Record<string, string>> {
|
|
152
|
+
if (!this.token || this.isTokenExpired()) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"Token expired. Please get a new token from your browser:\n" +
|
|
155
|
+
"1. Go to the API website and log in\n" +
|
|
156
|
+
"2. Open browser dev tools (F12)\n" +
|
|
157
|
+
"3. Copy the Authorization header from any API request\n" +
|
|
158
|
+
"4. Update your token using updateToken()",
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
return { Authorization: `Bearer ${this.token}` }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
updateToken(token: string): void {
|
|
165
|
+
this.token = token
|
|
166
|
+
this.tokenExpiry = new Date(Date.now() + 3600000) // 1 hour
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### API Key Authentication
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
class ApiKeyAuthProvider implements AuthProvider {
|
|
175
|
+
constructor(private apiKey: string) {}
|
|
176
|
+
|
|
177
|
+
async getAuthHeaders(): Promise<Record<string, string>> {
|
|
178
|
+
return { "X-API-Key": this.apiKey }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async handleAuthError(error: AxiosError): Promise<boolean> {
|
|
182
|
+
throw new Error("API key authentication failed. Please check your key.")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**📖 For detailed AuthProvider documentation and examples, see [docs/auth-provider-guide.md](./docs/auth-provider-guide.md)**
|
|
188
|
+
|
|
14
189
|
## Quick Start for Users
|
|
15
190
|
|
|
16
191
|
### Option 1: Using with Claude Desktop (Stdio Transport)
|
|
@@ -353,6 +528,18 @@ To see debug logs:
|
|
|
353
528
|
**Q: What is a "tool"?**
|
|
354
529
|
A: A tool corresponds to a single API endpoint derived from your OpenAPI specification, exposed as an MCP resource.
|
|
355
530
|
|
|
531
|
+
**Q: How can I use this package in my own project?**
|
|
532
|
+
A: You can import the `OpenAPIServer` class and use it as a library in your Node.js application. This allows you to create dedicated MCP servers for specific APIs with custom authentication, filtering, and error handling. See the [examples/](./examples/) directory for complete implementations.
|
|
533
|
+
|
|
534
|
+
**Q: What's the difference between using the CLI and using it as a library?**
|
|
535
|
+
A: The CLI is great for quick setup and testing, while the library approach allows you to create dedicated packages for specific APIs, implement custom authentication with `AuthProvider`, add custom logic, and distribute your server as a standalone npm module.
|
|
536
|
+
|
|
537
|
+
**Q: How do I handle APIs with expiring tokens?**
|
|
538
|
+
A: Use the `AuthProvider` interface instead of static headers. AuthProvider allows you to implement dynamic authentication with token refresh, expiration handling, and custom error recovery. See the AuthProvider examples for different patterns.
|
|
539
|
+
|
|
540
|
+
**Q: What is AuthProvider and when should I use it?**
|
|
541
|
+
A: `AuthProvider` is an interface for dynamic authentication that gets fresh headers before each request and handles authentication errors. Use it when your API has expiring tokens, requires token refresh, or needs complex authentication logic that static headers can't handle.
|
|
542
|
+
|
|
356
543
|
**Q: How do I filter which tools are loaded?**
|
|
357
544
|
A: Use the `--tool`, `--tag`, `--resource`, and `--operation` flags, or set `TOOLS_MODE=dynamic` for meta-tools only.
|
|
358
545
|
|
|
@@ -360,7 +547,7 @@ A: Use the `--tool`, `--tag`, `--resource`, and `--operation` flags, or set `TOO
|
|
|
360
547
|
A: Dynamic mode provides meta-tools (`list-api-endpoints`, `get-api-endpoint-schema`, `invoke-api-endpoint`) to inspect and interact with endpoints without preloading all operations, which is useful for large or changing APIs.
|
|
361
548
|
|
|
362
549
|
**Q: How do I specify custom headers for API requests?**
|
|
363
|
-
A: Use the `--headers` flag or `API_HEADERS` environment variable with `key:value` pairs separated by commas.
|
|
550
|
+
A: Use the `--headers` flag or `API_HEADERS` environment variable with `key:value` pairs separated by commas for CLI usage. For library usage, use the `headers` config option or implement an `AuthProvider` for dynamic headers.
|
|
364
551
|
|
|
365
552
|
**Q: Which transport methods are supported?**
|
|
366
553
|
A: The server supports stdio transport (default) for integration with AI systems and HTTP transport (with streaming via SSE) for web clients.
|
|
@@ -371,6 +558,9 @@ A: The server fully resolves `$ref` references in parameters and schemas, preser
|
|
|
371
558
|
**Q: What happens when parameter names conflict with request body properties?**
|
|
372
559
|
A: The server detects naming conflicts and automatically prefixes body property names with `body_` to avoid collisions, ensuring all properties are accessible.
|
|
373
560
|
|
|
561
|
+
**Q: Can I package my MCP server for distribution?**
|
|
562
|
+
A: Yes! When using the library approach, you can create a dedicated npm package for your API. See the Beatport example for a complete implementation that can be packaged and distributed as `npx your-api-mcp-server`.
|
|
563
|
+
|
|
374
564
|
**Q: Where can I find development and contribution guidelines?**
|
|
375
565
|
A: See the "For Developers" section above for commands (`npm run build`, `npm run dev`, etc) and pull request workflow.
|
|
376
566
|
|
package/bin/mcp-server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "../dist/
|
|
2
|
+
import "../dist/cli.js"
|
package/dist/bundle.js
CHANGED
|
@@ -18257,22 +18257,46 @@ var {
|
|
|
18257
18257
|
mergeConfig: mergeConfig2
|
|
18258
18258
|
} = axios_default;
|
|
18259
18259
|
|
|
18260
|
+
// src/auth-provider.ts
|
|
18261
|
+
function isAuthError(error) {
|
|
18262
|
+
return error.response?.status === 401 || error.response?.status === 403;
|
|
18263
|
+
}
|
|
18264
|
+
var StaticAuthProvider = class {
|
|
18265
|
+
constructor(headers = {}) {
|
|
18266
|
+
this.headers = headers;
|
|
18267
|
+
}
|
|
18268
|
+
async getAuthHeaders() {
|
|
18269
|
+
return { ...this.headers };
|
|
18270
|
+
}
|
|
18271
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18272
|
+
async handleAuthError(_error) {
|
|
18273
|
+
return false;
|
|
18274
|
+
}
|
|
18275
|
+
};
|
|
18276
|
+
|
|
18260
18277
|
// src/api-client.ts
|
|
18261
18278
|
var ApiClient = class {
|
|
18279
|
+
axiosInstance;
|
|
18280
|
+
toolsMap = /* @__PURE__ */ new Map();
|
|
18281
|
+
authProvider;
|
|
18262
18282
|
/**
|
|
18263
18283
|
* Create a new API client
|
|
18264
18284
|
*
|
|
18265
18285
|
* @param baseUrl - Base URL for the API
|
|
18266
|
-
* @param
|
|
18286
|
+
* @param authProviderOrHeaders - AuthProvider instance or static headers for backward compatibility
|
|
18267
18287
|
*/
|
|
18268
|
-
constructor(baseUrl,
|
|
18269
|
-
this.headers = headers;
|
|
18288
|
+
constructor(baseUrl, authProviderOrHeaders) {
|
|
18270
18289
|
this.axiosInstance = axios_default.create({
|
|
18271
18290
|
baseURL: baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`
|
|
18272
18291
|
});
|
|
18292
|
+
if (!authProviderOrHeaders) {
|
|
18293
|
+
this.authProvider = new StaticAuthProvider();
|
|
18294
|
+
} else if (typeof authProviderOrHeaders === "object" && !("getAuthHeaders" in authProviderOrHeaders)) {
|
|
18295
|
+
this.authProvider = new StaticAuthProvider(authProviderOrHeaders);
|
|
18296
|
+
} else {
|
|
18297
|
+
this.authProvider = authProviderOrHeaders;
|
|
18298
|
+
}
|
|
18273
18299
|
}
|
|
18274
|
-
axiosInstance;
|
|
18275
|
-
toolsMap = /* @__PURE__ */ new Map();
|
|
18276
18300
|
/**
|
|
18277
18301
|
* Set the available tools for the client
|
|
18278
18302
|
*
|
|
@@ -18298,6 +18322,17 @@ var ApiClient = class {
|
|
|
18298
18322
|
* @returns The API response data
|
|
18299
18323
|
*/
|
|
18300
18324
|
async executeApiCall(toolId, params) {
|
|
18325
|
+
return this.executeApiCallWithRetry(toolId, params, false);
|
|
18326
|
+
}
|
|
18327
|
+
/**
|
|
18328
|
+
* Execute an API call with optional retry on auth error
|
|
18329
|
+
*
|
|
18330
|
+
* @param toolId - The tool ID in format METHOD-path-parts
|
|
18331
|
+
* @param params - Parameters for the API call
|
|
18332
|
+
* @param isRetry - Whether this is a retry attempt
|
|
18333
|
+
* @returns The API response data
|
|
18334
|
+
*/
|
|
18335
|
+
async executeApiCallWithRetry(toolId, params, isRetry) {
|
|
18301
18336
|
try {
|
|
18302
18337
|
const { method, path } = this.parseToolId(toolId);
|
|
18303
18338
|
const toolDef = this.getToolDefinition(toolId);
|
|
@@ -18342,10 +18377,11 @@ var ApiClient = class {
|
|
|
18342
18377
|
}
|
|
18343
18378
|
}
|
|
18344
18379
|
}
|
|
18380
|
+
const authHeaders = await this.authProvider.getAuthHeaders();
|
|
18345
18381
|
const config = {
|
|
18346
18382
|
method: method.toLowerCase(),
|
|
18347
18383
|
url: resolvedPath,
|
|
18348
|
-
headers:
|
|
18384
|
+
headers: authHeaders
|
|
18349
18385
|
};
|
|
18350
18386
|
if (["get", "delete", "head", "options"].includes(method.toLowerCase())) {
|
|
18351
18387
|
config.params = this.processQueryParams(paramsCopy);
|
|
@@ -18357,6 +18393,16 @@ var ApiClient = class {
|
|
|
18357
18393
|
} catch (error) {
|
|
18358
18394
|
if (axios_default.isAxiosError(error)) {
|
|
18359
18395
|
const axiosError = error;
|
|
18396
|
+
if (!isRetry && isAuthError(axiosError)) {
|
|
18397
|
+
try {
|
|
18398
|
+
const shouldRetry = await this.authProvider.handleAuthError(axiosError);
|
|
18399
|
+
if (shouldRetry) {
|
|
18400
|
+
return this.executeApiCallWithRetry(toolId, params, true);
|
|
18401
|
+
}
|
|
18402
|
+
} catch (authHandlerError) {
|
|
18403
|
+
throw authHandlerError;
|
|
18404
|
+
}
|
|
18405
|
+
}
|
|
18360
18406
|
throw new Error(
|
|
18361
18407
|
`API request failed: ${axiosError.message}${axiosError.response ? ` (${axiosError.response.status}: ${typeof axiosError.response.data === "object" ? JSON.stringify(axiosError.response.data) : axiosError.response.data})` : ""}`
|
|
18362
18408
|
);
|
|
@@ -18413,7 +18459,8 @@ var OpenAPIServer = class {
|
|
|
18413
18459
|
}
|
|
18414
18460
|
);
|
|
18415
18461
|
this.toolsManager = new ToolsManager(config);
|
|
18416
|
-
|
|
18462
|
+
const authProviderOrHeaders = config.authProvider || new StaticAuthProvider(config.headers);
|
|
18463
|
+
this.apiClient = new ApiClient(config.apiBaseUrl, authProviderOrHeaders);
|
|
18417
18464
|
this.initializeHandlers();
|
|
18418
18465
|
}
|
|
18419
18466
|
/**
|
|
@@ -24049,14 +24096,19 @@ async function main() {
|
|
|
24049
24096
|
process.exit(1);
|
|
24050
24097
|
}
|
|
24051
24098
|
}
|
|
24052
|
-
|
|
24099
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
24100
|
+
main();
|
|
24101
|
+
}
|
|
24053
24102
|
export {
|
|
24054
24103
|
ApiClient,
|
|
24055
24104
|
OpenAPIServer,
|
|
24056
24105
|
OpenAPISpecLoader,
|
|
24106
|
+
StaticAuthProvider,
|
|
24057
24107
|
StreamableHttpServerTransport,
|
|
24058
24108
|
ToolsManager,
|
|
24109
|
+
isAuthError,
|
|
24059
24110
|
loadConfig,
|
|
24111
|
+
main,
|
|
24060
24112
|
parseHeaders
|
|
24061
24113
|
};
|
|
24062
24114
|
/*! Bundled license information:
|