OpenAPI 스펙에서 MCP
OpenAPI 스펙에서 MCP (MCP from OpenAPI Specs)
LiteLLM은 사용자 지정 MCP 서버 코드 없이도 어떤 OpenAPI/Swagger 스펙이든 MCP 서버로 변환할 수 있어요.
출처: 문서
본문
1단계: MCP 서버 추가 (Add the MCP Server)
config.yaml에서 OpenAPI 기반 서버를 추가해 주세요:
config.yaml:
mcp_servers:
petstore_mcp:
url: "https://petstore.swagger.io/v2"
spec_path: "/path/to/openapi.json"
auth_type: "none"
my_api_mcp:
url: "http://0.0.0.0:8090"
spec_path: "/path/to/openapi.json"
auth_type: "api_key"
auth_value: "your-api-key-here"
secured_api_mcp:
url: "https://api.example.com"
spec_path: "/path/to/openapi.json"
auth_type: "bearer_token"
auth_value: "your-bearer-token"
또는 UI에서: MCP Servers → Add New MCP Server로 가서 URL과 스펙 경로를 입력하면 LiteLLM이 스펙을 가져와 모든 엔드포인트를 도구로 로드해요.
구성 파라미터 (Configuration parameters):
| 파라미터 | 필수 | 설명 |
|---|---|---|
url |
예 | API의 기본 URL |
spec_path |
예 | OpenAPI 스펙의 경로 또는 URL(JSON 또는 YAML) |
auth_type |
아니오 | none, api_key, bearer_token, basic, authorization, oauth2 |
auth_value |
아니오 | 인증 값(auth_type 설정 시 필수) |
description |
아니오 | 선택적 설명 |
allowed_tools |
아니오 | 특정 도구의 허용 목록 |
disallowed_tools |
아니오 | 특정 도구의 차단 목록 |
지원 스펙 버전: OpenAPI 3.0.x, 3.1.x, Swagger 2.0. 각 작업의 operationId가 도구 이름이 되므로 고유해야 해요.
내부 스펙 URL (SSRF) (Internal spec URLs (SSRF))
spec_path가 http:// 또는 https:// URL이면 LiteLLM 프록시는 기본적으로 SSRF 보호를 활성화해 스펙을 가져와요. 호스트명이 해석되고, 해석된 주소 중 하나라도 전역 라우팅 가능하지 않으면(예: 10.x, 192.168.x, 127.0.0.1) 요청이 거부돼요. 단, URL의 호스트명(해석된 IP가 아니라)을 허용 목록에 추가하면 예외예요.
일반적인 경우:
- 스펙 URL이
https://api.example.com/...인데 네트워크 내부 DNS가 사설 IP를 반환한다면,api.example.com을 허용 목록에 추가(포트를 고정했다면api.example.com:443). - 스펙 URL이
http://127.0.0.1:8080/openapi.json이면127.0.0.1또는127.0.0.1:8080을 추가.
프록시 config.yaml의 litellm_settings 아래에 구성하세요(general_settings에서 읽지 않음):
config.yaml:
litellm_settings:
user_url_validation: true # default; set false only if you fully trust URL sources
user_url_allowed_hosts:
- "api.example.com"
- "127.0.0.1"
- "127.0.0.1:8080"
이 필드들의 전체 참조는 config settings: litellm_settings를 참고해 주세요.
2단계: 도구 이름·설명 재정의 (선택) (Optionally Override Tool Names and Descriptions)
기본적으로 도구 이름과 설명은 스펙의 operationId와 description 필드에서 가져와요. 업스트림 스펙을 건드리지 않고 MCP 클라이언트가 더 깔끔한 것을 보도록 이름을 바꾸거나 다시 쓸 수 있어요.
UI에서 (From the UI)
각 도구 카드에는 연필 아이콘이 있어요. 클릭해 인라인 편집기를 엽니다.
- Display Name — MCP 클라이언트가 보는 이름을 재정의
- Description — MCP 클라이언트가 보는 설명을 재정의
- 필드를 비워두면 스펙의 원본 유지
재정의를 설정하면 도구 카드에 보라색 Custom name 배지가 나타나요.
API에서 (From the API)
생성 또는 업데이트 요청에 tool_name_to_display_name과 tool_name_to_description을 전달해 주세요:
도구 이름 재정의로 서버 생성:
curl -X POST http://localhost:4000/v1/mcp/server \
-H "Authorization: Bearer $LITEL..._KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "petstore_mcp",
"url": "https://petstore.swagger.io/v2",
"spec_path": "/path/to/openapi.json",
"tool_name_to_display_name": {
"getPetById": "Get Pet",
"findPetsByStatus": "List Available Pets"
},
"tool_name_to_description": {
"getPetById": "Look up a pet by its ID",
"findPetsByStatus": "Returns all pets matching a given status (available, pending, sold)"
}
}'
기존 서버의 재정의 업데이트:
curl -X PUT http://localhost:4000/v1/mcp/server \
-H "Authorization: Bearer $LITEL..._KEY" \
-H "Content-Type: application/json" \
-d '{
"server_id": "<server-id>",
"tool_name_to_display_name": {
"getPetById": "Get Pet"
},
"tool_name_to_description": {
"getPetById": "Look up a pet by its ID"
}
}'
맵 키는 접두사가 붙지 않은 스펙의 **원본 operationId**예요. LiteLLM은 조회 전에 서버 접두사를 제거해요.
예를 들어 서버가 petstore_mcp이면 도구는 petstore_mcp-getPetById로 노출돼요. 맵 키는 여전히 getPetById예요.
재정의 전후 (Before and after):
# Without overrides
Tool: "petstore_mcp-getPetById"
Description: "Returns a single pet"
Tool: "petstore_mcp-findPetsByStatus"
Description: "Finds Pets by status"
# After overrides
Tool: "Get Pet"
Description: "Look up a pet by its ID"
Tool: "List Available Pets"
Description: "Returns all pets matching a given status (available, pending, sold)"
서버 사용 (Using the Server)
Python FastMCP:
from fastmcp import Client
import asyncio
config = {
"mcpServers": {
"petstore": {
"url": "http://localhost:4000/petstore_mcp/mcp",
"headers": {
"x-litellm-api-key": "Bearer sk-<your-litellm-api-key>"
}
}
}
}
client = Client(config)
async def main():
async with client:
tools = await client.list_tools()
print(f"Available tools: {[tool.name for tool in tools]}")
response = await client.call_tool(
name="Get Pet", # overridden name
arguments={"petId": "1"}
)
print(f"Response: {response}")
if __name__ == "__main__":
asyncio.run(main())
Cursor IDE:
{
"mcpServers": {
"Petstore": {
"url": "http://localhost:4000/petstore_mcp/mcp",
"headers": {
"x-litellm-api-key": "Bearer sk-<your-litellm-api-key>"
}
}
}
}
OpenAI Responses API:
curl --location 'https://api.openai.com/v1/responses' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ***" \
--data '{
"model": "gpt-5.6-terra",
"tools": [
{
"type": "mcp",
"server_label": "petstore",
"server_url": "http://localhost:4000/petstore_mcp/mcp",
"require_approval": "never",
"headers": {
"x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY"
}
}
],
"input": "Find all available pets",
"tool_choice": "required"
}'