yapi-mcp 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

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.
yapi_mcp/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """YApi MCP Server - Model Context Protocol adapter for YApi."""
2
+
3
+ from yapi_mcp.server import main, mcp
4
+
5
+ __all__ = ["main", "mcp"]
yapi_mcp/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for running yapi-mcp as a module: python -m yapi_mcp."""
2
+
3
+ from yapi_mcp.server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -1,48 +1,48 @@
1
- """Configuration management for YApi MCP Server."""
2
-
3
- from pydantic import Field, HttpUrl
4
- from pydantic_settings import BaseSettings, SettingsConfigDict
5
-
6
-
7
- class ServerConfig(BaseSettings):
8
- """YApi MCP Server configuration loaded from environment variables or .env file."""
9
-
10
- model_config = SettingsConfigDict(
11
- env_file=".env",
12
- env_file_encoding="utf-8",
13
- case_sensitive=False,
14
- )
15
-
16
- yapi_server_url: HttpUrl = Field(
17
- ...,
18
- description="YApi server base URL",
19
- examples=["https://yapi.example.com"],
20
- )
21
-
22
- yapi_token: str = Field(
23
- ...,
24
- min_length=1,
25
- description="YApi authentication token (_yapi_token cookie)",
26
- )
27
-
28
- yapi_uid: str = Field(
29
- ...,
30
- min_length=1,
31
- description="YApi user ID (_yapi_uid cookie)",
32
- )
33
-
34
- yapi_cas: str | None = Field(
35
- default=None,
36
- description="Optional CAS authentication cookie (e.g., ZYBIPSCAS for custom deployments)",
37
- )
38
-
39
- @property
40
- def cookies(self) -> dict[str, str]:
41
- """Return cookies dictionary for YApi API authentication."""
42
- cookies = {
43
- "_yapi_token": self.yapi_token,
44
- "_yapi_uid": self.yapi_uid,
45
- }
46
- if self.yapi_cas:
47
- cookies["ZYBIPSCAS"] = self.yapi_cas
48
- return cookies
1
+ """Configuration management for YApi MCP Server."""
2
+
3
+ from pydantic import Field, HttpUrl
4
+ from pydantic_settings import BaseSettings, SettingsConfigDict
5
+
6
+
7
+ class ServerConfig(BaseSettings):
8
+ """YApi MCP Server configuration loaded from environment variables or .env file."""
9
+
10
+ model_config = SettingsConfigDict(
11
+ env_file=".env",
12
+ env_file_encoding="utf-8",
13
+ case_sensitive=False,
14
+ )
15
+
16
+ yapi_server_url: HttpUrl = Field(
17
+ ...,
18
+ description="YApi server base URL",
19
+ examples=["https://yapi.example.com"],
20
+ )
21
+
22
+ yapi_token: str = Field(
23
+ ...,
24
+ min_length=1,
25
+ description="YApi authentication token (_yapi_token cookie)",
26
+ )
27
+
28
+ yapi_uid: str = Field(
29
+ ...,
30
+ min_length=1,
31
+ description="YApi user ID (_yapi_uid cookie)",
32
+ )
33
+
34
+ yapi_cas: str | None = Field(
35
+ default=None,
36
+ description="Optional CAS authentication cookie (e.g., ZYBIPSCAS for custom deployments)",
37
+ )
38
+
39
+ @property
40
+ def cookies(self) -> dict[str, str]:
41
+ """Return cookies dictionary for YApi API authentication."""
42
+ cookies = {
43
+ "_yapi_token": self.yapi_token,
44
+ "_yapi_uid": self.yapi_uid,
45
+ }
46
+ if self.yapi_cas:
47
+ cookies["ZYBIPSCAS"] = self.yapi_cas
48
+ return cookies
yapi_mcp/server.py ADDED
@@ -0,0 +1,185 @@
1
+ """YApi MCP Server - Main server module with fastmcp."""
2
+
3
+ import json
4
+ from functools import cache
5
+ from typing import Annotated
6
+
7
+ import httpx
8
+ from fastmcp import FastMCP
9
+
10
+ from yapi_mcp.config import ServerConfig
11
+ from yapi_mcp.yapi.client import YApiClient
12
+ from yapi_mcp.yapi.errors import map_http_error_to_mcp
13
+
14
+
15
+ class MCPToolError(RuntimeError):
16
+ """Base exception for MCP tool failures."""
17
+
18
+
19
+ class MCPHTTPError(MCPToolError):
20
+ """Exception raised when YApi returns an HTTP error."""
21
+
22
+
23
+ class MCPValidationError(MCPToolError):
24
+ """Exception raised when tool input validation fails."""
25
+
26
+
27
+ class InvalidInterfacePathError(ValueError):
28
+ """Raised when an interface path does not start with a slash."""
29
+
30
+ def __init__(self) -> None:
31
+ super().__init__("接口路径必须以 / 开头")
32
+
33
+
34
+ def _http_error_to_tool_error(error: httpx.HTTPStatusError) -> MCPHTTPError:
35
+ mcp_error = map_http_error_to_mcp(error)
36
+ return MCPHTTPError(mcp_error.message)
37
+
38
+
39
+ def _wrap_validation_error(error: ValueError) -> MCPValidationError:
40
+ message = f"参数验证失败: {error!s}"
41
+ return MCPValidationError(message)
42
+
43
+
44
+ def _wrap_tool_error(prefix: str, error: Exception) -> MCPToolError:
45
+ message = f"{prefix}: {error!s}"
46
+ return MCPToolError(message)
47
+
48
+
49
+ def _ensure_path_starts_with_slash(path: str) -> None:
50
+ if not path.startswith("/"):
51
+ raise InvalidInterfacePathError
52
+
53
+
54
+ SEARCH_INTERFACES_ERROR = "搜索接口失败"
55
+ GET_INTERFACE_ERROR = "获取接口失败"
56
+ SAVE_INTERFACE_ERROR = "保存接口失败"
57
+
58
+
59
+ # Initialize MCP server
60
+ mcp = FastMCP(
61
+ "YApi MCP Server",
62
+ version="0.1.0",
63
+ )
64
+
65
+
66
+ @cache
67
+ def get_config() -> ServerConfig:
68
+ """Get or create ServerConfig instance (cached)."""
69
+ return ServerConfig()
70
+
71
+
72
+ # Tool implementations will be added in subsequent tasks (T019-T022)
73
+
74
+
75
+ @mcp.tool()
76
+ async def yapi_search_interfaces(
77
+ project_id: Annotated[int, "YApi 项目 ID"],
78
+ keyword: Annotated[str, "搜索关键词(匹配接口标题/路径/描述)"],
79
+ ) -> str:
80
+ """在指定 YApi 项目中搜索接口,支持按标题、路径、描述模糊匹配."""
81
+ config = get_config()
82
+ try:
83
+ async with YApiClient(str(config.yapi_server_url), config.cookies) as client:
84
+ results = await client.search_interfaces(project_id, keyword)
85
+ # Return JSON string with search results
86
+ return json.dumps(
87
+ [result.model_dump(by_alias=True) for result in results],
88
+ ensure_ascii=False,
89
+ indent=2,
90
+ )
91
+ except MCPToolError:
92
+ raise
93
+ except httpx.HTTPStatusError as exc:
94
+ raise _http_error_to_tool_error(exc) from exc
95
+ except Exception as exc:
96
+ prefix = SEARCH_INTERFACES_ERROR
97
+ raise _wrap_tool_error(prefix, exc) from exc
98
+
99
+
100
+ @mcp.tool()
101
+ async def yapi_get_interface(
102
+ interface_id: Annotated[int, "接口 ID"],
103
+ ) -> str:
104
+ """获取 YApi 接口的完整定义(包括请求参数、响应结构、描述等)."""
105
+ config = get_config()
106
+ try:
107
+ async with YApiClient(str(config.yapi_server_url), config.cookies) as client:
108
+ interface = await client.get_interface(interface_id)
109
+ return json.dumps(
110
+ interface.model_dump(by_alias=True),
111
+ ensure_ascii=False,
112
+ indent=2,
113
+ )
114
+ except MCPToolError:
115
+ raise
116
+ except httpx.HTTPStatusError as exc:
117
+ raise _http_error_to_tool_error(exc) from exc
118
+ except Exception as exc:
119
+ prefix = GET_INTERFACE_ERROR
120
+ raise _wrap_tool_error(prefix, exc) from exc
121
+
122
+
123
+ @mcp.tool()
124
+ async def yapi_save_interface(
125
+ catid: Annotated[int, "分类 ID (必需)"],
126
+ project_id: Annotated[int | None, "项目 ID (创建时必需)"] = None,
127
+ interface_id: Annotated[int | None, "接口 ID (有值=更新,无值=创建)"] = None,
128
+ title: Annotated[str | None, "接口标题 (创建时必需)"] = None,
129
+ path: Annotated[str | None, "接口路径 (创建时必需,以/开头)"] = None,
130
+ method: Annotated[str | None, "HTTP方法 (创建时必需)"] = None,
131
+ req_body: Annotated[str, "请求参数(JSON字符串)"] = "",
132
+ res_body: Annotated[str, "响应结构(JSON字符串)"] = "",
133
+ desc: Annotated[str, "接口描述"] = "",
134
+ req_body_type: Annotated[str | None, "请求体类型(form/json/raw/file)"] = None,
135
+ req_body_is_json_schema: Annotated[bool | None, "请求体是否为JSON Schema"] = None,
136
+ res_body_type: Annotated[str | None, "响应体类型(json/raw)"] = None,
137
+ res_body_is_json_schema: Annotated[bool | None, "响应体是否为JSON Schema"] = None,
138
+ ) -> str:
139
+ """保存 YApi 接口定义。interface_id 有值则更新,无值则创建新接口。"""
140
+ config = get_config()
141
+ try:
142
+ if path is not None:
143
+ _ensure_path_starts_with_slash(path)
144
+
145
+ async with YApiClient(str(config.yapi_server_url), config.cookies) as client:
146
+ result = await client.save_interface(
147
+ catid=catid,
148
+ project_id=project_id,
149
+ interface_id=interface_id,
150
+ title=title,
151
+ path=path,
152
+ method=method,
153
+ req_body=req_body,
154
+ res_body=res_body,
155
+ desc=desc,
156
+ req_body_type=req_body_type,
157
+ req_body_is_json_schema=req_body_is_json_schema,
158
+ res_body_type=res_body_type,
159
+ res_body_is_json_schema=res_body_is_json_schema,
160
+ )
161
+ action = result["action"]
162
+ iface_id = result["interface_id"]
163
+ message = "接口创建成功" if action == "created" else "接口更新成功"
164
+ return json.dumps(
165
+ {"action": action, "interface_id": iface_id, "message": message},
166
+ ensure_ascii=False,
167
+ )
168
+ except MCPToolError:
169
+ raise
170
+ except httpx.HTTPStatusError as exc:
171
+ raise _http_error_to_tool_error(exc) from exc
172
+ except ValueError as exc:
173
+ raise _wrap_validation_error(exc) from exc
174
+ except Exception as exc:
175
+ prefix = SAVE_INTERFACE_ERROR
176
+ raise _wrap_tool_error(prefix, exc) from exc
177
+
178
+
179
+ def main() -> None:
180
+ """Entry point for uvx yapi-mcp command."""
181
+ mcp.run()
182
+
183
+
184
+ if __name__ == "__main__":
185
+ main()
@@ -1 +1 @@
1
- """MCP tools implementation."""
1
+ """MCP tools implementation."""
@@ -1 +1 @@
1
- """YApi API client module."""
1
+ """YApi API client module."""
@@ -0,0 +1,259 @@
1
+ """YApi API HTTP client implementation."""
2
+
3
+ from typing import Any, NoReturn
4
+
5
+ import httpx
6
+
7
+ from .models import YApiErrorResponse, YApiInterface, YApiInterfaceSummary
8
+
9
+
10
+ def _raise_yapi_api_error(response: httpx.Response, error: YApiErrorResponse) -> NoReturn:
11
+ message = f"YApi API error: {error.errmsg} (code: {error.errcode})"
12
+ raise httpx.HTTPStatusError(
13
+ message,
14
+ request=response.request,
15
+ response=response,
16
+ )
17
+
18
+
19
+ class YApiClient:
20
+ """Async HTTP client for YApi API with cookie-based authentication."""
21
+
22
+ def __init__(self, base_url: str, cookies: dict[str, str], timeout: float = 10.0) -> None:
23
+ """Initialize YApi client.
24
+
25
+ Args:
26
+ base_url: YApi server base URL (e.g., "https://yapi.example.com")
27
+ cookies: Authentication cookies dict with _yapi_token, _yapi_uid, ZYBIPSCAS
28
+ timeout: Request timeout in seconds (default: 10.0)
29
+ """
30
+ self.base_url = base_url.rstrip("/")
31
+ self.client = httpx.AsyncClient(
32
+ base_url=f"{self.base_url}/api",
33
+ cookies=cookies,
34
+ timeout=timeout,
35
+ follow_redirects=True,
36
+ )
37
+
38
+ async def __aenter__(self) -> "YApiClient":
39
+ """Async context manager entry."""
40
+ return self
41
+
42
+ async def __aexit__(self, *args: object) -> None:
43
+ """Async context manager exit - close HTTP client."""
44
+ await self.client.aclose()
45
+
46
+ async def close(self) -> None:
47
+ """Close the HTTP client connection."""
48
+ await self.client.aclose()
49
+
50
+ def _check_response(self, response: httpx.Response) -> None:
51
+ """Check YApi API response for errors and raise appropriate exceptions.
52
+
53
+ Args:
54
+ response: httpx Response object
55
+
56
+ Raises:
57
+ httpx.HTTPStatusError: For HTTP-level errors (4xx, 5xx)
58
+ """
59
+ # First check HTTP status codes
60
+ response.raise_for_status()
61
+
62
+ # Then check YApi API-level errors (errcode != 0)
63
+ try:
64
+ data = response.json()
65
+ if isinstance(data, dict) and "errcode" in data and data["errcode"] != 0:
66
+ error = YApiErrorResponse(**data)
67
+ # YApi returns errcode != 0 for business logic errors
68
+ # Treat these as HTTP-equivalent errors
69
+ _raise_yapi_api_error(response, error)
70
+ except (ValueError, KeyError):
71
+ # Not a JSON response or doesn't have errcode - proceed normally
72
+ pass
73
+
74
+ async def search_interfaces(
75
+ self, project_id: int, keyword: str
76
+ ) -> list[YApiInterfaceSummary]:
77
+ """Search interfaces in a YApi project.
78
+
79
+ 使用 list_menu 接口获取项目下全量接口,突破 50 条限制。
80
+ 支持按接口标题、路径、描述、分类名进行搜索。
81
+
82
+ Args:
83
+ project_id: YApi project ID
84
+ keyword: Search keyword (matches title, path, description, category name)
85
+
86
+ Returns:
87
+ List of matching interface summaries
88
+
89
+ Raises:
90
+ httpx.HTTPStatusError: For authentication, permission, or server errors
91
+ """
92
+ # 使用 list_menu 接口获取全量接口(无分页限制)
93
+ response = await self.client.get(
94
+ "/interface/list_menu",
95
+ params={"project_id": project_id},
96
+ )
97
+ self._check_response(response)
98
+
99
+ data = response.json()
100
+ categories = data.get("data", [])
101
+
102
+ # 展开树形结构为扁平列表,同时记录分类名
103
+ interfaces: list[dict] = []
104
+ for cat in categories:
105
+ cat_name = cat.get("name", "")
106
+ for iface in cat.get("list", []):
107
+ # 将分类名注入到接口数据中,用于搜索
108
+ iface["_cat_name"] = cat_name
109
+ interfaces.append(iface)
110
+
111
+ # 客户端关键词过滤(支持分类名搜索)
112
+ if keyword:
113
+ keyword_lower = keyword.lower()
114
+ interfaces = [
115
+ iface
116
+ for iface in interfaces
117
+ if keyword_lower in iface.get("title", "").lower()
118
+ or keyword_lower in iface.get("path", "").lower()
119
+ or keyword_lower in iface.get("desc", "").lower()
120
+ or keyword_lower in iface.get("_cat_name", "").lower()
121
+ ]
122
+
123
+ return [YApiInterfaceSummary(**iface) for iface in interfaces]
124
+
125
+ async def get_interface(self, interface_id: int) -> YApiInterface:
126
+ """Get complete interface definition by ID.
127
+
128
+ Args:
129
+ interface_id: YApi interface ID
130
+
131
+ Returns:
132
+ Complete interface definition
133
+
134
+ Raises:
135
+ httpx.HTTPStatusError: For authentication, not found, or server errors
136
+ """
137
+ response = await self.client.get("/interface/get", params={"id": interface_id})
138
+ self._check_response(response)
139
+
140
+ data = response.json()
141
+ return YApiInterface(**data["data"])
142
+
143
+ async def save_interface(
144
+ self,
145
+ catid: int,
146
+ project_id: int | None = None,
147
+ interface_id: int | None = None,
148
+ title: str | None = None,
149
+ path: str | None = None,
150
+ method: str | None = None,
151
+ req_body: str = "",
152
+ res_body: str = "",
153
+ desc: str = "",
154
+ req_body_type: str | None = None,
155
+ req_body_is_json_schema: bool | None = None,
156
+ res_body_type: str | None = None,
157
+ res_body_is_json_schema: bool | None = None,
158
+ ) -> dict[str, Any]:
159
+ """Save interface definition (create or update).
160
+
161
+ If interface_id is provided, update the existing interface.
162
+ If interface_id is not provided, create a new interface.
163
+
164
+ Args:
165
+ catid: Category ID (required for both create and update)
166
+ project_id: Project ID (required for create)
167
+ interface_id: Interface ID (if provided, update; otherwise create)
168
+ title: Interface title (required for create)
169
+ path: Interface path, must start with / (required for create)
170
+ method: HTTP method (required for create)
171
+ req_body: Request body definition (JSON string, optional)
172
+ res_body: Response body definition (JSON string, optional)
173
+ desc: Interface description (optional)
174
+ req_body_type: Request body type (form, json, raw, file)
175
+ req_body_is_json_schema: Whether req_body is JSON Schema format
176
+ res_body_type: Response body type (json, raw)
177
+ res_body_is_json_schema: Whether res_body is JSON Schema format
178
+
179
+ Returns:
180
+ dict with keys: action ("created" or "updated"), interface_id (int)
181
+
182
+ Raises:
183
+ ValueError: When required parameters are missing for create mode
184
+ httpx.HTTPStatusError: For validation, permission, or server errors
185
+ """
186
+ if interface_id is None:
187
+ # 创建模式:校验必填参数
188
+ missing = []
189
+ if project_id is None:
190
+ missing.append("project_id")
191
+ if title is None:
192
+ missing.append("title")
193
+ if path is None:
194
+ missing.append("path")
195
+ if method is None:
196
+ missing.append("method")
197
+ if missing:
198
+ msg = f"创建接口需要以下参数: {', '.join(missing)}"
199
+ raise ValueError(msg)
200
+
201
+ # 调用创建 API
202
+ payload: dict[str, Any] = {
203
+ "project_id": project_id,
204
+ "catid": catid,
205
+ "title": title,
206
+ "path": path,
207
+ "method": method.upper(), # type: ignore[union-attr]
208
+ }
209
+
210
+ if req_body:
211
+ payload["req_body_other"] = req_body
212
+ payload["req_body_type"] = req_body_type if req_body_type else "json"
213
+ payload["req_body_is_json_schema"] = (
214
+ req_body_is_json_schema if req_body_is_json_schema is not None else True
215
+ )
216
+ if res_body:
217
+ payload["res_body"] = res_body
218
+ payload["res_body_type"] = res_body_type if res_body_type else "json"
219
+ payload["res_body_is_json_schema"] = (
220
+ res_body_is_json_schema if res_body_is_json_schema is not None else True
221
+ )
222
+ if desc:
223
+ payload["desc"] = desc
224
+
225
+ response = await self.client.post("/interface/add", json=payload)
226
+ self._check_response(response)
227
+
228
+ data = response.json()
229
+ return {"action": "created", "interface_id": int(data["data"]["_id"])}
230
+
231
+ # 更新模式
232
+ payload = {"id": interface_id, "catid": catid}
233
+
234
+ if title is not None:
235
+ payload["title"] = title
236
+ if path is not None:
237
+ payload["path"] = path
238
+ if method is not None:
239
+ payload["method"] = method.upper()
240
+ if req_body:
241
+ payload["req_body_other"] = req_body
242
+ if res_body:
243
+ payload["res_body"] = res_body
244
+ if desc is not None:
245
+ payload["desc"] = desc
246
+ # 类型标记参数独立设置(不依赖内容参数)
247
+ if req_body_type is not None:
248
+ payload["req_body_type"] = req_body_type
249
+ if req_body_is_json_schema is not None:
250
+ payload["req_body_is_json_schema"] = req_body_is_json_schema
251
+ if res_body_type is not None:
252
+ payload["res_body_type"] = res_body_type
253
+ if res_body_is_json_schema is not None:
254
+ payload["res_body_is_json_schema"] = res_body_is_json_schema
255
+
256
+ response = await self.client.post("/interface/up", json=payload)
257
+ self._check_response(response)
258
+
259
+ return {"action": "updated", "interface_id": interface_id}
@@ -1,14 +1,33 @@
1
1
  """Error mapping from YApi/HTTP errors to MCP errors."""
2
2
 
3
- from typing import Any
3
+ from collections.abc import Mapping, MutableMapping
4
4
 
5
5
  import httpx
6
6
 
7
+ ErrorData = MutableMapping[str, object]
8
+
9
+ HTTP_STATUS_UNAUTHORIZED = 401
10
+ HTTP_STATUS_NOT_FOUND = 404
11
+ HTTP_STATUS_FORBIDDEN = 403
12
+ HTTP_STATUS_SERVER_ERROR = 500
13
+ HTTP_STATUS_BAD_REQUEST = 400
14
+
15
+ MCP_CODE_AUTH_FAILED = -32001
16
+ MCP_CODE_NOT_FOUND = -32002
17
+ MCP_CODE_FORBIDDEN = -32003
18
+ MCP_CODE_SERVER_ERROR = -32000
19
+ MCP_CODE_INVALID_PARAMS = -32602
20
+
7
21
 
8
22
  class MCPError(Exception):
9
23
  """MCP protocol error with error code and optional data."""
10
24
 
11
- def __init__(self, code: int, message: str, data: Any = None) -> None:
25
+ def __init__(
26
+ self,
27
+ code: int,
28
+ message: str,
29
+ data: ErrorData | None = None,
30
+ ) -> None:
12
31
  """Initialize MCP error.
