mcp-openapi-schema-explorer 1.0.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/.devcontainer/devcontainer.json +24 -0
- package/.github/dependabot.yml +13 -0
- package/.github/workflows/ci.yml +111 -0
- package/.husky/pre-commit +6 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +12 -0
- package/.releaserc.json +23 -0
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +67 -0
- package/Dockerfile +3 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/src/config.d.ts +15 -0
- package/dist/src/config.js +19 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/handlers/component-detail-handler.d.ts +14 -0
- package/dist/src/handlers/component-detail-handler.js +87 -0
- package/dist/src/handlers/component-detail-handler.js.map +1 -0
- package/dist/src/handlers/component-map-handler.d.ts +14 -0
- package/dist/src/handlers/component-map-handler.js +63 -0
- package/dist/src/handlers/component-map-handler.js.map +1 -0
- package/dist/src/handlers/handler-utils.d.ts +69 -0
- package/dist/src/handlers/handler-utils.js +180 -0
- package/dist/src/handlers/handler-utils.js.map +1 -0
- package/dist/src/handlers/operation-handler.d.ts +14 -0
- package/dist/src/handlers/operation-handler.js +86 -0
- package/dist/src/handlers/operation-handler.js.map +1 -0
- package/dist/src/handlers/path-item-handler.d.ts +14 -0
- package/dist/src/handlers/path-item-handler.js +66 -0
- package/dist/src/handlers/path-item-handler.js.map +1 -0
- package/dist/src/handlers/top-level-field-handler.d.ts +14 -0
- package/dist/src/handlers/top-level-field-handler.js +72 -0
- package/dist/src/handlers/top-level-field-handler.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +177 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/rendering/components.d.ts +67 -0
- package/dist/src/rendering/components.js +177 -0
- package/dist/src/rendering/components.js.map +1 -0
- package/dist/src/rendering/document.d.ts +36 -0
- package/dist/src/rendering/document.js +147 -0
- package/dist/src/rendering/document.js.map +1 -0
- package/dist/src/rendering/path-item.d.ts +45 -0
- package/dist/src/rendering/path-item.js +141 -0
- package/dist/src/rendering/path-item.js.map +1 -0
- package/dist/src/rendering/paths.d.ts +26 -0
- package/dist/src/rendering/paths.js +78 -0
- package/dist/src/rendering/paths.js.map +1 -0
- package/dist/src/rendering/types.d.ts +50 -0
- package/dist/src/rendering/types.js +12 -0
- package/dist/src/rendering/types.js.map +1 -0
- package/dist/src/rendering/utils.d.ts +31 -0
- package/dist/src/rendering/utils.js +79 -0
- package/dist/src/rendering/utils.js.map +1 -0
- package/dist/src/services/formatters.d.ts +36 -0
- package/dist/src/services/formatters.js +52 -0
- package/dist/src/services/formatters.js.map +1 -0
- package/dist/src/services/reference-transform.d.ts +27 -0
- package/dist/src/services/reference-transform.js +75 -0
- package/dist/src/services/reference-transform.js.map +1 -0
- package/dist/src/services/spec-loader.d.ts +27 -0
- package/dist/src/services/spec-loader.js +77 -0
- package/dist/src/services/spec-loader.js.map +1 -0
- package/dist/src/types.d.ts +11 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/uri-builder.d.ts +81 -0
- package/dist/src/utils/uri-builder.js +121 -0
- package/dist/src/utils/uri-builder.js.map +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +4 -0
- package/dist/src/version.js.map +1 -0
- package/eslint.config.js +88 -0
- package/jest.config.js +32 -0
- package/justfile +66 -0
- package/memory-bank/activeContext.md +139 -0
- package/memory-bank/productContext.md +39 -0
- package/memory-bank/progress.md +141 -0
- package/memory-bank/projectbrief.md +50 -0
- package/memory-bank/systemPatterns.md +224 -0
- package/memory-bank/techContext.md +131 -0
- package/package.json +76 -0
- package/scripts/generate-version.js +49 -0
- package/src/config.ts +33 -0
- package/src/handlers/component-detail-handler.ts +121 -0
- package/src/handlers/component-map-handler.ts +92 -0
- package/src/handlers/handler-utils.ts +230 -0
- package/src/handlers/operation-handler.ts +114 -0
- package/src/handlers/path-item-handler.ts +88 -0
- package/src/handlers/top-level-field-handler.ts +92 -0
- package/src/index.ts +222 -0
- package/src/rendering/components.ts +228 -0
- package/src/rendering/document.ts +167 -0
- package/src/rendering/path-item.ts +157 -0
- package/src/rendering/paths.ts +87 -0
- package/src/rendering/types.ts +63 -0
- package/src/rendering/utils.ts +107 -0
- package/src/services/formatters.ts +71 -0
- package/src/services/reference-transform.ts +105 -0
- package/src/services/spec-loader.ts +88 -0
- package/src/types.ts +17 -0
- package/src/utils/uri-builder.ts +134 -0
- package/src/version.ts +4 -0
- package/test/__tests__/e2e/format.test.ts +224 -0
- package/test/__tests__/e2e/resources.test.ts +369 -0
- package/test/__tests__/e2e/spec-loading.test.ts +172 -0
- package/test/__tests__/unit/config.test.ts +39 -0
- package/test/__tests__/unit/handlers/component-detail-handler.test.ts +241 -0
- package/test/__tests__/unit/handlers/component-map-handler.test.ts +187 -0
- package/test/__tests__/unit/handlers/handler-utils.test.ts +255 -0
- package/test/__tests__/unit/handlers/operation-handler.test.ts +202 -0
- package/test/__tests__/unit/handlers/path-item-handler.test.ts +153 -0
- package/test/__tests__/unit/handlers/top-level-field-handler.test.ts +182 -0
- package/test/__tests__/unit/rendering/components.test.ts +269 -0
- package/test/__tests__/unit/rendering/document.test.ts +172 -0
- package/test/__tests__/unit/rendering/path-item.test.ts +197 -0
- package/test/__tests__/unit/rendering/paths.test.ts +115 -0
- package/test/__tests__/unit/services/formatters.test.ts +109 -0
- package/test/__tests__/unit/services/reference-transform.test.ts +320 -0
- package/test/__tests__/unit/services/spec-loader.test.ts +214 -0
- package/test/__tests__/unit/utils/uri-builder.test.ts +103 -0
- package/test/fixtures/complex-endpoint.json +146 -0
- package/test/fixtures/empty-api.json +8 -0
- package/test/fixtures/multi-component-types.json +55 -0
- package/test/fixtures/paths-test.json +61 -0
- package/test/fixtures/sample-api.json +68 -0
- package/test/fixtures/sample-v2-api.json +39 -0
- package/test/setup.ts +32 -0
- package/test/utils/console-helpers.ts +48 -0
- package/test/utils/mcp-test-helpers.ts +66 -0
- package/test/utils/test-types.ts +54 -0
- package/tsconfig.json +25 -0
- package/tsconfig.test.json +5 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Technical Context
|
|
2
|
+
|
|
3
|
+
## Development Stack
|
|
4
|
+
|
|
5
|
+
- TypeScript for implementation
|
|
6
|
+
- MCP SDK for server functionality
|
|
7
|
+
- Jest for testing
|
|
8
|
+
- npm for package distribution
|
|
9
|
+
|
|
10
|
+
## Key Dependencies
|
|
11
|
+
|
|
12
|
+
- `@modelcontextprotocol/sdk`: Core MCP functionality
|
|
13
|
+
- `swagger2openapi`: OpenAPI/Swagger spec loading, parsing, and v2->v3 conversion (Runtime dependency)
|
|
14
|
+
- `js-yaml`: YAML parsing (Runtime dependency)
|
|
15
|
+
- `zod`: Schema validation (Runtime dependency)
|
|
16
|
+
- `openapi-types`: OpenAPI type definitions (devDependency)
|
|
17
|
+
- `typescript`: TypeScript compiler (devDependency)
|
|
18
|
+
- `@types/*`: Various type definitions (devDependencies)
|
|
19
|
+
- `jest`: Testing framework (devDependency)
|
|
20
|
+
- `eslint`: Code linting (devDependency)
|
|
21
|
+
- `prettier`: Code formatting (devDependency)
|
|
22
|
+
- `semantic-release` & plugins (`@semantic-release/*`): Automated releases (devDependencies)
|
|
23
|
+
- `just`: Task runner (Used locally, installed via action in CI)
|
|
24
|
+
|
|
25
|
+
## Technical Requirements
|
|
26
|
+
|
|
27
|
+
1. Must follow MCP protocol specifications.
|
|
28
|
+
2. Must handle large OpenAPI/Swagger specs efficiently.
|
|
29
|
+
3. Must provide type-safe reference handling (transforming internal refs to MCP URIs).
|
|
30
|
+
4. Must support loading specs from local file paths and remote HTTP/HTTPS URLs.
|
|
31
|
+
5. Must support OpenAPI v3.0 and Swagger v2.0 formats (with v2.0 being converted to v3.0).
|
|
32
|
+
6. Must be easily testable and maintainable.
|
|
33
|
+
|
|
34
|
+
## Development Environment
|
|
35
|
+
|
|
36
|
+
- TypeScript setup with strict type checking
|
|
37
|
+
- Jest testing framework with coverage
|
|
38
|
+
- ESLint for code quality
|
|
39
|
+
- Prettier for code formatting
|
|
40
|
+
- `just` task runner (`justfile`) for common development tasks (build, test, lint, etc.)
|
|
41
|
+
- Conventional Commits standard for commit messages (required for `semantic-release`)
|
|
42
|
+
- Test fixtures and helpers
|
|
43
|
+
|
|
44
|
+
## Code Organization
|
|
45
|
+
|
|
46
|
+
- Services layer:
|
|
47
|
+
- `SpecLoaderService`: Uses `swagger2openapi` to load specs from files/URLs and handle v2->v3 conversion.
|
|
48
|
+
- `ReferenceTransformService`: Transforms internal `#/components/...` refs to MCP URIs.
|
|
49
|
+
- `Formatters`: Handle JSON/YAML output.
|
|
50
|
+
- Handlers layer for resource endpoints.
|
|
51
|
+
- Rendering layer for generating resource content.
|
|
52
|
+
- Utilities (e.g., URI builder).
|
|
53
|
+
- Strong typing with generics.
|
|
54
|
+
- Comprehensive test coverage.
|
|
55
|
+
|
|
56
|
+
## Testing Infrastructure
|
|
57
|
+
|
|
58
|
+
- Unit tests:
|
|
59
|
+
- `SpecLoaderService` (mocking `swagger2openapi`).
|
|
60
|
+
- `ReferenceTransformService`.
|
|
61
|
+
- Rendering classes.
|
|
62
|
+
- Handlers (mocking services).
|
|
63
|
+
- End-to-end tests:
|
|
64
|
+
- Verify resource access for local v3, local v2, and remote v3 specs.
|
|
65
|
+
- Test multi-value parameters.
|
|
66
|
+
- Cover success and error scenarios.
|
|
67
|
+
- Verify resource completion logic using `client.complete()`.
|
|
68
|
+
- Type-safe test utilities (`mcp-test-helpers`).
|
|
69
|
+
- Test fixtures (including v2.0 and v3.0 examples).
|
|
70
|
+
- Coverage reporting via Jest and upload to Codecov via GitHub Actions.
|
|
71
|
+
- CI Integration (`.github/workflows/ci.yml`):
|
|
72
|
+
- Runs checks (`just all`, `just security`, CodeQL) on pushes/PRs to `main`.
|
|
73
|
+
- Uses Node 22 environment.
|
|
74
|
+
|
|
75
|
+
## Response Formats
|
|
76
|
+
|
|
77
|
+
1. Base Formats
|
|
78
|
+
|
|
79
|
+
- JSON format (default format)
|
|
80
|
+
- YAML format support
|
|
81
|
+
- URI-based reference links
|
|
82
|
+
- Token-efficient structure
|
|
83
|
+
- OpenAPI v3 type compliance
|
|
84
|
+
|
|
85
|
+
2. Format Service
|
|
86
|
+
|
|
87
|
+
- Pluggable formatter architecture
|
|
88
|
+
- Format-specific MIME types (`application/json`, `text/yaml`)
|
|
89
|
+
- Type-safe formatter interface (`IFormatter`)
|
|
90
|
+
- Consistent error formatting (`text/plain`)
|
|
91
|
+
- CLI-configurable output format (`--output-format`)
|
|
92
|
+
|
|
93
|
+
3. Implementation
|
|
94
|
+
- Format-specific serialization
|
|
95
|
+
- Shared type system
|
|
96
|
+
- Error response handling
|
|
97
|
+
- Multiple operation support
|
|
98
|
+
- Reference transformation
|
|
99
|
+
|
|
100
|
+
## Deployment / Release Process
|
|
101
|
+
|
|
102
|
+
- Automated publishing to npm via `semantic-release` triggered by pushes to `main` branch in GitHub Actions.
|
|
103
|
+
- Relies on Conventional Commits to determine version bumps.
|
|
104
|
+
- Creates version tags (e.g., `v1.2.3`) and GitHub Releases automatically.
|
|
105
|
+
- Requires `NPM_TOKEN` secret configured in GitHub repository for publishing.
|
|
106
|
+
- `CHANGELOG.md` is automatically generated and updated.
|
|
107
|
+
- Server version is dynamically set at runtime based on the release version.
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
- Command-line argument based configuration (`src/config.ts`).
|
|
112
|
+
- Single required argument: `<path-or-url-to-spec>`.
|
|
113
|
+
- Optional argument: `--output-format <json|yaml|json-minified>`.
|
|
114
|
+
- Required argument validation.
|
|
115
|
+
- TypeScript type safety (`ServerConfig` interface).
|
|
116
|
+
- Error handling for missing/invalid arguments.
|
|
117
|
+
|
|
118
|
+
## Error Handling
|
|
119
|
+
|
|
120
|
+
- Descriptive error messages
|
|
121
|
+
- Type-safe error handling
|
|
122
|
+
- Consistent error format
|
|
123
|
+
- Proper error propagation
|
|
124
|
+
|
|
125
|
+
## Future Extensions
|
|
126
|
+
|
|
127
|
+
- AsyncAPI format support
|
|
128
|
+
- GraphQL schema support
|
|
129
|
+
- External reference resolution
|
|
130
|
+
- Enhanced schema resources
|
|
131
|
+
- Reference validation
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-openapi-schema-explorer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP OpenAPI schema explorer",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/src/index.js",
|
|
7
|
+
"types": "dist/src/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mcp-openapi-schema-explorer": "dist/src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "rm -rf dist && mkdir -p dist && npx tsc && chmod +x dist/src/index.js",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"test:watch": "jest --watch",
|
|
15
|
+
"test:coverage": "jest --coverage",
|
|
16
|
+
"lint": "eslint . --ext .ts",
|
|
17
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
18
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
19
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
20
|
+
"type-check": "tsc --noEmit",
|
|
21
|
+
"prepare": "husky",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/kadykov/mcp-openapi-schema-explorer.git"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"openapi"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/kadykov/mcp-openapi-schema-explorer/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/kadykov/mcp-openapi-schema-explorer#readme",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
40
|
+
"js-yaml": "^4.1.0",
|
|
41
|
+
"swagger2openapi": "7.0.8",
|
|
42
|
+
"zod": "^3.24.2"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/js": "^9.24.0",
|
|
46
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
47
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
48
|
+
"@semantic-release/exec": "^7.0.3",
|
|
49
|
+
"@semantic-release/git": "^10.0.1",
|
|
50
|
+
"@semantic-release/github": "^11.0.1",
|
|
51
|
+
"@semantic-release/npm": "^12.0.1",
|
|
52
|
+
"@semantic-release/release-notes-generator": "^14.0.3",
|
|
53
|
+
"@types/jest": "^29.5.14",
|
|
54
|
+
"@types/js-yaml": "^4.0.9",
|
|
55
|
+
"@types/node": "^22.14.1",
|
|
56
|
+
"@types/node-fetch": "^2.6.12",
|
|
57
|
+
"@types/swagger2openapi": "^7.0.4",
|
|
58
|
+
"@typescript-eslint/eslint-plugin": "^8.29.0",
|
|
59
|
+
"@typescript-eslint/parser": "^8.29.0",
|
|
60
|
+
"axios": "^1.8.4",
|
|
61
|
+
"eslint": "^9.24.0",
|
|
62
|
+
"eslint-plugin-security": "^3.0.1",
|
|
63
|
+
"globals": "^16.0.0",
|
|
64
|
+
"husky": "^9.1.7",
|
|
65
|
+
"jest": "^29.7.0",
|
|
66
|
+
"jest-silent-reporter": "^0.6.0",
|
|
67
|
+
"license-checker": "^25.0.1",
|
|
68
|
+
"msw": "^2.7.4",
|
|
69
|
+
"openapi-types": "^12.1.3",
|
|
70
|
+
"openapi-typescript": "^7.6.1",
|
|
71
|
+
"prettier": "^3.5.3",
|
|
72
|
+
"semantic-release": "^24.2.3",
|
|
73
|
+
"ts-jest": "^29.3.1",
|
|
74
|
+
"typescript": "^5.8.3"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// scripts/generate-version.js
|
|
2
|
+
// Purpose: Writes the release version provided by semantic-release to src/version.ts
|
|
3
|
+
// Called by: @semantic-release/exec during the 'prepare' step
|
|
4
|
+
|
|
5
|
+
import fs from 'fs'; // Use import
|
|
6
|
+
import path from 'path'; // Use import
|
|
7
|
+
import { fileURLToPath } from 'url'; // Needed to convert import.meta.url
|
|
8
|
+
|
|
9
|
+
// Get version from the command line argument passed by semantic-release exec
|
|
10
|
+
// process is globally available in ESM
|
|
11
|
+
const version = process.argv[2];
|
|
12
|
+
|
|
13
|
+
if (!version) {
|
|
14
|
+
console.error('Error: No version argument provided to generate-version.js!');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Basic check for semantic version format (adjust regex if needed)
|
|
19
|
+
if (!/^\d+\.\d+\.\d+(-[\w.-]+)?(\+[\w.-]+)?$/.test(version)) {
|
|
20
|
+
console.error(`Error: Invalid version format received: "${version}"`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const content = `// Auto-generated by scripts/generate-version.js during semantic-release prepare step
|
|
25
|
+
// Do not edit this file manually.
|
|
26
|
+
|
|
27
|
+
export const VERSION = '${version}';
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
// Derive the directory path in ESM
|
|
31
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
32
|
+
const __dirname = path.dirname(__filename);
|
|
33
|
+
|
|
34
|
+
// Construct the absolute path to src/version.ts
|
|
35
|
+
const filePath = path.join(__dirname, '..', 'src', 'version.ts');
|
|
36
|
+
const fileDir = path.dirname(filePath);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Ensure the src directory exists (though it should)
|
|
40
|
+
if (!fs.existsSync(fileDir)) {
|
|
41
|
+
fs.mkdirSync(fileDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
// Write the version file
|
|
44
|
+
fs.writeFileSync(filePath, content, { encoding: 'utf-8' });
|
|
45
|
+
console.log(`Successfully wrote version ${version} to ${filePath}`);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`Error writing version file to ${filePath}:`, error);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for the OpenAPI Explorer MCP server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { OutputFormat } from './services/formatters.js';
|
|
6
|
+
|
|
7
|
+
/** Server configuration */
|
|
8
|
+
export interface ServerConfig {
|
|
9
|
+
/** Path to OpenAPI specification file */
|
|
10
|
+
specPath: string;
|
|
11
|
+
/** Output format for responses */
|
|
12
|
+
outputFormat: OutputFormat;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Load server configuration from command line arguments */
|
|
16
|
+
export function loadConfig(specPath?: string, options?: { outputFormat?: string }): ServerConfig {
|
|
17
|
+
if (!specPath) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
'OpenAPI spec path is required. Usage: npx mcp-openapi-schema-explorer <path-to-spec> [--output-format json|yaml]'
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const format = options?.outputFormat || 'json';
|
|
24
|
+
if (format !== 'json' && format !== 'yaml' && format !== 'json-minified') {
|
|
25
|
+
throw new Error('Invalid output format. Supported formats: json, yaml, json-minified');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
specPath,
|
|
30
|
+
// Cast is safe here due to the validation above
|
|
31
|
+
outputFormat: format as OutputFormat,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReadResourceTemplateCallback,
|
|
3
|
+
ResourceTemplate,
|
|
4
|
+
} from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import { Variables } from '@modelcontextprotocol/sdk/shared/uriTemplate.js';
|
|
6
|
+
import { SpecLoaderService } from '../types.js';
|
|
7
|
+
import { IFormatter } from '../services/formatters.js';
|
|
8
|
+
import {
|
|
9
|
+
RenderableComponentMap,
|
|
10
|
+
ComponentType,
|
|
11
|
+
VALID_COMPONENT_TYPES,
|
|
12
|
+
} from '../rendering/components.js';
|
|
13
|
+
import { RenderContext, RenderResultItem } from '../rendering/types.js';
|
|
14
|
+
import { createErrorResult } from '../rendering/utils.js';
|
|
15
|
+
// Import shared handler utils
|
|
16
|
+
import {
|
|
17
|
+
formatResults,
|
|
18
|
+
isOpenAPIV3,
|
|
19
|
+
FormattedResultItem,
|
|
20
|
+
getValidatedComponentMap, // Import helper
|
|
21
|
+
getValidatedComponentDetails, // Import helper
|
|
22
|
+
} from './handler-utils.js'; // Already has .js
|
|
23
|
+
|
|
24
|
+
const BASE_URI = 'openapi://';
|
|
25
|
+
|
|
26
|
+
// Removed duplicated FormattedResultItem type - now imported from handler-utils
|
|
27
|
+
// Removed duplicated formatResults function - now imported from handler-utils
|
|
28
|
+
// Removed duplicated isOpenAPIV3 function - now imported from handler-utils
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handles requests for specific component details.
|
|
32
|
+
* Corresponds to the `openapi://components/{type}/{name*}` template.
|
|
33
|
+
*/
|
|
34
|
+
export class ComponentDetailHandler {
|
|
35
|
+
constructor(
|
|
36
|
+
private specLoader: SpecLoaderService,
|
|
37
|
+
private formatter: IFormatter
|
|
38
|
+
) {}
|
|
39
|
+
|
|
40
|
+
getTemplate(): ResourceTemplate {
|
|
41
|
+
// TODO: Add completion logic if needed
|
|
42
|
+
return new ResourceTemplate(`${BASE_URI}components/{type}/{name*}`, {
|
|
43
|
+
list: undefined,
|
|
44
|
+
complete: undefined,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
handleRequest: ReadResourceTemplateCallback = async (
|
|
49
|
+
uri: URL,
|
|
50
|
+
variables: Variables
|
|
51
|
+
): Promise<{ contents: FormattedResultItem[] }> => {
|
|
52
|
+
const type = variables.type as string;
|
|
53
|
+
// Correct variable access key: 'name', not 'name*'
|
|
54
|
+
const nameVar = variables['name']; // Can be string or string[]
|
|
55
|
+
const mapUriSuffix = `components/${type}`;
|
|
56
|
+
const context: RenderContext = { formatter: this.formatter, baseUri: BASE_URI };
|
|
57
|
+
let resultItems: RenderResultItem[];
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
if (!VALID_COMPONENT_TYPES.includes(type as ComponentType)) {
|
|
61
|
+
throw new Error(`Invalid component type: ${type}`);
|
|
62
|
+
}
|
|
63
|
+
const componentType = type as ComponentType;
|
|
64
|
+
|
|
65
|
+
// Normalize names: Handle string for single value, array for multiple.
|
|
66
|
+
let names: string[] = [];
|
|
67
|
+
if (Array.isArray(nameVar)) {
|
|
68
|
+
names = nameVar.map(n => String(n).trim()); // Ensure elements are strings
|
|
69
|
+
} else if (typeof nameVar === 'string') {
|
|
70
|
+
names = [nameVar.trim()]; // Treat as single item array
|
|
71
|
+
}
|
|
72
|
+
names = names.filter(n => n.length > 0); // Remove empty strings
|
|
73
|
+
|
|
74
|
+
if (names.length === 0) {
|
|
75
|
+
throw new Error('No valid component name specified.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const spec = await this.specLoader.getTransformedSpec({
|
|
79
|
+
resourceType: 'schema', // Use 'schema' for now
|
|
80
|
+
format: 'openapi',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Use imported type guard
|
|
84
|
+
if (!isOpenAPIV3(spec)) {
|
|
85
|
+
throw new Error('Only OpenAPI v3 specifications are supported');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// --- Use helper to get validated component map ---
|
|
89
|
+
const componentMapObj = getValidatedComponentMap(spec, componentType);
|
|
90
|
+
|
|
91
|
+
// --- Create Map and use helper to get validated component names/details ---
|
|
92
|
+
// Create the Map from the validated object
|
|
93
|
+
const detailsMap = new Map(Object.entries(componentMapObj));
|
|
94
|
+
// Pass the Map to the helper
|
|
95
|
+
const validDetails = getValidatedComponentDetails(detailsMap, names, componentType);
|
|
96
|
+
const validNames = validDetails.map(detail => detail.name); // Extract names
|
|
97
|
+
|
|
98
|
+
// Instantiate RenderableComponentMap with the validated map object
|
|
99
|
+
const renderableMap = new RenderableComponentMap(
|
|
100
|
+
componentMapObj, // componentMapObj retrieved safely via helper
|
|
101
|
+
componentType,
|
|
102
|
+
mapUriSuffix
|
|
103
|
+
);
|
|
104
|
+
// Pass the validated names to the rendering function
|
|
105
|
+
resultItems = renderableMap.renderComponentDetail(context, validNames);
|
|
106
|
+
} catch (error: unknown) {
|
|
107
|
+
// Catch errors from helpers (e.g., type/name not found) or rendering
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
console.error(`Error handling request ${uri.href}: ${message}`);
|
|
110
|
+
// Create a single error item representing the overall request failure
|
|
111
|
+
resultItems = createErrorResult(
|
|
112
|
+
uri.href.substring(BASE_URI.length), // Use request URI suffix
|
|
113
|
+
message
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Use imported formatResults
|
|
118
|
+
const contents = formatResults(context, resultItems);
|
|
119
|
+
return { contents };
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReadResourceTemplateCallback,
|
|
3
|
+
ResourceTemplate,
|
|
4
|
+
} from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import { Variables } from '@modelcontextprotocol/sdk/shared/uriTemplate.js';
|
|
6
|
+
import { SpecLoaderService } from '../types.js';
|
|
7
|
+
import { IFormatter } from '../services/formatters.js';
|
|
8
|
+
import {
|
|
9
|
+
RenderableComponentMap,
|
|
10
|
+
ComponentType,
|
|
11
|
+
VALID_COMPONENT_TYPES,
|
|
12
|
+
} from '../rendering/components.js';
|
|
13
|
+
import { RenderContext, RenderResultItem } from '../rendering/types.js';
|
|
14
|
+
import { createErrorResult } from '../rendering/utils.js';
|
|
15
|
+
// Import shared handler utils
|
|
16
|
+
import {
|
|
17
|
+
formatResults,
|
|
18
|
+
isOpenAPIV3,
|
|
19
|
+
FormattedResultItem,
|
|
20
|
+
getValidatedComponentMap, // Import the helper
|
|
21
|
+
} from './handler-utils.js'; // Already has .js
|
|
22
|
+
|
|
23
|
+
const BASE_URI = 'openapi://';
|
|
24
|
+
|
|
25
|
+
// Removed duplicated FormattedResultItem type - now imported from handler-utils
|
|
26
|
+
// Removed duplicated formatResults function - now imported from handler-utils
|
|
27
|
+
// Removed duplicated isOpenAPIV3 function - now imported from handler-utils
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Handles requests for listing component names of a specific type.
|
|
31
|
+
* Corresponds to the `openapi://components/{type}` template.
|
|
32
|
+
*/
|
|
33
|
+
export class ComponentMapHandler {
|
|
34
|
+
constructor(
|
|
35
|
+
private specLoader: SpecLoaderService,
|
|
36
|
+
private formatter: IFormatter // Needed for context
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
getTemplate(): ResourceTemplate {
|
|
40
|
+
// TODO: Add completion logic if needed
|
|
41
|
+
return new ResourceTemplate(`${BASE_URI}components/{type}`, {
|
|
42
|
+
list: undefined,
|
|
43
|
+
complete: undefined,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
handleRequest: ReadResourceTemplateCallback = async (
|
|
48
|
+
uri: URL,
|
|
49
|
+
variables: Variables
|
|
50
|
+
): Promise<{ contents: FormattedResultItem[] }> => {
|
|
51
|
+
const type = variables.type as string;
|
|
52
|
+
const mapUriSuffix = `components/${type}`;
|
|
53
|
+
const context: RenderContext = { formatter: this.formatter, baseUri: BASE_URI };
|
|
54
|
+
let resultItems: RenderResultItem[];
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
if (!VALID_COMPONENT_TYPES.includes(type as ComponentType)) {
|
|
58
|
+
throw new Error(`Invalid component type: ${type}`);
|
|
59
|
+
}
|
|
60
|
+
const componentType = type as ComponentType;
|
|
61
|
+
|
|
62
|
+
const spec = await this.specLoader.getTransformedSpec({
|
|
63
|
+
resourceType: 'schema', // Use 'schema' for now
|
|
64
|
+
format: 'openapi',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Use imported type guard
|
|
68
|
+
if (!isOpenAPIV3(spec)) {
|
|
69
|
+
throw new Error('Only OpenAPI v3 specifications are supported');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- Use helper to get validated component map ---
|
|
73
|
+
const componentMapObj = getValidatedComponentMap(spec, componentType);
|
|
74
|
+
|
|
75
|
+
// Instantiate RenderableComponentMap with the validated map
|
|
76
|
+
const renderableMap = new RenderableComponentMap(
|
|
77
|
+
componentMapObj, // componentMapObj retrieved safely via helper
|
|
78
|
+
componentType,
|
|
79
|
+
mapUriSuffix
|
|
80
|
+
);
|
|
81
|
+
resultItems = renderableMap.renderList(context);
|
|
82
|
+
} catch (error: unknown) {
|
|
83
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
84
|
+
console.error(`Error handling request ${uri.href}: ${message}`);
|
|
85
|
+
resultItems = createErrorResult(mapUriSuffix, message);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Use imported formatResults
|
|
89
|
+
const contents = formatResults(context, resultItems);
|
|
90
|
+
return { contents };
|
|
91
|
+
};
|
|
92
|
+
}
|