# 외부 MCP 연결 등록 (/docs/api/agents/createMcpConnection)
**POST /v1/accounts/{accountId}/mcp-connections**
```json
{
  "operationId": "createMcpConnection",
  "summary": "외부 MCP 연결 등록",
  "description": "에이전트가 사용할 원격 MCP 서버를 등록합니다. url 은 공개 인터넷에서 접근 가능한 https 주소여야 하며(포트는 443만), 자격증명은 query 가 아닌 인증 설정으로 전달합니다.\n\n등록 직후 상태는 untested 입니다. 에이전트에 연결하려면 먼저 연결 테스트를 통과해야 합니다.",
  "tags": [
    "Agents"
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "name",
            "url"
          ],
          "properties": {
            "name": {
              "type": "string",
              "maxLength": 80,
              "example": "예약 CRM"
            },
            "url": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048,
              "description": "https 전용. 포트는 생략하거나 443. 사설/내부 주소는 등록할 수 없습니다.",
              "example": "https://crm.example.com/mcp"
            },
            "transport": {
              "type": "string",
              "enum": [
                "streamable_http",
                "sse"
              ],
              "default": "streamable_http",
              "description": "sse 는 호환용 legacy 입니다.",
              "example": "streamable_http"
            },
            "authType": {
              "type": "string",
              "enum": [
                "none",
                "bearer",
                "api_key"
              ],
              "default": "none",
              "example": "bearer"
            },
            "headerName": {
              "type": "string",
              "maxLength": 64,
              "description": "authType=api_key 일 때만 사용합니다. Authorization·Host·Cookie 등 예약 헤더는 사용할 수 없습니다(Bearer 가 필요하면 authType=bearer 를 쓰세요).",
              "example": "X-Api-Key"
            },
            "secret": {
              "type": "string",
              "maxLength": 8192,
              "description": "authType 이 none 이 아닐 때 필수. 저장 시 암호화되며 **다시 조회할 수 없습니다**.",
              "example": "sk-live-0123456789"
            }
          }
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "등록 성공(연결 테스트 전이므로 status=untested)",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "description": "에이전트가 사용할 외부 MCP 서버 연결. 자격증명은 저장 시 암호화되며 **어떤 응답에도 포함되지 않습니다** — 설정 여부는 hasSecret 으로만 확인합니다.",
            "required": [
              "connectionId",
              "name",
              "url",
              "host",
              "transport",
              "authType",
              "authHeaderName",
              "hasSecret",
              "enabled",
              "status",
              "toolCount",
              "tools",
              "lastTestedAt",
              "lastErrorCode",
              "agentCount",
              "dateCreated",
              "dateUpdated"
            ],
            "properties": {
              "connectionId": {
                "type": "string",
                "description": "연결 ID",
                "example": "clx0mcp0000000000000001"
              },
              "name": {
                "type": "string",
                "example": "예약 CRM"
              },
              "url": {
                "type": "string",
                "format": "uri",
                "example": "https://crm.example.com/mcp"
              },
              "host": {
                "type": "string",
                "description": "url 의 호스트. 목록 화면 표시용.",
                "example": "crm.example.com"
              },
              "transport": {
                "type": "string",
                "enum": [
                  "streamable_http",
                  "sse"
                ],
                "example": "streamable_http"
              },
              "authType": {
                "type": "string",
                "enum": [
                  "none",
                  "bearer",
                  "api_key"
                ],
                "example": "bearer"
              },
              "authHeaderName": {
                "type": "string",
                "nullable": true,
                "description": "authType=api_key 일 때의 헤더 이름. bearer 는 Authorization 고정이라 null.",
                "example": null
              },
              "hasSecret": {
                "type": "boolean",
                "description": "자격증명 설정 여부. 값 자체는 조회할 수 없습니다.",
                "example": true
              },
              "enabled": {
                "type": "boolean",
                "example": true
              },
              "status": {
                "type": "string",
                "description": "untested 는 연결 테스트를 통과하지 않은 상태입니다. 에이전트에 붙이려면 tested 여야 하며, 통화 중에도 tested 인 연결만 사용됩니다. credential_error 는 마지막 테스트가 인증 실패한 경우입니다.",
                "enum": [
                  "untested",
                  "tested",
                  "credential_error"
                ],
                "example": "tested"
              },
              "toolCount": {
                "type": "integer",
                "minimum": 0,
                "example": 3
              },
              "tools": {
                "type": "array",
                "description": "마지막 연결 테스트에서 발견된 도구 목록. 표시·선택용 스냅샷입니다.",
                "items": {
                  "type": "object",
                  "description": "MCP 서버가 노출한 도구 하나. name/description/inputSchema 는 **연결한 서버가 제공한 값**이며 모델 입력에 그대로 포함됩니다(신뢰할 수 있는 서버만 연결하세요).",
                  "required": [
                    "name",
                    "description",
                    "inputSchema"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "example": "get_reservation"
                    },
                    "description": {
                      "type": "string",
                      "nullable": true,
                      "example": "예약 번호로 예약 정보를 조회합니다."
                    },
                    "inputSchema": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "도구 인자의 JSON Schema. 서버가 준 원본입니다."
                    }
                  }
                }
              },
              "lastTestedAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "lastErrorCode": {
                "type": "string",
                "nullable": true,
                "description": "마지막 연결 실패 사유.",
                "enum": [
                  "URL_BLOCKED",
                  "AUTH_FAILED",
                  "TIMEOUT",
                  "TLS_ERROR",
                  "PROTOCOL_ERROR",
                  "TOO_MANY_TOOLS"
                ],
                "example": null
              },
              "agentCount": {
                "type": "integer",
                "minimum": 0,
                "description": "이 연결을 사용하는 에이전트 수.",
                "example": 1
              },
              "dateCreated": {
                "type": "string",
                "format": "date-time"
              },
              "dateUpdated": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "url/인증 입력값 검증 실패",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "description": "사람이 읽을 수 있는 에러 메시지"
              },
              "code": {
                "type": "string",
                "description": "기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서\n바뀔 수 있지만 코드는 계약입니다.\n\n같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는\n  - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서\n    해제해야 발신됩니다.\n  - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다.\n\n전화번호 정규화 관련:\n  - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님)\n  - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)",
                "example": "recipient_blocked"
              },
              "ip": {
                "type": "string",
                "description": "`overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다.\n해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔\n「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.",
                "example": "52.79.213.41"
              },
              "processedAt": {
                "type": "string",
                "format": "date-time",
                "description": "문자 발송 요청을 거부한 서버가 오류 응답을 생성한 시각(UTC). 콘솔은 이 값을 한국 시각으로 표시합니다. 인증·요청 형식 검증 등 발송 서비스 진입 전 오류에는 없을 수 있습니다.",
                "example": "2026-10-21T04:10:34.303Z"
              },
              "blockers": {
                "type": "array",
                "description": "`certificate_not_eligible` 일 때만 옵니다. 발급할 수 없는 번호와 그 이유입니다.",
                "items": {
                  "type": "object",
                  "description": "번호 하나가 발급되지 않는(또는 안내가 필요한) 이유.\n\n| code | 뜻 | 발급 |\n|---|---|---|\n| `number_not_owned` | 이 계정이 보유한 번호가 아님(반납·재배정 포함) | 막힘 |\n| `number_not_carrier_issued` | 한국AI통신이 개통한 070·대표번호가 아님(모바일 게이트웨이, 타사 번호) | 막힘 |\n| `business_info_incomplete` | 사업자 계정인데(관리번호 제외) 상호·사업자등록번호·대표자명·사업장 주소 중 빈 칸 | 막힘 |\n| `identity_unverified` | 개인 계정 번호 또는 관리번호인데 그 번호에 본인확인 정보가 없음 | 막힘 |\n| `account_restricted` | 이용 제한 계정 | 막힘 |\n| `business_recheck_pending` | 사업자 계정인데 사업자 정보 재확인이 진행 중(통과 전) — 관리번호를 뺀 계정의 모든 번호 | 막힘 |\n| `holder_is_personal` | 개인 명의로 찍힌다는 안내 | 가능 |",
                  "properties": {
                    "number": {
                      "type": "string",
                      "description": "422 응답의 blockers 에만 있다",
                      "example": "07012340002"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "number_not_owned",
                        "number_not_carrier_issued",
                        "business_info_incomplete",
                        "identity_unverified",
                        "account_restricted",
                        "business_recheck_pending",
                        "holder_is_personal"
                      ],
                      "example": "number_not_carrier_issued"
                    },
                    "message": {
                      "type": "string",
                      "example": "이용증명원은 한국AI통신이 개통한 070·대표번호만 발급됩니다."
                    }
                  },
                  "required": [
                    "code",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "401": {
      "description": "인증 실패",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "error": {
                "type": "string",
                "description": "사람이 읽을 수 있는 에러 메시지"
              },
              "code": {
                "type": "string",
                "description": "기계 판독용 에러 코드. **분기는 이 값으로 하세요** — `error` 문구는 안내를 다듬으면서\n바뀔 수 있지만 코드는 계약입니다.\n\n같은 상태 코드라도 대응이 갈리는 경우가 있습니다. 예를 들어 발신의 `422` 는\n  - `recipient_blocked`: 수신거부 명단에 있는 번호입니다. 재시도하지 말고 명단에서\n    해제해야 발신됩니다.\n  - `quota_exceeded`: 플랜 한도를 넘었습니다. 다음 주기에 다시 발신됩니다.\n\n전화번호 정규화 관련:\n  - `INVALID_PHONE_NUMBER`: 전화번호 포맷 오류 (한국 번호 형식 아님)\n  - `UNSUPPORTED_COUNTRY`: 국제 발신 미지원 (+82 외 국가코드)",
                "example": "recipient_blocked"
              },
              "ip": {
                "type": "string",
                "description": "`overseas_ip_blocked` 일 때만 옵니다. 국외 IP 라고 판정한 요청 IP 입니다.\n해외 리전 클라우드 서버에서 호출한다면 이 값이 그 서버의 공인 IP 이며, 콘솔\n「국외 접속 예외」에서 예외를 신청할 때 이 고정 IP 를 적습니다.",
                "example": "52.79.213.41"
              },
              "processedAt": {
                "type": "string",
                "format": "date-time",
                "description": "문자 발송 요청을 거부한 서버가 오류 응답을 생성한 시각(UTC). 콘솔은 이 값을 한국 시각으로 표시합니다. 인증·요청 형식 검증 등 발송 서비스 진입 전 오류에는 없을 수 있습니다.",
                "example": "2026-10-21T04:10:34.303Z"
              },
              "blockers": {
                "type": "array",
                "description": "`certificate_not_eligible` 일 때만 옵니다. 발급할 수 없는 번호와 그 이유입니다.",
                "items": {
                  "type": "object",
                  "description": "번호 하나가 발급되지 않는(또는 안내가 필요한) 이유.\n\n| code | 뜻 | 발급 |\n|---|---|---|\n| `number_not_owned` | 이 계정이 보유한 번호가 아님(반납·재배정 포함) | 막힘 |\n| `number_not_carrier_issued` | 한국AI통신이 개통한 070·대표번호가 아님(모바일 게이트웨이, 타사 번호) | 막힘 |\n| `business_info_incomplete` | 사업자 계정인데(관리번호 제외) 상호·사업자등록번호·대표자명·사업장 주소 중 빈 칸 | 막힘 |\n| `identity_unverified` | 개인 계정 번호 또는 관리번호인데 그 번호에 본인확인 정보가 없음 | 막힘 |\n| `account_restricted` | 이용 제한 계정 | 막힘 |\n| `business_recheck_pending` | 사업자 계정인데 사업자 정보 재확인이 진행 중(통과 전) — 관리번호를 뺀 계정의 모든 번호 | 막힘 |\n| `holder_is_personal` | 개인 명의로 찍힌다는 안내 | 가능 |",
                  "properties": {
                    "number": {
                      "type": "string",
                      "description": "422 응답의 blockers 에만 있다",
                      "example": "07012340002"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "number_not_owned",
                        "number_not_carrier_issued",
                        "business_info_incomplete",
                        "identity_unverified",
                        "account_restricted",
                        "business_recheck_pending",
                        "holder_is_personal"
                      ],
                      "example": "number_not_carrier_issued"
                    },
                    "message": {
                      "type": "string",
                      "example": "이용증명원은 한국AI통신이 개통한 070·대표번호만 발급됩니다."
                    }
                  },
                  "required": [
                    "code",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}
```