13
32
 
14
33
  Args:
@@ -21,9 +40,9 @@ class MCPError(Exception):
21
40
  self.message = message
22
41
  self.data = data
23
42
 
24
- def to_dict(self) -> dict[str, Any]:
43
+ def to_dict(self) -> dict[str, object]:
25
44
  """Convert to MCP error response dict."""
26
- result: dict[str, Any] = {"code": self.code, "message": self.message}
45
+ result: dict[str, object] = {"code": self.code, "message": self.message}
27
46
  if self.data is not None:
28
47
  result["data"] = self.data
29
48
  return result
@@ -49,49 +68,52 @@ def map_http_error_to_mcp(error: httpx.HTTPStatusError) -> MCPError:
49
68
  status_code = error.response.status_code
50
69
 
51
70
  # Try to extract YApi error details from response
52
- error_data: dict[str, Any] = {"http_status": status_code}
71
+ error_data: ErrorData = {"http_status": status_code}
53
72
  try:
54
73
  yapi_error = error.response.json()
55
- if isinstance(yapi_error, dict):
56
- error_data["yapi_error"] = yapi_error
74
+ if isinstance(yapi_error, Mapping):
75
+ error_data["yapi_error"] = dict(yapi_error)
57
76
  except Exception:
58
77
  # If response is not JSON, include response text
