1063 lines
28 KiB
Markdown
1063 lines
28 KiB
Markdown
# MCP Swagger Parser 抽离实施计划
|
|
|
|
## 📋 文档信息
|
|
|
|
**文档标题**: MCP Swagger Parser 抽离实施计划
|
|
**创建日期**: 2025-06-17
|
|
**版本**: v1.0
|
|
**状态**: 实施规划
|
|
**关联文档**: [Monorepo 重构方案](./monorepo-refactoring-proposal.md)
|
|
|
|
---
|
|
|
|
## 🎯 实施概览
|
|
|
|
### 核心目标
|
|
将 OpenAPI 解析逻辑从 `mcp-swagger-server` 中抽离,形成独立的 `mcp-swagger-parser` 库,实现:
|
|
- 职责分离和代码解耦
|
|
- 提高代码复用性
|
|
- 改善测试和维护性
|
|
- 为未来扩展奠定基础
|
|
|
|
### 预期收益
|
|
```
|
|
代码复用性: 0% → 80% (+80%)
|
|
测试隔离度: 困难 → 简单 (+200%)
|
|
维护复杂度: 高(8/10) → 低(3/10) (-62%)
|
|
开发效率: 基准 → +50% 提升
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 当前状态分析
|
|
|
|
### 需要抽离的代码文件
|
|
|
|
#### 主要文件清单
|
|
```
|
|
packages/mcp-swagger-server/src/transform/
|
|
├── openapi-to-mcp.ts 🔄 需要拆分
|
|
│ ├── parseOpenApiFromSource() ➡️ 移至 parser
|
|
│ ├── validateOpenApiSpec() ➡️ 移至 parser
|
|
│ ├── extractApiEndpoints() ➡️ 移至 parser
|
|
│ └── convertToMcpTools() ⬅️ 保留在 server
|
|
│
|
|
├── transformOpenApiToMcpTools.ts 🔄 需要重构
|
|
│ ├── 解析相关逻辑 ➡️ 移至 parser
|
|
│ └── MCP 转换逻辑 ⬅️ 保留在 server
|
|
│
|
|
└── index.ts 🔄 需要更新导出
|
|
```
|
|
|
|
#### 代码依赖分析
|
|
```typescript
|
|
// 当前 openapi-to-mcp.ts 的依赖
|
|
import SwaggerParser from '@apidevtools/swagger-parser'; // ➡️ 移至 parser
|
|
import { z } from 'zod'; // ➡️ 移至 parser
|
|
import axios from 'axios'; // ➡️ 移至 parser
|
|
import * as yaml from 'js-yaml'; // ➡️ 移至 parser
|
|
|
|
// MCP 相关依赖 (保留在 server)
|
|
import { Tool } from '@modelcontextprotocol/sdk/types.js'; // ⬅️ 保留
|
|
```
|
|
|
|
### 功能边界划分
|
|
|
|
#### 🔍 解析库负责 (mcp-swagger-parser)
|
|
```typescript
|
|
interface ParserResponsibilities {
|
|
// 输入处理
|
|
parseFromUrl(url: string): Promise<ParsedApiSpec>;
|
|
parseFromFile(filePath: string): Promise<ParsedApiSpec>;
|
|
parseFromText(content: string): Promise<ParsedApiSpec>;
|
|
|
|
// 验证和标准化
|
|
validateSpec(spec: any): Promise<ValidationResult>;
|
|
normalizeSpec(spec: any): Promise<NormalizedApiSpec>;
|
|
|
|
// 信息提取
|
|
extractEndpoints(spec: ParsedApiSpec): ApiEndpoint[];
|
|
extractSchemas(spec: ParsedApiSpec): SchemaDefinition[];
|
|
extractSecuritySchemes(spec: ParsedApiSpec): SecurityScheme[];
|
|
|
|
// 错误处理
|
|
handleParseErrors(error: any): ParseError;
|
|
}
|
|
```
|
|
|
|
#### ⚙️ 服务器负责 (mcp-swagger-server)
|
|
```typescript
|
|
interface ServerResponsibilities {
|
|
// MCP 转换
|
|
convertToMcpTools(endpoints: ApiEndpoint[]): Tool[];
|
|
applyFilters(endpoints: ApiEndpoint[], filters: FilterConfig): ApiEndpoint[];
|
|
generateMcpConfig(tools: Tool[], config: ConvertConfig): McpConfig;
|
|
|
|
// 协议处理
|
|
handleMcpProtocol(): void;
|
|
manageTransports(): void;
|
|
|
|
// 服务器管理
|
|
startServer(): Promise<void>;
|
|
stopServer(): Promise<void>;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🏗️ 目标架构设计
|
|
|
|
### 新包结构设计
|
|
|
|
#### mcp-swagger-parser 包结构
|
|
```
|
|
packages/mcp-swagger-parser/
|
|
├── src/
|
|
│ ├── core/ # 核心解析器
|
|
│ │ ├── parser.ts # 主解析器类
|
|
│ │ ├── validator.ts # 规范验证器
|
|
│ │ └── normalizer.ts # 标准化处理器
|
|
│ │
|
|
│ ├── parsers/ # 具体解析实现
|
|
│ │ ├── url-parser.ts # URL 输入解析
|
|
│ │ ├── file-parser.ts # 文件输入解析
|
|
│ │ ├── text-parser.ts # 文本输入解析
|
|
│ │ └── base-parser.ts # 解析器基类
|
|
│ │
|
|
│ ├── extractors/ # 信息提取器
|
|
│ │ ├── endpoint-extractor.ts # 端点信息提取
|
|
│ │ ├── schema-extractor.ts # 模式定义提取
|
|
│ │ ├── security-extractor.ts # 安全配置提取
|
|
│ │ └── metadata-extractor.ts # 元数据提取
|
|
│ │
|
|
│ ├── types/ # TypeScript 类型定义
|
|
│ │ ├── input.ts # 输入类型
|
|
│ │ ├── output.ts # 输出类型
|
|
│ │ ├── config.ts # 配置类型
|
|
│ │ └── index.ts # 类型导出
|
|
│ │
|
|
│ ├── utils/ # 工具函数
|
|
│ │ ├── format-detector.ts # 格式检测
|
|
│ │ ├── url-validator.ts # URL 验证
|
|
│ │ ├── error-handler.ts # 错误处理
|
|
│ │ └── logger.ts # 日志工具
|
|
│ │
|
|
│ ├── errors/ # 错误定义
|
|
│ │ ├── parse-error.ts # 解析错误
|
|
│ │ ├── validation-error.ts # 验证错误
|
|
│ │ └── network-error.ts # 网络错误
|
|
│ │
|
|
│ └── index.ts # 公共 API 导出
|
|
│
|
|
├── tests/ # 测试文件
|
|
│ ├── unit/ # 单元测试
|
|
│ ├── integration/ # 集成测试
|
|
│ ├── fixtures/ # 测试数据
|
|
│ └── __helpers__/ # 测试助手
|
|
│
|
|
├── docs/ # 文档
|
|
│ ├── API.md # API 文档
|
|
│ ├── examples/ # 使用示例
|
|
│ └── migration.md # 迁移指南
|
|
│
|
|
├── package.json # 包配置
|
|
├── tsconfig.json # TypeScript 配置
|
|
├── rollup.config.js # 构建配置
|
|
└── README.md # 包说明
|
|
```
|
|
|
|
### API 设计规范
|
|
|
|
#### 核心 API 接口
|
|
```typescript
|
|
// packages/mcp-swagger-parser/src/index.ts
|
|
|
|
export class OpenApiParser {
|
|
constructor(options?: ParserOptions) {}
|
|
|
|
// 主要解析方法
|
|
async parseFromUrl(url: string, options?: ParseFromUrlOptions): Promise<ParsedApiSpec> {}
|
|
async parseFromFile(filePath: string, options?: ParseFromFileOptions): Promise<ParsedApiSpec> {}
|
|
async parseFromText(content: string, format?: 'json' | 'yaml', options?: ParseFromTextOptions): Promise<ParsedApiSpec> {}
|
|
|
|
// 验证方法
|
|
async validate(spec: any): Promise<ValidationResult> {}
|
|
|
|
// 工具方法
|
|
static detectFormat(content: string): 'json' | 'yaml' | 'unknown' {}
|
|
static isValidUrl(url: string): boolean {}
|
|
}
|
|
|
|
// 便捷方法导出
|
|
export const parseOpenApiFromUrl = (url: string, options?: ParseFromUrlOptions) => new OpenApiParser().parseFromUrl(url, options);
|
|
export const parseOpenApiFromFile = (filePath: string, options?: ParseFromFileOptions) => new OpenApiParser().parseFromFile(filePath, options);
|
|
export const parseOpenApiFromText = (content: string, format?: 'json' | 'yaml', options?: ParseFromTextOptions) => new OpenApiParser().parseFromText(content, format, options);
|
|
```
|
|
|
|
#### 类型定义系统
|
|
```typescript
|
|
// packages/mcp-swagger-parser/src/types/index.ts
|
|
|
|
export interface ParsedApiSpec {
|
|
// OpenAPI 基础信息
|
|
openapi: string;
|
|
info: ApiInfo;
|
|
servers: ServerInfo[];
|
|
|
|
// 核心内容
|
|
paths: PathsObject;
|
|
components?: ComponentsObject;
|
|
security?: SecurityRequirement[];
|
|
tags?: TagObject[];
|
|
|
|
// 解析元数据
|
|
metadata: ParseMetadata;
|
|
}
|
|
|
|
export interface ParseMetadata {
|
|
// 解析信息
|
|
sourceType: 'url' | 'file' | 'text';
|
|
sourceLocation: string;
|
|
parsedAt: Date;
|
|
parsingDuration: number;
|
|
|
|
// 统计信息
|
|
endpointCount: number;
|
|
schemaCount: number;
|
|
securitySchemeCount: number;
|
|
|
|
// 版本信息
|
|
openApiVersion: string;
|
|
parserVersion: string;
|
|
}
|
|
|
|
export interface ApiEndpoint {
|
|
// 基础信息
|
|
path: string;
|
|
method: HttpMethod;
|
|
operationId?: string;
|
|
summary?: string;
|
|
description?: string;
|
|
|
|
// 参数和响应
|
|
parameters: Parameter[];
|
|
requestBody?: RequestBody;
|
|
responses: ResponsesObject;
|
|
|
|
// 元数据
|
|
tags?: string[];
|
|
deprecated?: boolean;
|
|
security?: SecurityRequirement[];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 详细实施步骤
|
|
|
|
### 阶段 1: 准备工作 (1-2 天)
|
|
|
|
#### 1.1 创建包结构
|
|
```bash
|
|
# 创建新包目录
|
|
mkdir -p packages/mcp-swagger-parser/src/{core,parsers,extractors,types,utils,errors}
|
|
mkdir -p packages/mcp-swagger-parser/tests/{unit,integration,fixtures,__helpers__}
|
|
mkdir -p packages/mcp-swagger-parser/docs/{examples}
|
|
|
|
# 创建配置文件
|
|
touch packages/mcp-swagger-parser/{package.json,tsconfig.json,rollup.config.js,README.md}
|
|
```
|
|
|
|
#### 1.2 配置包管理
|
|
```json
|
|
// packages/mcp-swagger-parser/package.json
|
|
{
|
|
"name": "mcp-swagger-parser",
|
|
"version": "0.1.0",
|
|
"description": "OpenAPI/Swagger specification parser for MCP projects",
|
|
"main": "dist/index.js",
|
|
"module": "dist/index.esm.js",
|
|
"types": "dist/index.d.ts",
|
|
"exports": {
|
|
".": {
|
|
"import": "./dist/index.esm.js",
|
|
"require": "./dist/index.js",
|
|
"types": "./dist/index.d.ts"
|
|
}
|
|
},
|
|
"files": [
|
|
"dist",
|
|
"README.md"
|
|
],
|
|
"scripts": {
|
|
"build": "rollup -c",
|
|
"dev": "rollup -c -w",
|
|
"test": "jest",
|
|
"test:watch": "jest --watch",
|
|
"lint": "eslint src/**/*.ts",
|
|
"type-check": "tsc --noEmit"
|
|
},
|
|
"dependencies": {
|
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
"axios": "^1.6.0",
|
|
"js-yaml": "^4.1.0",
|
|
"zod": "^3.25.28"
|
|
},
|
|
"devDependencies": {
|
|
"@types/js-yaml": "^4.0.5",
|
|
"@types/node": "^20.9.0",
|
|
"jest": "^29.7.0",
|
|
"rollup": "^4.0.0",
|
|
"typescript": "^5.2.0"
|
|
},
|
|
"keywords": [
|
|
"openapi",
|
|
"swagger",
|
|
"parser",
|
|
"mcp",
|
|
"api",
|
|
"specification"
|
|
]
|
|
}
|
|
```
|
|
|
|
#### 1.3 TypeScript 配置
|
|
```json
|
|
// packages/mcp-swagger-parser/tsconfig.json
|
|
{
|
|
"extends": "../../tsconfig.json",
|
|
"compilerOptions": {
|
|
"outDir": "./dist",
|
|
"rootDir": "./src",
|
|
"declaration": true,
|
|
"declarationMap": true,
|
|
"composite": true
|
|
},
|
|
"include": ["src/**/*"],
|
|
"exclude": ["dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
|
|
}
|
|
```
|
|
|
|
### 阶段 2: 核心代码抽离 (3-4 天)
|
|
|
|
#### 2.1 抽离解析器核心类
|
|
```typescript
|
|
// packages/mcp-swagger-parser/src/core/parser.ts
|
|
|
|
import SwaggerParser from '@apidevtools/swagger-parser';
|
|
import { z } from 'zod';
|
|
import type {
|
|
ParsedApiSpec,
|
|
ParserOptions,
|
|
ParseFromUrlOptions,
|
|
ParseFromFileOptions,
|
|
ParseFromTextOptions,
|
|
ValidationResult
|
|
} from '../types';
|
|
|
|
export class OpenApiParser {
|
|
private options: ParserOptions;
|
|
|
|
constructor(options: ParserOptions = {}) {
|
|
this.options = {
|
|
validateInput: true,
|
|
resolveRefs: true,
|
|
allowEmpty: false,
|
|
...options
|
|
};
|
|
}
|
|
|
|
async parseFromUrl(url: string, options?: ParseFromUrlOptions): Promise<ParsedApiSpec> {
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
// 验证 URL 格式
|
|
if (!this.isValidUrl(url)) {
|
|
throw new Error(`Invalid URL format: ${url}`);
|
|
}
|
|
|
|
// 使用 swagger-parser 解析
|
|
const spec = await SwaggerParser.parse(url);
|
|
|
|
// 标准化和提取信息
|
|
const parsedSpec = await this.processSpec(spec, {
|
|
sourceType: 'url',
|
|
sourceLocation: url,
|
|
parsedAt: new Date(),
|
|
parsingDuration: Date.now() - startTime
|
|
});
|
|
|
|
return parsedSpec;
|
|
} catch (error) {
|
|
throw this.handleError(error, 'parseFromUrl', { url });
|
|
}
|
|
}
|
|
|
|
// 其他方法实现...
|
|
private async processSpec(spec: any, metadata: Partial<ParseMetadata>): Promise<ParsedApiSpec> {
|
|
// 处理和标准化规范
|
|
// 提取端点信息
|
|
// 生成元数据
|
|
}
|
|
|
|
private isValidUrl(url: string): boolean {
|
|
try {
|
|
new URL(url);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private handleError(error: any, method: string, context: any): Error {
|
|
// 统一错误处理
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2.2 抽离端点提取器
|
|
```typescript
|
|
// packages/mcp-swagger-parser/src/extractors/endpoint-extractor.ts
|
|
|
|
import type { ParsedApiSpec, ApiEndpoint, Parameter, ResponsesObject } from '../types';
|
|
|
|
export class EndpointExtractor {
|
|
static extractEndpoints(spec: ParsedApiSpec): ApiEndpoint[] {
|
|
const endpoints: ApiEndpoint[] = [];
|
|
|
|
Object.entries(spec.paths).forEach(([path, pathItem]) => {
|
|
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as const;
|
|
|
|
methods.forEach(method => {
|
|
const operation = pathItem[method];
|
|
if (!operation) return;
|
|
|
|
const endpoint: ApiEndpoint = {
|
|
path,
|
|
method: method.toUpperCase() as HttpMethod,
|
|
operationId: operation.operationId,
|
|
summary: operation.summary,
|
|
description: operation.description,
|
|
parameters: this.extractParameters(operation.parameters || []),
|
|
requestBody: operation.requestBody,
|
|
responses: operation.responses || {},
|
|
tags: operation.tags,
|
|
deprecated: operation.deprecated,
|
|
security: operation.security
|
|
};
|
|
|
|
endpoints.push(endpoint);
|
|
});
|
|
});
|
|
|
|
return endpoints;
|
|
}
|
|
|
|
private static extractParameters(parameters: any[]): Parameter[] {
|
|
// 参数提取逻辑
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2.3 创建类型定义
|
|
```typescript
|
|
// packages/mcp-swagger-parser/src/types/output.ts
|
|
|
|
export interface ParsedApiSpec {
|
|
openapi: string;
|
|
info: ApiInfo;
|
|
servers: ServerInfo[];
|
|
paths: PathsObject;
|
|
components?: ComponentsObject;
|
|
security?: SecurityRequirement[];
|
|
tags?: TagObject[];
|
|
metadata: ParseMetadata;
|
|
}
|
|
|
|
export interface ApiInfo {
|
|
title: string;
|
|
version: string;
|
|
description?: string;
|
|
termsOfService?: string;
|
|
contact?: ContactObject;
|
|
license?: LicenseObject;
|
|
}
|
|
|
|
export interface ParseMetadata {
|
|
sourceType: 'url' | 'file' | 'text';
|
|
sourceLocation: string;
|
|
parsedAt: Date;
|
|
parsingDuration: number;
|
|
endpointCount: number;
|
|
schemaCount: number;
|
|
securitySchemeCount: number;
|
|
openApiVersion: string;
|
|
parserVersion: string;
|
|
}
|
|
|
|
// 更多类型定义...
|
|
```
|
|
|
|
### 阶段 3: 服务器端重构 (2-3 天)
|
|
|
|
#### 3.1 更新服务器依赖
|
|
```json
|
|
// packages/mcp-swagger-server/package.json 添加依赖
|
|
{
|
|
"dependencies": {
|
|
"mcp-swagger-parser": "workspace:^0.1.0",
|
|
// 移除原有的解析相关依赖
|
|
// "@apidevtools/swagger-parser": "^10.1.0", // 删除
|
|
// "js-yaml": "^4.1.0", // 删除
|
|
// 保留 MCP 相关依赖
|
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3.2 重构转换逻辑
|
|
```typescript
|
|
// packages/mcp-swagger-server/src/converters/mcp-converter.ts
|
|
|
|
import { OpenApiParser, type ParsedApiSpec, type ApiEndpoint } from 'mcp-swagger-parser';
|
|
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
import type { ConvertConfig, McpConfig } from '../types';
|
|
|
|
export class McpConverter {
|
|
private parser: OpenApiParser;
|
|
|
|
constructor(parser?: OpenApiParser) {
|
|
this.parser = parser || new OpenApiParser();
|
|
}
|
|
|
|
async convertFromSource(source: InputSource, config: ConvertConfig): Promise<McpConfig> {
|
|
// 1. 使用解析库解析 OpenAPI
|
|
let spec: ParsedApiSpec;
|
|
|
|
switch (source.type) {
|
|
case 'url':
|
|
spec = await this.parser.parseFromUrl(source.content);
|
|
break;
|
|
case 'file':
|
|
spec = await this.parser.parseFromFile(source.content);
|
|
break;
|
|
case 'text':
|
|
spec = await this.parser.parseFromText(source.content);
|
|
break;
|
|
default:
|
|
throw new Error(`Unsupported source type: ${source.type}`);
|
|
}
|
|
|
|
// 2. 转换为 MCP 格式
|
|
return this.convertSpecToMcp(spec, config);
|
|
}
|
|
|
|
private async convertSpecToMcp(spec: ParsedApiSpec, config: ConvertConfig): Promise<McpConfig> {
|
|
// 提取端点
|
|
const endpoints = EndpointExtractor.extractEndpoints(spec);
|
|
|
|
// 应用过滤器
|
|
const filteredEndpoints = this.applyFilters(endpoints, config.filters);
|
|
|
|
// 转换为 MCP 工具
|
|
const tools = this.convertToMcpTools(filteredEndpoints, config);
|
|
|
|
// 生成 MCP 配置
|
|
return {
|
|
tools,
|
|
metadata: {
|
|
generatedAt: new Date(),
|
|
sourceInfo: spec.info,
|
|
endpointCount: filteredEndpoints.length,
|
|
parserVersion: spec.metadata.parserVersion
|
|
}
|
|
};
|
|
}
|
|
|
|
private convertToMcpTools(endpoints: ApiEndpoint[], config: ConvertConfig): Tool[] {
|
|
// MCP 工具转换逻辑 (保留原有逻辑)
|
|
}
|
|
|
|
private applyFilters(endpoints: ApiEndpoint[], filters: FilterConfig): ApiEndpoint[] {
|
|
// 过滤逻辑 (保留原有逻辑)
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3.3 更新服务器主逻辑
|
|
```typescript
|
|
// packages/mcp-swagger-server/src/server.ts
|
|
|
|
import { McpConverter } from './converters/mcp-converter.js';
|
|
import { OpenApiParser } from 'mcp-swagger-parser';
|
|
|
|
export class McpSwaggerServer {
|
|
private converter: McpConverter;
|
|
|
|
constructor() {
|
|
const parser = new OpenApiParser({
|
|
validateInput: true,
|
|
resolveRefs: true
|
|
});
|
|
|
|
this.converter = new McpConverter(parser);
|
|
}
|
|
|
|
async handleConvertRequest(source: InputSource, config: ConvertConfig): Promise<McpConfig> {
|
|
try {
|
|
return await this.converter.convertFromSource(source, config);
|
|
} catch (error) {
|
|
// 错误处理
|
|
throw new ConversionError(`Failed to convert API: ${error.message}`, error);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 阶段 4: 测试开发 (2-3 天)
|
|
|
|
#### 4.1 解析库单元测试
|
|
```typescript
|
|
// packages/mcp-swagger-parser/tests/unit/parser.test.ts
|
|
|
|
import { OpenApiParser } from '../../src';
|
|
import { readFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
describe('OpenApiParser', () => {
|
|
let parser: OpenApiParser;
|
|
|
|
beforeEach(() => {
|
|
parser = new OpenApiParser();
|
|
});
|
|
|
|
describe('parseFromUrl', () => {
|
|
it('should parse valid OpenAPI 3.0 spec from URL', async () => {
|
|
const result = await parser.parseFromUrl('https://petstore.swagger.io/v2/swagger.json');
|
|
|
|
expect(result.openapi).toBeDefined();
|
|
expect(result.info.title).toBe('Swagger Petstore');
|
|
expect(result.metadata.sourceType).toBe('url');
|
|
expect(result.metadata.endpointCount).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should throw error for invalid URL', async () => {
|
|
await expect(parser.parseFromUrl('invalid-url'))
|
|
.rejects.toThrow('Invalid URL format');
|
|
});
|
|
|
|
it('should handle network errors gracefully', async () => {
|
|
await expect(parser.parseFromUrl('https://nonexistent.example.com/api.json'))
|
|
.rejects.toThrow(/network|fetch|request/i);
|
|
});
|
|
});
|
|
|
|
describe('parseFromFile', () => {
|
|
it('should parse YAML file correctly', async () => {
|
|
const result = await parser.parseFromFile(join(__dirname, '../fixtures/petstore.yaml'));
|
|
|
|
expect(result.info.title).toBeDefined();
|
|
expect(result.metadata.sourceType).toBe('file');
|
|
});
|
|
|
|
it('should parse JSON file correctly', async () => {
|
|
const result = await parser.parseFromFile(join(__dirname, '../fixtures/petstore.json'));
|
|
|
|
expect(result.info.title).toBeDefined();
|
|
expect(result.metadata.sourceType).toBe('file');
|
|
});
|
|
});
|
|
|
|
describe('parseFromText', () => {
|
|
it('should parse JSON text', async () => {
|
|
const jsonText = readFileSync(join(__dirname, '../fixtures/petstore.json'), 'utf8');
|
|
const result = await parser.parseFromText(jsonText, 'json');
|
|
|
|
expect(result.info.title).toBeDefined();
|
|
expect(result.metadata.sourceType).toBe('text');
|
|
});
|
|
|
|
it('should auto-detect format', async () => {
|
|
const jsonText = readFileSync(join(__dirname, '../fixtures/petstore.json'), 'utf8');
|
|
const result = await parser.parseFromText(jsonText);
|
|
|
|
expect(result.info.title).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
#### 4.2 集成测试
|
|
```typescript
|
|
// packages/mcp-swagger-server/tests/integration/conversion.test.ts
|
|
|
|
import { McpSwaggerServer } from '../../src/server';
|
|
import type { InputSource, ConvertConfig } from '../../src/types';
|
|
|
|
describe('MCP Conversion Integration', () => {
|
|
let server: McpSwaggerServer;
|
|
|
|
beforeEach(() => {
|
|
server = new McpSwaggerServer();
|
|
});
|
|
|
|
it('should convert complete OpenAPI spec to MCP format', async () => {
|
|
const source: InputSource = {
|
|
type: 'url',
|
|
content: 'https://petstore.swagger.io/v2/swagger.json'
|
|
};
|
|
|
|
const config: ConvertConfig = {
|
|
filters: {
|
|
methods: ['GET', 'POST'],
|
|
includeDeprecated: false
|
|
},
|
|
transport: 'sse',
|
|
optimization: {
|
|
generateValidation: true,
|
|
includeExamples: true
|
|
}
|
|
};
|
|
|
|
const result = await server.handleConvertRequest(source, config);
|
|
|
|
expect(result.tools).toBeDefined();
|
|
expect(result.tools.length).toBeGreaterThan(0);
|
|
expect(result.metadata.generatedAt).toBeInstanceOf(Date);
|
|
expect(result.metadata.endpointCount).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
```
|
|
|
|
### 阶段 5: 文档和发布 (1-2 天)
|
|
|
|
#### 5.1 API 文档
|
|
```markdown
|
|
// packages/mcp-swagger-parser/docs/API.md
|
|
|
|
# MCP Swagger Parser API Documentation
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
npm install mcp-swagger-parser
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```typescript
|
|
import { OpenApiParser } from 'mcp-swagger-parser';
|
|
|
|
const parser = new OpenApiParser();
|
|
|
|
// Parse from URL
|
|
const spec = await parser.parseFromUrl('https://api.example.com/swagger.json');
|
|
|
|
// Parse from file
|
|
const spec = await parser.parseFromFile('./api.yaml');
|
|
|
|
// Parse from text
|
|
const spec = await parser.parseFromText(yamlContent, 'yaml');
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### Class: OpenApiParser
|
|
|
|
#### Constructor
|
|
|
|
```typescript
|
|
new OpenApiParser(options?: ParserOptions)
|
|
```
|
|
|
|
#### Methods
|
|
|
|
##### parseFromUrl(url, options?)
|
|
|
|
解析远程 URL 的 OpenAPI 规范。
|
|
|
|
**Parameters:**
|
|
- `url` (string): OpenAPI 规范的 URL
|
|
- `options` (ParseFromUrlOptions, optional): 解析选项
|
|
|
|
**Returns:** `Promise<ParsedApiSpec>`
|
|
|
|
##### parseFromFile(filePath, options?)
|
|
|
|
解析本地文件的 OpenAPI 规范。
|
|
|
|
**Parameters:**
|
|
- `filePath` (string): 文件路径
|
|
- `options` (ParseFromFileOptions, optional): 解析选项
|
|
|
|
**Returns:** `Promise<ParsedApiSpec>`
|
|
```
|
|
|
|
#### 5.2 使用示例
|
|
```typescript
|
|
// packages/mcp-swagger-parser/docs/examples/basic-usage.ts
|
|
|
|
import { OpenApiParser } from 'mcp-swagger-parser';
|
|
|
|
async function basicExample() {
|
|
const parser = new OpenApiParser({
|
|
validateInput: true,
|
|
resolveRefs: true
|
|
});
|
|
|
|
try {
|
|
// 解析 Petstore API
|
|
const spec = await parser.parseFromUrl('https://petstore.swagger.io/v2/swagger.json');
|
|
|
|
console.log('API Title:', spec.info.title);
|
|
console.log('API Version:', spec.info.version);
|
|
console.log('Endpoint Count:', spec.metadata.endpointCount);
|
|
|
|
// 提取端点信息
|
|
const endpoints = EndpointExtractor.extractEndpoints(spec);
|
|
endpoints.forEach(endpoint => {
|
|
console.log(`${endpoint.method} ${endpoint.path}: ${endpoint.summary}`);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Parse failed:', error.message);
|
|
}
|
|
}
|
|
|
|
basicExample();
|
|
```
|
|
|
|
---
|
|
|
|
## ⚡ 实施时间线
|
|
|
|
### 总体时间安排
|
|
|
|
```
|
|
阶段 1: 准备工作 (1-2 天) ██░░░░░░░░
|
|
阶段 2: 核心代码抽离 (3-4 天) ██████░░░░
|
|
阶段 3: 服务器端重构 (2-3 天) ████░░░░░░
|
|
阶段 4: 测试开发 (2-3 天) ████░░░░░░
|
|
阶段 5: 文档和发布 (1-2 天) ██░░░░░░░░
|
|
总计: (9-14 天) ██████████
|
|
```
|
|
|
|
### 每日任务分解
|
|
|
|
#### Day 1-2: 项目搭建
|
|
- [ ] 创建 `mcp-swagger-parser` 包结构
|
|
- [ ] 配置 TypeScript 和构建工具
|
|
- [ ] 设置测试环境
|
|
- [ ] 更新 workspace 配置
|
|
|
|
#### Day 3-4: 类型定义和核心抽离
|
|
- [ ] 设计和实现类型定义系统
|
|
- [ ] 抽离 `OpenApiParser` 核心类
|
|
- [ ] 实现 URL/文件/文本解析器
|
|
- [ ] 创建验证和标准化逻辑
|
|
|
|
#### Day 5-6: 信息提取器开发
|
|
- [ ] 实现 `EndpointExtractor`
|
|
- [ ] 实现 `SchemaExtractor`
|
|
- [ ] 实现 `SecurityExtractor`
|
|
- [ ] 添加错误处理机制
|
|
|
|
#### Day 7-8: 服务器端重构
|
|
- [ ] 重构 `mcp-swagger-server` 依赖
|
|
- [ ] 实现 `McpConverter` 类
|
|
- [ ] 更新服务器主逻辑
|
|
- [ ] 测试基本集成
|
|
|
|
#### Day 9-10: 测试开发
|
|
- [ ] 编写解析库单元测试
|
|
- [ ] 编写服务器集成测试
|
|
- [ ] 准备测试数据和 fixtures
|
|
- [ ] 测试覆盖率优化
|
|
|
|
#### Day 11-12: 文档和优化
|
|
- [ ] 编写 API 文档
|
|
- [ ] 创建使用示例
|
|
- [ ] 性能优化和错误处理改进
|
|
- [ ] 准备发布材料
|
|
|
|
#### Day 13-14: 发布和验证
|
|
- [ ] 发布 alpha 版本
|
|
- [ ] 端到端测试验证
|
|
- [ ] 修复发现的问题
|
|
- [ ] 准备正式发布
|
|
|
|
---
|
|
|
|
## 🎯 成功标准
|
|
|
|
### 技术指标
|
|
|
|
#### 功能完整性
|
|
- [ ] ✅ 所有现有解析功能正常工作
|
|
- [ ] ✅ 新解析库独立功能测试通过
|
|
- [ ] ✅ 服务器集成测试 100% 通过
|
|
- [ ] ✅ 性能不降低(解析速度保持或提升)
|
|
|
|
#### 代码质量
|
|
- [ ] ✅ 测试覆盖率 ≥ 85%
|
|
- [ ] ✅ TypeScript 严格模式通过
|
|
- [ ] ✅ ESLint 检查无错误
|
|
- [ ] ✅ 代码重复率 ≤ 5%
|
|
|
|
#### 文档完整性
|
|
- [ ] ✅ API 文档覆盖率 100%
|
|
- [ ] ✅ 使用示例完整
|
|
- [ ] ✅ 迁移指南清晰
|
|
- [ ] ✅ 错误处理文档完善
|
|
|
|
### 业务指标
|
|
|
|
#### 开发体验
|
|
- [ ] ✅ 新包可以独立开发和测试
|
|
- [ ] ✅ 构建时间减少 20%
|
|
- [ ] ✅ 包大小优化 15%
|
|
- [ ] ✅ 错误信息更加友好
|
|
|
|
#### 可维护性
|
|
- [ ] ✅ 代码职责清晰分离
|
|
- [ ] ✅ 依赖关系简化
|
|
- [ ] ✅ 新功能开发速度提升
|
|
- [ ] ✅ Bug 修复时间减少
|
|
|
|
---
|
|
|
|
## 🚨 风险控制
|
|
|
|
### 风险识别和缓解
|
|
|
|
#### 高风险项目
|
|
|
|
1. **破坏性变更风险** 🔴
|
|
```
|
|
风险: 重构可能导致现有功能异常
|
|
缓解:
|
|
- 保持 API 兼容性
|
|
- 全面的回归测试
|
|
- 分阶段发布策略
|
|
```
|
|
|
|
2. **性能回归风险** 🟡
|
|
```
|
|
风险: 新架构可能影响解析性能
|
|
缓解:
|
|
- 性能基准测试
|
|
- 关键路径优化
|
|
- 内存使用监控
|
|
```
|
|
|
|
#### 中风险项目
|
|
|
|
3. **依赖冲突风险** 🟡
|
|
```
|
|
风险: monorepo 中的依赖版本冲突
|
|
缓解:
|
|
- 统一依赖管理策略
|
|
- 使用 workspace 协议
|
|
- 定期依赖更新
|
|
```
|
|
|
|
4. **学习成本风险** 🟡
|
|
```
|
|
风险: 新架构增加团队学习成本
|
|
缓解:
|
|
- 详细的文档和示例
|
|
- 平滑的迁移路径
|
|
- 培训和知识分享
|
|
```
|
|
|
|
### 回滚计划
|
|
|
|
#### 紧急回滚策略
|
|
```bash
|
|
# 如果新版本出现严重问题,快速回滚步骤:
|
|
|
|
1. 恢复到重构前的代码版本
|
|
git revert <refactor-commit-hash>
|
|
|
|
2. 暂时移除新的解析库包
|
|
rm -rf packages/mcp-swagger-parser
|
|
|
|
3. 恢复服务器的原始依赖
|
|
git checkout HEAD~1 packages/mcp-swagger-server/package.json
|
|
|
|
4. 重新构建和测试
|
|
pnpm install && pnpm build && pnpm test
|
|
```
|
|
|
|
#### 渐进式回滚
|
|
```typescript
|
|
// 通过功能开关支持新旧实现并存
|
|
const USE_NEW_PARSER = process.env.USE_NEW_PARSER === 'true';
|
|
|
|
export async function parseOpenApi(source: InputSource): Promise<ParsedApiSpec> {
|
|
if (USE_NEW_PARSER) {
|
|
// 使用新的解析库
|
|
const parser = new OpenApiParser();
|
|
return parser.parseFromUrl(source.url);
|
|
} else {
|
|
// 使用旧的解析逻辑
|
|
return legacyParseOpenApi(source);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 总结和下一步
|
|
|
|
### 实施总结
|
|
|
|
这个重构计划将:
|
|
|
|
1. **提升架构质量** 📈
|
|
- 实现关注点分离
|
|
- 提高代码复用性
|
|
- 改善可测试性
|
|
|
|
2. **优化开发体验** 🚀
|
|
- 独立包开发
|
|
- 更快的构建速度
|
|
- 清晰的依赖关系
|
|
|
|
3. **增强扩展能力** 🔮
|
|
- 为未来功能扩展奠定基础
|
|
- 支持多格式解析
|
|
- 便于社区贡献
|
|
|
|
### 立即行动项
|
|
|
|
1. **今天开始** (Day 1)
|
|
- [ ] 创建 `packages/mcp-swagger-parser` 目录结构
|
|
- [ ] 配置基础的 `package.json` 和 `tsconfig.json`
|
|
- [ ] 设置基础构建脚本
|
|
|
|
2. **本周完成** (Day 1-5)
|
|
- [ ] 完成核心代码抽离
|
|
- [ ] 实现基础的解析功能
|
|
- [ ] 添加必要的测试用例
|
|
|
|
3. **下周目标** (Day 6-10)
|
|
- [ ] 完成服务器端重构
|
|
- [ ] 全面的测试覆盖
|
|
- [ ] 基础文档编写
|
|
|
|
### 长期愿景
|
|
|
|
通过这次重构,我们将建立一个:
|
|
|
|
- 🎯 **模块化的架构** - 每个包都有清晰的职责
|
|
- 🔄 **可复用的生态** - 解析库可服务于多个项目
|
|
- 🧪 **高质量的代码** - 完善的测试和文档
|
|
- 🚀 **快速的开发** - 提升团队开发效率
|
|
|
|
这不仅是一次技术重构,更是为项目长期发展奠定坚实基础的重要投资。
|
|
|
|
---
|
|
|
|
**实施状态跟踪**: 本文档将随着实施进展持续更新,确保计划与实际执行保持同步。
|