Developer guide - ivo-toby/mcp-openapi-server GitHub Wiki
This guide provides comprehensive documentation for developers working with or contributing to the @ivotoby/openapi-mcp-server
codebase. It covers key concepts, internal architecture, and development workflows.
- Architecture Overview
- Core Concepts
- Tool ID System
- Tool Name Abbreviation System
- Resource Name Extraction
- Filtering System
- Authentication System
- OpenAPI Processing
- Development Workflow
- Testing Guidelines
- Contributing
The MCP OpenAPI Server consists of several key components:
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β OpenAPIServer β β ToolsManager β β OpenAPISpecLoaderβ
β β β β β β
β - Server setup βββββΆβ - Tool filteringβββββΆβ - Spec parsing β
β - Transport mgmtβ β - Tool lookup β β - Tool creation β
β - Request routingβ β - Tool metadata β β - Schema processingβ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β ApiClient β β AuthProvider β β Tool ID Utils β
β β β β β β
β - HTTP requests β β - Dynamic auth β β - ID generation β
β - Parameter β β - Token refresh β β - ID parsing β
β handling β β - Error recoveryβ β - Hyphen escapingβ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
The server extends the standard MCP Tool
interface with metadata for efficient filtering:
interface ExtendedTool extends Tool {
/** OpenAPI tags associated with this tool's operation */
tags?: string[]
/** HTTP method for this tool (GET, POST, etc.) */
httpMethod?: string
/** Primary resource name extracted from the path */
resourceName?: string
/** Original OpenAPI path before toolId conversion */
originalPath?: string
}
This metadata is computed during tool creation and enables efficient filtering without re-parsing tool IDs or accessing the raw OpenAPI specification.
The server supports three distinct tools loading modes:
-
"all"
(default): Load all tools from the OpenAPI spec, applying any specified filters -
"dynamic"
: Load only meta-tools for API exploration (list-api-endpoints
,get-api-endpoint-schema
,invoke-api-endpoint
) -
"explicit"
: Load only tools explicitly listed inincludeTools
, ignoring all other filters
Tool IDs uniquely identify API endpoints and have the format: METHOD::pathPart
Examples:
-
GET::users
β GET /users -
POST::api__v1__users
β POST /api/v1/users -
GET::api__resource-name__items
β GET /api/resource-name/items
Critical for developers: The tool ID system uses double underscores (__
) as a separator for path segments. This approach is robust and avoids the complexities of hyphen-escaping schemes.
OpenAPI paths (e.g., /api/v1/users
, /api/resource-name/items
) need to be converted into a flat string format for tool IDs. A clear separator is needed to distinguish between different segments of the original path. Legitimate hyphens within path segments (e.g., resource-name
) must be preserved.
-
Double underscores (
__
) are used to replace slashes (/
) from the original path. - Legitimate hyphens within path segments are preserved as-is.
Original Path | Tool ID | Parsed Back |
---|---|---|
/users |
GET::users |
/users |
/api/v1/users |
GET::api__v1__users |
/api/v1/users |
/api/resource-name/items |
GET::api__resource-name__items |
/api/resource-name/items |
/user-profile/data |
GET::user-profile__data |
/user-profile/data |
/a_b/c-d/e_f-g |
GET::a_b__c-d__e_f-g |
/a_b/c-d/e_f-g |
Generation (generateToolId
):
const cleanPath = path
.replace(/^\//, "") // Remove leading slash
.replace(/\/+/g, "/") // Collapse multiple consecutive slashes to single slash
.replace(/\{([^}]+)\}/g, "$1") // Remove curly braces from path params
.replace(/\//g, "__") // Convert slashes to double underscores
const sanitizedPath = sanitizeForToolId(cleanPath) // Apply further sanitization
return `${method.toUpperCase()}::${sanitizedPath}`
Parsing (parseToolId
):
const [method, pathPart] = toolId.split("::", 2)
// Simply replace double underscores with slashes
const path = pathPart.replace(/__/g, "/")
return { method, path: "/" + path }
Tool IDs are sanitized by the sanitizeForToolId
helper function to ensure they contain only safe characters [A-Za-z0-9_-]
. The process involves:
-
Removing disallowed characters: Any character not in
A-Za-z0-9_-
is removed. -
Collapsing underscores: Sequences of three or more underscores (
___
,____
, etc.) are collapsed to a double underscore (__
). This preserves the__
path separator if an original path segment happened to contain multiple underscores that were then joined by__
. -
Trimming: Leading or trailing underscores (
_
) and hyphens (-
) are removed from the final sanitized path part. -
Original path structure: Note that operations like collapsing multiple slashes (
//
to/
) in the original path happen before sanitization during thegenerateToolId
's path cleaning phase.
(This section can be removed as the previous limitations were specific to the hyphen-escaping scheme. The double underscore system is much simpler and avoids those issues. If new limitations are identified, they can be added here.)
Tool names are generated from OpenAPI operationId
, summary
, or fallback patterns and must be β€64 characters with format [a-z0-9-]+
.
The abbreviation system follows a multi-step process:
- Initial Sanitization: Replace non-alphanumeric characters with hyphens
- Word Splitting: Split by underscores, camelCase, and numbers
- Common Word Removal: Remove words like "controller", "api", "service"
- Standard Abbreviations: Apply predefined abbreviations
- Vowel Removal: For long words (>5 chars) that aren't abbreviations
- Truncation & Hashing: Add hash suffix if original was long or result exceeds limit
const REVISED_COMMON_WORDS_TO_REMOVE = [
"controller",
"api",
"operation",
"handler",
"endpoint",
"action",
"perform",
"execute",
"retrieve",
"specify",
"for",
"and",
"the",
"with",
"from",
"into",
"onto",
"out",
]
const WORD_ABBREVIATIONS = {
service: "Svc",
user: "Usr",
management: "Mgmt",
authority: "Auth",
group: "Grp",
update: "Upd",
delete: "Del",
create: "Crt",
configuration: "Config",
resource: "Res",
authentication: "Authn", // ... and more
}
Original | Process | Result |
---|---|---|
getUserDetails |
get-user-details | get-user-details |
ServiceUsersManagementController_updateServiceUsersAuthorityGroup |
Split β Remove common β Abbreviate β Hash | svc-usrs-mgmt-upd-svc-usrs-auth-grp-a1b2 |
UpdateUserConfigurationManagement |
Split β Abbreviate | upd-usr-config-mgmt |
Set disableAbbreviation: true
to disable the abbreviation system:
- No common word removal
- No standard abbreviations
- No vowel removal
- No length limits (may cause errors if names exceed 64 characters)
Resource names are extracted from OpenAPI paths for filtering purposes:
private extractResourceName(path: string): string | undefined {
const segments = path.replace(/^\//, "").split("/")
// Find the last non-parameter segment
for (let i = segments.length - 1; i >= 0; i--) {
const segment = segments[i]
if (!segment.includes("{") && !segment.includes("}") && segment.length > 0) {
return segment
}
}
return segments[0] || undefined
}
Path | Resource Name |
---|---|
/users |
users |
/users/{id} |
users |
/api/v1/users/{id}/posts |
posts |
/api/v1/user-profile-settings |
settings |
/health |
health |
Filters are applied in a specific order with different precedence:
-
includeTools
(highest priority): If specified, overrides all other filters -
includeOperations
: Filter by HTTP methods (AND operation with remaining filters) -
includeResources
: Filter by resource names (AND operation) -
includeTags
: Filter by OpenAPI tags (AND operation)
toolsMode: "all"
// Apply filters as AND operations
// Empty filter arrays = no filtering for that dimension
toolsMode: "explicit"
includeTools: ["GET::users", "POST::users"]
// ONLY load explicitly listed tools
// Ignore all other filters
toolsMode: "dynamic"
// Load only meta-tools:
// - list-api-endpoints
// - get-api-endpoint-schema
// - invoke-api-endpoint
All filtering is case-insensitive:
- Tool IDs:
GET::Users
matches filterget::users
- Tool names:
getUsers
matches filtergetusers
- Resource names:
Users
matches filterusers
- Tags:
ADMIN
matches filteradmin
- HTTP methods:
GET
matches filterget
interface AuthProvider {
/**
* Get authentication headers for the current request
* Called before each API request to get fresh headers
*/
getAuthHeaders(): Promise<Record<string, string>>
/**
* Handle authentication errors from API responses
* Called when the API returns 401 or 403 errors
* Return true to retry the request, false otherwise
*/
handleAuthError(error: AxiosError): Promise<boolean>
}
-
Before each request:
getAuthHeaders()
is called -
On auth errors (401/403):
handleAuthError()
is called -
If
handleAuthError()
returnstrue
: Request is retried once with fresh headers -
If
handleAuthError()
returnsfalse
: Error is propagated to user
Static Authentication (backward compatible):
const config = {
headers: { Authorization: "Bearer token" },
}
// Internally creates StaticAuthProvider
Dynamic Authentication:
const config = {
authProvider: new MyAuthProvider(),
// No headers property when using AuthProvider
}
The server resolves OpenAPI $ref
references:
-
Parameter references:
$ref: "#/components/parameters/MyParam"
-
Schema references:
$ref: "#/components/schemas/MySchema"
- Recursive references: Circular reference detection prevents infinite loops
- External references: Gracefully handled (returns empty schema)
Supports OpenAPI schema composition keywords:
-
allOf
: Schemas are merged into a single object -
oneOf
/anyOf
: Composition is preserved in the input schema -
not
: Preserved as-is in the input schema
Path-level parameters are inherited by operations:
- Path parameters are added to all operations in the path
- Operation parameters can override path parameters (same name + location)
- Merging logic combines both sets without duplication
The server creates unified input schemas by merging:
- Path parameters (from URL path)
- Query parameters (from URL query string)
-
Header parameters (with
x-parameter-location: "header"
) -
Cookie parameters (with
x-parameter-location: "cookie"
) -
Request body (flattened into schema or wrapped in
body
property)
For request bodies with multiple content types:
-
Priority:
application/json
>application/x-www-form-urlencoded
>multipart/form-data
> others -
File uploads:
multipart/form-data
withtype: string, format: binary
git clone <repository>
cd mcp-openapi-server
npm install
npm run dev # Watch mode with auto-rebuild
npm run inspect-watch # Debug mode with auto-reload
npm run build # Build TypeScript
npm run typecheck # Type checking only
npm run lint # ESLint
npm run test # Run tests
npm run test:watch # Watch mode tests
npm run clean # Remove build artifacts
src/
βββ config.ts # Configuration loading and validation
βββ server.ts # Main OpenAPIServer class
βββ tools-manager.ts # Tool filtering and management
βββ openapi-loader.ts # OpenAPI spec parsing and tool creation
βββ api-client.ts # HTTP client for API requests
βββ auth-provider.ts # Authentication interfaces and implementations
βββ transport-http.ts # HTTP transport implementation
βββ utils/
βββ tool-id.ts # Tool ID generation and parsing
βββ abbreviations.ts # Name abbreviation rules
test/
βββ *.test.ts # Unit tests for each module
βββ fixtures/ # Test data and mock OpenAPI specs
docs/
βββ developer-guide.md # This document
βββ auth-provider-guide.md # AuthProvider documentation
βββ plans/ # Development plans and improvements
examples/
βββ basic-library-usage/ # Simple library usage example
βββ auth-provider-example/ # AuthProvider implementations
βββ beatport-example/ # Real-world production example
Tests are organized by module with comprehensive coverage:
- Unit tests: Test individual functions and classes
- Integration tests: Test component interactions
- Edge case tests: Test error conditions and boundary cases
- Regression tests: Prevent known issues from reoccurring
- Tool ID System: Round-trip consistency, hyphen escaping, edge cases
- Abbreviation System: All processing steps, edge cases, hash generation
- Filtering Logic: All filter combinations, precedence, case sensitivity
- OpenAPI Processing: Reference resolution, schema composition, parameter inheritance
- Authentication: Static and dynamic auth, error handling, retry logic
npm test # All tests
npm test -- --watch # Watch mode
npm test tool-id-utils # Specific test file
npm test -- --coverage # Coverage report
Parameterized tests for comprehensive coverage:
const testCases = [
{ input: "GET::users", expected: { method: "GET", path: "/users" } },
{ input: "POST::api-v1-users", expected: { method: "POST", path: "/api/v1/users" } },
]
for (const { input, expected } of testCases) {
const result = parseToolId(input)
expect(result).toEqual(expected)
}
Mock management for isolated testing:
const mockSpecLoader = {
loadOpenAPISpec: vi.fn(),
parseOpenAPISpec: vi.fn(),
}
- TypeScript: Strict mode enabled
- Formatting: Prettier with project configuration
- Linting: ESLint with TypeScript rules
- Naming: camelCase for variables/functions, PascalCase for classes, kebab-case for files
Follow Google's Technical Writing Style Guide:
- Use active voice and present tense
- Write clear, concise sentences
- Define terminology when needed
- Use lists and tables for complex information
- Include examples for all concepts
All code must have comprehensive JSDoc documentation:
/**
* Parse a tool ID into HTTP method and path
*
* Tool IDs have the format: METHOD::pathPart where pathPart has slashes
* converted to hyphens and legitimate hyphens escaped as double hyphens.
*
* @param toolId - Tool ID in format METHOD::pathPart
* @returns Object containing method and path
*
* @example
* parseToolId("GET::users") β { method: "GET", path: "/users" }
* parseToolId("GET::api__resource-name__items") β { method: "GET", path: "/api/resource-name/items" }
*/
- Fork the repository
-
Create a feature branch from
main
- Implement changes with tests
-
Run
npm run typecheck && npm run lint && npm test
- Update documentation if needed
- Submit pull request with clear description
Follow conventional commit format:
feat: add support for OpenAPI 3.1 specifications
- Implement OpenAPI 3.1 parser compatibility
- Add tests for new specification features
- Update documentation with 3.1 examples
Closes #123
When adding new features:
- Design: Consider backward compatibility
- Test: Add comprehensive test coverage
- Document: Update relevant documentation
- Examples: Add usage examples if applicable
- Performance: Consider impact on existing functionality
This developer guide should be updated as the codebase evolves to ensure it remains accurate and comprehensive.