59
78
  error_data["response_text"] = error.response.text[:200]
60
79
 
61
80
  # Map HTTP status codes to MCP error codes
62
- if status_code == 401:
81
+ if status_code == HTTP_STATUS_UNAUTHORIZED:
63
82
  return MCPError(
64
- code=-32001,
83
+ code=MCP_CODE_AUTH_FAILED,
65
84
  message="认证失败: Cookie 无效或过期",
66
85
  data=error_data,
67
86
  )
68
- if status_code == 404:
87
+ if status_code == HTTP_STATUS_NOT_FOUND:
88
+ errmsg = error_data.get("yapi_error", {}).get("errmsg", "Resource not found")
69
89
  return MCPError(
70
- code=-32002,
71
- message=f"资源不存在: {error_data.get('yapi_error', {}).get('errmsg', 'Resource not found')}",
90
+ code=MCP_CODE_NOT_FOUND,
91
+ message=f"资源不存在: {errmsg}",
72
92
  data=error_data,
73
93
  )
74
- if status_code == 403:
94
+ if status_code == HTTP_STATUS_FORBIDDEN:
75
95
  return MCPError(
76
- code=-32003,
96
+ code=MCP_CODE_FORBIDDEN,
77
97
  message="权限不足: 无法操作该项目/接口",
78
98
  data=error_data,
79
99
  )
80
- if status_code >= 500:
100
+ if status_code >= HTTP_STATUS_SERVER_ERROR:
101
+ errmsg = error_data.get("yapi_error", {}).get("errmsg", "Internal server error")
81
102
  return MCPError(
82
- code=-32000,
83
- message=f"YApi 服务器错误: {error_data.get('yapi_error', {}).get('errmsg', 'Internal server error')}",
103
+ code=MCP_CODE_SERVER_ERROR,
104
+ message=f"YApi 服务器错误: {errmsg}",
84
105
  data=error_data,
85
106
  )
86
- if status_code == 400:
107
+ if status_code == HTTP_STATUS_BAD_REQUEST:
108
+ errmsg = error_data.get("yapi_error", {}).get("errmsg", "Bad request")
87
109
  return MCPError(
88
- code=-32602,
89
- message=f"Invalid params: {error_data.get('yapi_error', {}).get('errmsg', 'Bad request')}",
110
+ code=MCP_CODE_INVALID_PARAMS,
111
+ message=f"Invalid params: {errmsg}",
90
112
  data=error_data,
91
113
  )
92
114
  # Other 4xx errors
93
115
  return MCPError(
94
- code=-32602,
116
+ code=MCP_CODE_INVALID_PARAMS,
95
117
  message=f"Invalid params: HTTP {status_code}",
96
118
  data=error_data,
97
119
  )