For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
기본 탐색 메뉴
Theme
개요 ChatGPT 및 Codex 사용자 문서 활용 사례

인증

플러그인 MCP 서버의 인증 방식과 구현 방법을 알아봅니다.

사용자 인증

많은 플러그인 MCP 서버는 익명 사용자가 읽기 전용으로 이용할 수 있습니다. 다만 고객별 데이터나 쓰기 작업을 제공하는 서버는 사용자를 인증해야 합니다.

게시된 플러그인은 ChatGPT와 Codex에서 실행할 수 있습니다. 두 제품 모두 같은 MCP 인가 규약을 따릅니다. 이 가이드에서는 제품에 따라 콜백, 메타데이터 문서 또는 계정 연결 인터페이스가 다른 경우 ChatGPT 클라이언트의 동작을 별도로 설명합니다.

기존 서버 측 애플리케이션에 연결하거나 사용자 간에 데이터를 공유해야 한다면 자체 인가 서버를 연동할 수 있습니다.

OAuth 2.1로 맞춤 인증 구현하기

사용자 인증을 요구하는 MCP 서버는 MCP 인가 사양을 따르는 OAuth 2.1 플로우를 구현해야 합니다.

구성 요소

  • 리소스 서버: 도구를 제공하고 요청마다 액세스 토큰을 검증하는 MCP 서버입니다.
  • 인가 서버: 토큰을 발급하고 검색용 메타데이터를 게시하는 ID 공급자 또는 자체 구현 서버입니다.
  • 클라이언트: 사용자를 대신해 동작하는 ChatGPT, Codex 등의 OpenAI 호스트입니다. 지원되는 클라이언트는 클라이언트 ID 메타데이터 문서(CIMD), 동적 클라이언트 등록(DCR), 사전 정의된 OAuth 클라이언트, PKCE를 사용합니다.

MCP 인가 사양의 요구 사항

  • MCP 서버에서 보호 리소스 메타데이터 제공하기
  • 인가 서버에서 OAuth 메타데이터 게시하기
  • OAuth 플로우 전반에서 resource 매개변수 값 그대로 전달하기
  • OpenAI 호스트가 OAuth 클라이언트를 식별하거나 등록하는 방식 선택하기(CIMD, DCR 또는 사전 정의된 OAuth 클라이언트)
  • 인가 서버가 허용하는 토큰 엔드포인트 인증 방식 게시하기

각 요구 사항을 구현하는 방법은 다음과 같습니다.

MCP 서버에서 보호 리소스 메타데이터 제공하기

  • ChatGPT가 메타데이터를 가져올 위치를 알 수 있도록 GET https://your-mcp.example.com/.well-known/oauth-protected-resource와 같은 HTTPS 엔드포인트를 제공하세요. 또는 WWW-Authenticate 헤더에 같은 URL을 넣어 401 Unauthorized 응답으로 알리세요.
  • 이 엔드포인트는 리소스 서버와 사용 가능한 인가 서버를 설명하는 JSON 문서를 반환합니다.
{
  "resource": "https://your-mcp.example.com",
  "authorization_servers": ["https://auth.yourcompany.com"],
  "scopes_supported": ["files:read", "files:write"],
  "resource_documentation": "https://yourcompany.com/docs/mcp"
}
  • 주요 필드는 다음과 같이 설정하세요.
    • resource: MCP 서버의 정식 HTTPS 식별자입니다. ChatGPT는 OAuth 플로우에서 이 값을 그대로 resource 쿼리 매개변수로 전송합니다.
    • authorization_servers: ID 공급자를 가리키는 발급자 기본 URL을 하나 이상 지정합니다. ChatGPT는 각 URL에서 OAuth 메타데이터를 찾습니다.
    • scopes_supported: ChatGPT가 사용자에게 요청할 권한을 설명하는 데 사용하는 선택적 범위 목록입니다.
    • RFC 9728의 선택적 필드인 resource_documentation, resource_policy_uri, resource_tos_uri 등을 포함하면 클라이언트와 관리자가 구성을 이해하기 쉽습니다.

인증되지 않은 요청을 차단할 때는 다음과 같은 인증 챌린지를 반환하세요.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://your-mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

이 헤더 하나로 ChatGPT는 처음 접하는 메타데이터 URL도 찾을 수 있습니다.

인가 서버에서 OAuth 메타데이터 게시하기

  • ChatGPT가 구성을 읽을 수 있도록 ID 공급자는 다음 표준 위치 중 하나에서 검색 문서를 제공해야 합니다.
    • OAuth 2.0 메타데이터: https://auth.yourcompany.com/.well-known/oauth-authorization-server
    • OpenID Connect 메타데이터: https://auth.yourcompany.com/.well-known/openid-configuration
  • 각 문서를 통해 OpenAI 호스트는 사용자를 어디로 보낼지, 코드를 어떻게 교환할지, 클라이언트로서 자신을 어떻게 식별할지 알 수 있습니다. 일반적인 응답은 다음과 같습니다.
{
  "issuer": "https://auth.yourcompany.com",
  "authorization_response_iss_parameter_supported": true,
  "authorization_endpoint": "https://auth.yourcompany.com/oauth2/v1/authorize",
  "token_endpoint": "https://auth.yourcompany.com/oauth2/v1/token",
  "client_id_metadata_document_supported": true,
  "token_endpoint_auth_methods_supported": ["none", "private_key_jwt"],
  "registration_endpoint": "https://auth.yourcompany.com/oauth2/v1/register",
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": ["files:read", "files:write"]
}
  • 다음 필드는 정확히 설정해야 합니다.
    • issuer: 인가 서버의 정식 식별자입니다. 보호 리소스 메타데이터의 authorization_servers 목록에도 정확히 같은 값을 사용하세요.
    • authorization_response_iss_parameter_supported: 이 필드를 true로 설정하려면 인가 서버가 오류 응답을 포함한 모든 인가 응답에 iss 매개변수를 반환해야 합니다.
    • authorization_endpoint, token_endpoint: ChatGPT가 PKCE를 사용하는 OAuth 인가 코드 플로우를 처음부터 끝까지 실행하는 데 필요한 URL입니다.
    • client_id_metadata_document_supported: ChatGPT가 클라이언트 등록에 CIMD를 사용하도록 하려면 true로 설정하세요. ChatGPT는 CIMD를 사용할 수 있으면 우선 사용하지만, CIMD와 DCR을 모두 지원하면 플러그인 개발자가 DCR을 선택할 수 있습니다.
    • token_endpoint_auth_methods_supported: 인가 서버가 허용하는 토큰 엔드포인트 인증 방식을 포함하세요. 이 필드는 CIMD, DCR, 사전 정의된 OAuth 클라이언트 모두에 적용됩니다. CIMD에서 ChatGPT는 공개 클라이언트의 토큰 교환에 none을, 서명된 클라이언트 어설션을 사용하는 토큰 교환에 private_key_jwt를 지원합니다. 다른 OAuth 클라이언트는 일반적으로 none, client_secret_post 또는 client_secret_basic을 사용합니다.
    • registration_endpoint: 동적 클라이언트 등록(DCR)을 지원할 때 포함하세요. DCR을 사용하면 ChatGPT가 MCP 서버 연결 전용 client_id를 생성하고 재사용할 수 있습니다.
    • code_challenge_methods_supported: 반드시 S256을 포함해야 합니다. 인가 서버 메타데이터에서 이 필드를 생략하거나 S256 지원을 명시하지 않으면 해당 MCP 서버는 지원되지 않습니다. 이는 MCP 인가 사양의 요구 사항입니다.
    • 선택적 필드는 RFC 8414와 OpenID Discovery를 따릅니다. 관리자가 정책을 구성하는 데 도움이 되는 필드를 포함하세요.

OIDC 범위

  • 공급자가 OIDC 범위(예: openid, email, profile)를 scopes_supported에 명시하면 ChatGPT는 OAuth 플로우에서 기본적으로 해당 범위를 요청합니다. 이 필드는 공급자의 .well-known/oauth-authorization-server 또는 .well-known/openid-configuration 문서에 있습니다.
  • 일부 ID 공급자는 문서에 명시한 OIDC 범위를 기본으로 활성화하지 않습니다. 공급자 설정을 확인하세요. OAuth 클라이언트가 CIMD를 사용하든, 수동으로 또는 DCR로 생성되었든, 문서에 명시된 모든 범위가 해당 클라이언트에서 활성화되어 있어야 합니다.

워크스페이스 도메인 제한 지원하기

ChatGPT Enterprise 워크스페이스에서는 이메일 도메인 소유권을 확인할 수 있습니다. OAuth로 연결된 플러그인이 사용자의 확인된 이메일 주소를 제공하면 ChatGPT는 해당 도메인을 기준으로 사용자가 회사 계정으로 개인 워크스페이스나 조직 외부의 다른 워크스페이스에 플러그인을 연결하는 것을 막을 수 있습니다.

이 보호 기능을 지원하려면 인가 서버를 다음과 같이 구성하세요.

  • OpenID Connect 검색 메타데이터를 게시하세요.
  • openid와 email 범위를 메타데이터에 명시하고 활성화하세요.
  • 사용자의 email 클레임과 email_verified: true를 반환하는 UserInfo 엔드포인트를 메타데이터에 명시하세요.

OAuth 플로우에서 ID 토큰에 이 클레임들을 담아 반환할 수도 있습니다. 다만 워크스페이스 도메인 제한에는 UserInfo 엔드포인트가 필수입니다.

Enterprise 워크스페이스에서도 자체 도메인 소유권을 확인해야 합니다. 인가 서버는 사용자 신원 정보를 제공합니다. ChatGPT는 이 정보를 워크스페이스에 설정된 확인된 도메인과 대조합니다. 워크스페이스의 도메인 소유권 확인은 인가 서버가 담당하지 않습니다.

재인가 시 로그인 컨텍스트 유지하기

ChatGPT가 추가 OAuth 범위를 요청하는 등의 이유로 기존 연결을 재인가할 때는 이전 OIDC ID 토큰을 표준 id_token_hint 매개변수로 인가 요청에 포함할 수 있습니다. 사용자가 처음부터 다시 로그인하지 않고도 추가 범위를 허용할 수 있도록 최초 OAuth 플로우에서 ID 토큰을 발급하고 인가 시 id_token_hint를 반영하도록 인가 서버를 구성하세요.

이 최적화는 선택 사항입니다. ID 토큰이 없거나 인가 서버가 힌트를 사용하지 않아도 재인가는 가능합니다.

발급자 식별로 콜백 보호하기

OpenAI 호스트는 RFC 9207의 발급자 식별 방식을 사용해 인가 서버 혼동 공격으로부터 OAuth 콜백을 보호합니다. ChatGPT와 Codex가 조건에 맞는 OAuth 클라이언트를 생성할 때 고정 리디렉션 URI를 사용할 수 있도록 다음과 같이 설정하세요.

  • authorization_response_iss_parameter_supported: true를 인가 서버 메타데이터에 설정하세요.
  • 메타데이터의 issuer 필드와 보호 리소스 메타데이터의 authorization_servers 목록에 정확히 같은 발급자 식별자를 사용하세요.
  • 성공 및 오류를 포함한 모든 인가 응답에 iss를 반환하세요. 이 값은 메타데이터의 issuer와 정확히 일치해야 합니다. 클라이언트는 문자열을 그대로 비교하며 끝의 슬래시, 경로, 포트, 대소문자를 정규화하지 않습니다.

ChatGPT와 Codex는 사용자를 리디렉션하기 전에 선택한 메타데이터의 issuer를 기록하고, 인가 코드를 교환하기 전에 응답의 iss를 확인합니다. 서버가 발급자 식별을 지원한다고 명시했지만 iss를 생략하거나 다른 값을 반환하면 ChatGPT와 Codex는 응답을 거부합니다. 이러한 요구 사항은 MCP 인가 응답 검증 규칙을 따릅니다.

리디렉션 URL

MCP 서버 관리 페이지에 표시된 프로덕션 리디렉션 URI를 그대로 복사해 인가 서버의 허용 목록에 추가하세요.

  • 인가 서버가 위의 발급자 식별 요구 사항을 충족하지 않으면 ChatGPT는 콜백 ID별 리디렉션 URI인 https://chatgpt.com/connector/oauth/{callback_id}를 사용합니다.
  • 인가 서버가 해당 요구 사항을 충족하면 ChatGPT는 고정 리디렉션 URI인 https://chatgpt.com/connector_platform_oauth_redirect를 사용합니다.

ChatGPT가 콜백 ID별 리디렉션을 도입하기 전에 게시된 MCP 서버도 계속 고정 리디렉션 URI를 사용합니다.

OAuth 플로우 전반에서 resource 매개변수 값 그대로 전달하기

  • ChatGPT는 인가 요청과 토큰 요청 모두에 resource=https%3A%2F%2Fyour-mcp.example.com을 추가합니다. 이 값은 토큰과 앞서 설명한 보호 리소스 메타데이터를 연결합니다.
  • 인가 서버가 해당 값을 액세스 토큰에 복사하도록 구성하세요. 일반적으로 aud 클레임에 넣습니다. 그러면 MCP 서버는 토큰이 자신만을 대상으로 발급되었는지 검증할 수 있습니다.
  • 토큰에 예상한 대상이나 범위가 없으면 토큰을 거부하고 WWW-Authenticate 챌린지를 반환해 ChatGPT가 올바른 매개변수로 다시 인가를 요청하도록 하세요.

인가 코드 플로우 지원하기

  • MCP 클라이언트인 ChatGPT는 S256 코드 챌린지로 PKCE를 적용한 인가 코드 플로우를 실행합니다. 따라서 공격자가 인가 코드를 가로채도 재사용할 수 없습니다.
  • 클라이언트가 메타데이터에서 PKCE 지원 여부를 확인할 수 있도록 인가 서버는 code_challenge_methods_supported에 S256을 포함해 게시해야 합니다.

OAuth 플로우

위에서 설명한 MCP 인가 사양을 구현하면 OAuth 플로우는 다음과 같이 진행됩니다.

  1. ChatGPT가 MCP 서버에 보호 리소스 메타데이터를 요청합니다.

  1. ChatGPT가 자신을 OAuth 클라이언트로 식별합니다. MCP 서버가 CIMD를 사용하면 ChatGPT는 동적 클라이언트 등록을 건너뛰고 CIMD 문서 URL을 client_id로 전송합니다. 위의 발급자 식별 요구 사항을 충족하는 인가 서버에는 고정 URL인 https://chatgpt.com/oauth/client.json을 사용하고, 그 밖의 서버에는 콜백 ID별 URL인 https://chatgpt.com/oauth/{callback_id}/client.json을 사용합니다. MCP 서버 관리 페이지에는 해당 연결의 콜백 모드에 맞는 정확한 클라이언트 메타데이터 문서와 리디렉션 URI가 표시됩니다. MCP 서버가 DCR을 사용하면 ChatGPT는 해당 MCP 서버 연결에 대해 인가 서버의 registration_endpoint를 한 번 호출해 생성된 client_id를 받고 이후에도 이 연결에 같은 클라이언트를 사용합니다.

CIMD를 사용하면 클라이언트 등록 단계가 없습니다. 다음 화면은 DCR을 사용할 때의 등록 단계입니다.

  1. 사용자가 도구를 처음 호출하면 ChatGPT 클라이언트가 PKCE를 적용한 OAuth 인가 코드 플로우를 시작합니다. 사용자는 인증을 거치고 요청된 범위에 동의합니다.

  1. ChatGPT는 인가 코드를 액세스 토큰으로 교환한 뒤 이후 MCP 요청에 토큰을 첨부합니다(Authorization: Bearer <token>).

  1. 서버는 요청마다 토큰의 발급자, 대상, 만료 시각, 범위를 검증한 뒤 도구를 실행합니다.

클라이언트 등록

인가 서버가 클라이언트 ID 메타데이터 문서(CIMD)를 지원하고 플러그인 개발자가 이 방식을 선택했다면 클라이언트 등록에 CIMD를 우선 사용하세요. CIMD를 사용할 때 ChatGPT는 HTTPS 메타데이터 문서 URL을 client_id로 사용합니다. 인가 서버는 해당 문서를 가져와 게시된 클라이언트 메타데이터와 리디렉션 리소스 식별자를 검증하고, 이 URL을 ChatGPT의 고정 클라이언트 식별자로 사용합니다.

CIMD를 지원한다면 인가 서버 메타데이터에 client_id_metadata_document_supported: true를 설정하세요. 그러면 ChatGPT는 CIMD를 선택한 MCP 서버들에 하나의 고정 클라이언트 식별자를 사용합니다. 인가 서버는 이 식별자를 리디렉션 URI 허용 목록, 요청 한도 등의 정책에 활용할 수 있습니다.

ChatGPT는 MCP SEP-3149에서 제안한 CIMD 변경 사항을 도입하고 있습니다. 프로덕션 CIMD 문서에서는 token_endpoint_auth_methods_supported를 ChatGPT가 사용할 수 있는 방식의 배열로 게시합니다. 배열 순서는 우선순위를 나타내지 않습니다. 전환 기간에는 기존 단수형 필드인 token_endpoint_auth_method도 게시해 선호하는 방식을 표시합니다.

{
  "token_endpoint_auth_methods_supported": ["none", "private_key_jwt"],
  "token_endpoint_auth_method": "private_key_jwt"
}

복수형 필드가 나타내는 내용은 문서에 따라 다릅니다. 인가 서버 메타데이터에는 토큰 엔드포인트가 허용하는 방식이, ChatGPT의 CIMD 문서에는 ChatGPT가 사용할 수 있는 방식이 나열됩니다. ChatGPT는 양쪽에 공통으로 있는 방식 중 하나를 선택합니다. 기존 단수형 필드의 선호 방식이 양쪽 목록에 있으면 ChatGPT는 그 방식을 사용합니다. 단수형 필드에 지정된 방식을 여전히 필수로 취급하는 인가 서버와 호환되도록 하기 위해서입니다. 그렇지 않으면 다른 공통 방식을 사용할 수 있습니다.

CIMD의 복수형 필드를 읽는 인가 서버는 자체 보안 정책에서 해당 클라이언트에 대해 금지한 방식을 제외하고 양쪽 목록에 공통으로 있는 모든 방식을 허용해야 합니다. 두 목록의 교집합에 없는 방식은 반드시 거부해야 합니다. client_id URL은 고정되며, 쿼리 매개변수로 인증 방식별 문서를 선택하지 않습니다.

지원되는 방식은 다음과 같습니다.

  • none: 토큰 엔드포인트가 클라이언트 인증 없이 PKCE 기반 인가 코드 교환을 지원하면 이 공개 클라이언트 플로우를 사용하세요. ChatGPT는 클라이언트별 시크릿을 저장하지 않습니다.
  • private_key_jwt: 토큰 엔드포인트가 클라이언트 인증을 요구하면 서명된 클라이언트 어설션을 사용하는 이 플로우를 사용하세요. ChatGPT는 CIMD 메타데이터에 공개 JWKS URL을 게시합니다. JWKS는 메타데이터와 같은 출처의 /oauth/jwks.json에서 제공됩니다. ChatGPT는 관리형 개인 키와 kid를 사용해 서버 측에서 토큰 요청에 서명하고, 인가 서버는 공개 JWKS로 어설션을 검증합니다.

DCR도 계속 지원됩니다. registration_endpoint를 포함하면 플러그인 개발자가 DCR을 선택했거나 CIMD를 사용할 수 없을 때 ChatGPT가 클라이언트를 동적으로 등록할 수 있습니다. ChatGPT는 MCP 서버 연결마다 DCR을 한 번 실행한 뒤 등록된 OAuth 클라이언트를 보관해 해당 연결에 재사용합니다. DCR에서는 개별 연결이 많으면 등록되는 클라이언트도 많아질 수 있으므로 대규모 환경에서는 일반적으로 CIMD가 관리하기 더 쉽습니다.

MCP 서버 연결을 사용하는 동안 등록된 OAuth 클라이언트를 유효하게 유지하세요. 클라이언트 시크릿이 있다면 해당 시크릿도 유효하게 유지하세요. 인가 서버가 둘 중 하나를 만료시키거나 삭제 또는 교체하면 사용자와 검토자가 연결할 때 invalid_client 오류가 발생할 수 있습니다. 액세스 토큰과 갱신 토큰은 평소처럼 만료되거나 교체되어도 됩니다.

클라이언트 식별

MCP 서버에서 요청이 실제로 ChatGPT에서 왔는지 어떻게 확인할 수 있을까요? ChatGPT는 MCP 서버에 연결할 때 OpenAI가 관리하는 클라이언트 인증서를 제시하므로 전송 계층에서 mTLS로 클라이언트를 검증할 수 있습니다. ChatGPT가 공개한 송신 IP 범위를 허용 목록에 추가할 수도 있습니다. ChatGPT는 클라이언트 자격 증명, 서비스 계정, JWT 베어러 어설션과 같은 머신 간 OAuth 권한 부여 방식을 지원하지 않습니다. 또한 사용자 지정 API 키나 고객이 제공한 mTLS 인증서를 제시할 수 없습니다.

CIMD는 ChatGPT의 신원을 명시한 고정 문서를 HTTPS로 인가 서버에 제공해 클라이언트 식별을 강화합니다. private_key_jwt를 사용하는 경우 CIMD 메타데이터에 게시된 공개 JWKS로 ChatGPT의 토큰 엔드포인트 클라이언트 어설션을 검증하세요.

상호 TLS(mTLS)

이제 ChatGPT는 MCP 서버와 TLS 연결을 설정할 때 OpenAI가 관리하는 클라이언트 인증서를 제시합니다. 애플리케이션이 클라이언트 인증서를 검증한다면 아래 OpenAI 인증서 체인을 신뢰하도록 구성하세요.

MCP 서버와 TLS 연결을 설정할 때 다음과 같이 클라이언트 인증서를 검증하세요.

  • 리프 인증서가 있고 인증서 체인이 OpenAI Connectors mTLS 중간 CA로 이어지는지 확인하세요.
  • 리프 인증서가 클라이언트 인증 용도로 유효한지 확인하세요.
  • 리프 인증서의 SAN dnsName 값이 mtls.prod.connectors.openai.com인지 확인하세요.
  • 리프 인증서 지문을 고정하지 마세요. OpenAI는 게시된 CA 체인을 유지하면서 리프 인증서를 교체할 수 있습니다.

mTLS로 ChatGPT를 MCP 클라이언트로 인증하세요. 최종 사용자 인증과 도구 접근 권한 부여에는 계속 OAuth 2.1을 사용하세요.

ID 공급자 선택하기

대부분의 OAuth 2.1 ID 공급자는 검색 문서를 제공하고, none 또는 private_key_jwt를 사용하는 CIMD를 지원하고, 필요할 때 DCR을 지원하며, 발급하는 토큰에 resource 매개변수 값을 그대로 포함하면 MCP 인가 요구 사항을 충족할 수 있습니다. 클라이언트 등록에 CIMD를 지원하는 공급자를 우선 선택하세요.

인증을 처음부터 직접 구현하기보다는 이미 검증된 ID 공급자를 사용하는 것을 강력히 권장합니다.

다음은 널리 사용되는 ID 공급자의 설정 가이드입니다.

Auth0

Auth0는 메타데이터 검색, CIMD 등록, API 보안, 자체 및 타사 도구 호출을 위한 토큰 교환을 제공해 MCP 클라이언트가 MCP 서버에 안전하게 연결할 수 있도록 합니다.

호스팅형 공급자 예시(Stytch)

토큰 검증 구현하기

OAuth 플로우가 완료되면 ChatGPT는 발급받은 액세스 토큰을 이후 MCP 요청에 직접 첨부합니다(Authorization: Bearer …). MCP 서버는 요청이 도착하면 토큰을 신뢰하기 전에 리소스 서버에서 필요한 검사를 모두 수행해야 합니다. 서명을 검증하고, 발급자와 대상이 예상한 값과 일치하는지 확인하고, 만료 여부와 재전송 공격 가능성을 점검하고, 범위에 따른 권한을 적용하세요. 이러한 검증은 MCP 서버 운영자가 책임집니다.

구체적인 절차는 다음과 같습니다.

  • 인가 서버가 게시한 서명 키를 가져와(일반적으로 JWKS 사용) 토큰의 서명과 iss를 검증하세요.
  • 만료되었거나 아직 유효 기간이 시작되지 않은 토큰은 거부하세요(exp/nbf).
  • 토큰이 해당 서버를 대상으로 발급되었고(aud 또는 resource 클레임), 필수로 지정한 범위가 포함되어 있는지 확인하세요.
  • 서버별 정책 검사를 수행한 뒤 확인된 신원을 요청 컨텍스트에 추가하거나 401 응답과 WWW-Authenticate 챌린지를 반환하세요.

검증에 실패하면 401 Unauthorized 응답에 보호 리소스 메타데이터를 가리키는 WWW-Authenticate 헤더를 포함하세요. 그러면 클라이언트가 OAuth 플로우를 다시 실행해야 한다는 것을 알 수 있습니다.

SDK의 토큰 검증 기능

Python과 TypeScript용 MCP SDK에는 토큰 검증을 처음부터 직접 구현하지 않아도 되도록 도와주는 헬퍼가 포함되어 있습니다.

여러 계정 지원하기

다중 계정 기능을 사용하면 개인 계정과 업무 계정처럼 여러 계정을 같은 플러그인에 연결할 수 있습니다. OpenAI는 선택한 연결의 인증된 자격 증명으로 각 도구 호출을 라우팅합니다. 프로필 도구가 없어도 여러 계정을 연결할 수 있습니다. 사용자가 연결을 구분하고 재연결 후에도 같은 프로필임을 알아볼 수 있도록 고정 ID와 식별에 유용한 표시용 메타데이터를 반환하는 프로필 도구를 제공하세요. 이 도구를 호출하려면 인증이 필요해야 합니다.

사용자가 여러 계정을 이용하는 방식

사용자는 플러그인 설정 페이지에서 계정을 추가로 연결할 수 있습니다. 모델은 연결된 모든 계정을 사용할 수 있으며, 도구를 호출할 때 사용자 요청에 맞는 계정을 하나 이상 선택합니다. 각 도구 호출에는 선택한 계정의 자격 증명과 권한이 적용됩니다.

계정 식별 개선하기

OpenAI가 연결된 프로필을 식별하고 구분하기 쉬운 레이블을 표시할 수 있도록 다음과 같이 구성하세요.

  • 인증을 요구하는 프로필 도구를 제공하세요. 이 도구는 요청의 자격 증명에 해당하는 프로필을 고유하고 일관되게 식별하는 불투명 ID를 반환해야 합니다. 그러면 OpenAI는 재연결 후에도 같은 프로필을 알아보고 다른 프로필과 구분할 수 있습니다. id라는 이름의 필드가 있어도 그 값이 이러한 조건을 충족해야 식별에 사용할 수 있습니다.
  • OpenAI가 인증된 프로필 정보를 가져올 때 어떤 도구를 호출해야 하는지 찾을 수 있도록 MCP 메타데이터에 프로필 도구를 지정하세요.

프로필 정보가 필요하면 OpenAI는 런타임에 지정된 도구를 찾아 해당 연결의 자격 증명으로 호출하고, 응답을 검증한 뒤 프로필 데이터를 사용합니다. 프로필 도구가 없어도 계정을 연결할 수 있지만 계정 레이블 표시, 계정 식별, 중복 감지의 정확도가 떨어질 수 있습니다. 프로필 도구를 선언했다면 유효한 식별 정보를 반환하세요. 응답이 유효하지 않으면 계정을 연결하지 못할 수 있습니다.

고정 프로필 식별자 정의하기

프로필은 요청에 사용된 자격 증명으로 인증된 주체를 식별합니다. 어떤 프로필을 독립적으로 연결할 수 있는지는 서비스에서 정합니다. 이 규약은 서비스의 조직 구조나 인가 모델을 규정하지 않습니다.

앱 내에서 고유한 불투명 프로필 ID를 반환하세요. 같은 프로필은 토큰 갱신이나 재연결 후에도 같은 ID를 유지해야 하고, 서로 다른 프로필에는 서로 다른 ID가 있어야 합니다. OpenAI는 ID의 내용을 해석하지 않고 값만 비교합니다.

프로필 전체를 식별할 수 있는 변경 불가능한 불투명 공급자 ID가 이미 있다면 사용하세요. 그렇지 않으면 불투명 ID를 한 번만 할당하고 해당 프로필과의 매핑을 영구 저장해 이후 요청에서도 같은 ID를 조회하세요. 내부 관계 정보는 서비스 안에서만 관리하세요. 반환하는 ID에 이름, 이메일 주소, 조직 관계를 인코딩하지 마세요.

id는 다음 조건을 충족해야 합니다.

  • 공백 이외의 문자가 하나 이상 있는 문자열이어야 합니다. 숫자형 공급자 ID는 문자열로 직렬화하세요.
  • 같은 프로필은 토큰 갱신, 재연결, 범위 확대 후에도 같은 ID를 유지해야 합니다.
  • 앱을 통해 연결할 수 있는 서로 다른 프로필은 서로 다른 ID를 가져야 합니다.
  • 프로필의 이메일, 이름, 표시 레이블이 바뀌어도 ID는 그대로 유지해야 합니다.
  • 프로필을 삭제한 뒤에도 해당 ID를 다른 프로필에 재할당해서는 안 됩니다.

로그인, 토큰, 세션, 도구 호출마다 새 ID를 생성하지 마세요. 이메일과 수정 가능한 이름은 표시용 메타데이터에 넣으세요. 변경하거나 재할당할 수 있는 이메일 주소는 고정 프로필 ID로 사용할 수 없습니다. Google OIDC에서는 이메일 클레임 대신 고정된 sub를 사용하세요. Google 문서에 따르면 이메일은 바뀔 수 있지만 sub는 바뀌지 않으며 다른 사용자에게 재사용되지 않습니다. Google ID 문서를 참고하세요.

연동을 업데이트할 때 기존 프로필 ID를 유지하세요. 표시 이름이 바뀌거나 새 토큰이 발급되거나 새로 연결되어도 새로운 프로필 식별자가 생겨서는 안 됩니다.

프로필 도구 구현 및 선언하기

빈 인수 객체를 받아 현재 프로필을 반환하는 읽기 전용 도구를 제공하고, 이 도구를 호출할 때는 인증을 요구하세요. 도구 이름은 get_profile, whoami 또는 다른 이름이어도 됩니다. 런타임에는 메타데이터를 보고 프로필 도구를 찾습니다. OpenAI가 응답을 올바르게 사용하려면 응답이 아래의 식별 요구 사항을 충족해야 합니다.

  • 요청에서 검증한 자격 증명으로 신원을 확인하세요.
  • 작업을 읽기 전용으로 구현하고 일반적인 연결 권한으로 사용할 수 있게 하세요.
  • 현재 요청의 자격 증명에 해당하는 프로필을 정확히 하나만 반환하세요.
  • 호출자에게 사용자 ID, 이메일 또는 계정 선택 값을 요구하지 마세요.
  • 인증에 실패하면 임시 ID나 다른 계정의 프로필을 반환하지 말고 적절한 인증 오류를 반환하세요.

프로필 응답은 다음 JSON 스키마를 따라야 합니다.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S",
      "description": "Opaque profile identifier, unique within this app and unchanged across token refresh, reconnection, and display-metadata changes. Never reassigned to another profile."
    },
    "name": {
      "type": "string",
      "description": "Display name for the authenticated profile."
    },
    "email": {
      "type": "string",
      "description": "Email address for display; not used as the profile identity."
    },
    "nickname": {
      "type": "string",
      "description": "A useful label that helps users distinguish connected profiles."
    }
  },
  "required": ["id"],
  "additionalProperties": false
}

응답에는 공백 이외의 문자가 하나 이상 있는 문자열 id가 포함되어야 합니다. 표시용 필드는 선택 사항입니다. 도구 메타데이터는 OpenAI에 프로필 정보를 가져올 위치를 알려주고, 응답은 현재 자격 증명에 해당하는 프로필을 식별합니다.

스키마 검증은 응답이 프로필 처리에 필요한 구조와 필드 타입을 갖추었는지 확인합니다. 서비스는 별도로 ID의 고유성과 일관성을 보장하고 자격 증명에 해당하는 프로필만 반환해야 합니다. 메타데이터를 선언하거나 스키마 검증을 통과하는 것만으로는 이러한 동작을 보장할 수 없습니다.

사용자가 프로필을 구분할 수 있도록 값이 있는 경우 name, email, nickname 중 해당 필드를 포함하세요. 값이 없는 선택적 필드는 생략하고, 값을 임의로 만들거나 관련 없는 개인 데이터를 추가하지 마세요. 사람이 읽고 구분하는 데 도움이 되는 정보는 ID 대신 nickname에 넣으세요.

도구에 _meta["openai/profile"]: true를 설정하고 프로필 응답 스키마를 도구의 outputSchema로 게시하세요. 이 표시는 어떤 도구가 프로필 정보를 제공하는지 OpenAI에 알려줍니다. 표시 자체가 기능을 활성화하거나 사용 자격을 부여하지는 않습니다. 표시가 없거나 값이 false이면 이 방식으로는 해당 도구를 프로필 정보의 출처로 지정하지 않은 것입니다. 문자열, 숫자, null은 유효한 표시 값이 아닙니다.

{
  "name": "get_profile",
  "description": "Return the profile represented by this request's authenticated credentials. The opaque id is unique within this app and remains unchanged across token refresh, reconnection, and display-metadata changes.",
  "inputSchema": {
    "type": "object",
    "properties": {},
    "additionalProperties": false
  },
  "outputSchema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "id": {
        "type": "string",
        "minLength": 1,
        "pattern": "\\S",
        "description": "Opaque profile identifier, unique within this app and unchanged across token refresh, reconnection, and display-metadata changes. Never reassigned to another profile."
      },
      "name": {
        "type": "string",
        "description": "Display name for the authenticated profile."
      },
      "email": {
        "type": "string",
        "description": "Email address for display; not used as the profile identity."
      },
      "nickname": {
        "type": "string",
        "description": "A useful label that helps users distinguish connected profiles."
      }
    },
    "required": ["id"],
    "additionalProperties": false
  },
  "annotations": {
    "readOnlyHint": true,
    "destructiveHint": false,
    "openWorldHint": false
  },
  "securitySchemes": [
    {
      "type": "oauth2",
      "scopes": []
    }
  ],
  "_meta": {
    "openai/profile": true
  }
}

프로필 접근에 OAuth 범위가 필요하다면 연동에 실제로 사용하는 범위를 지정하세요. 이 선언만으로 인증이 구현되지는 않습니다. 서버에서 자격 증명을 검증하고 권한을 적용해야 합니다. 토큰 검증 구현하기와 도구 레퍼런스를 참고하세요.

프로필을 structuredContent에 넣어 반환하면 outputSchema에 따라 검증할 수 있습니다. 호환성을 위해 텍스트 콘텐츠 항목에도 같은 프로필을 JSON으로 직렬화해 포함하세요.

{
  "content": [
    {
      "type": "text",
      "text": "{\"id\":\"prf_8d7e4b19\",\"name\":\"Alex Chen\",\"email\":\"alex@example.com\",\"nickname\":\"Alex — Moonwaffle work\"}"
    }
  ],
  "structuredContent": {
    "id": "prf_8d7e4b19",
    "name": "Alex Chen",
    "email": "alex@example.com",
    "nickname": "Alex — Moonwaffle work"
  },
  "isError": false
}

단일 JSON 객체의 최상위에 프로필 필드를 넣으세요.

이미 프로필 도구가 있나요? 기존 이름을 유지하고 프로필 메타데이터 선언을 추가한 뒤 표준 프로필 응답을 반환하세요. 기존 응답 형식이 다르다면 서버에서 형식을 변환하거나 스키마에 맞는 간단한 래퍼 도구를 제공하세요. 표준 연동 방식에서는 모든 앱이 같은 선언과 응답 형식을 사용합니다.

구현 예시: Moonwaffle 프로필 ID 유지하기

가상의 서비스 Moonwaffle에서 Alex가 두 프로필을 독립적으로 연결할 수 있다고 가정해 보겠습니다. Moonwaffle은 각 프로필에 서로 다른 불투명 ID를 저장합니다. 요청의 자격 증명으로 저장된 프로필 중 하나를 찾으면 프로필 도구가 그 프로필의 기존 ID를 반환합니다.

저장된 프로필 예시. 레이블은 바뀔 수 있지만 식별자는 그대로 유지됩니다.

Alex — Moonwaffle personal: prf_42a9c6e0
Alex — Moonwaffle work:     prf_8d7e4b19

이 예시의 ID에는 프로필 레이블이나 내부 관계가 인코딩되어 있지 않습니다. ID는 프로필마다 한 번만 저장하며, 재연결이나 토큰 갱신 후에도, 이메일이나 표시 이름이 변경된 후에도 같은 값을 사용합니다.

인증된 프로필로 응답을 구성하세요. 다음 JavaScript 예시는 MCP SDK에 연결할 수 있는 핸들러 로직입니다. loadAuthenticatedProfile은 애플리케이션에서 구현하는 연동 코드입니다. 요청의 자격 증명을 검증하고 그 권한을 적용한 뒤 해당 프로필에 저장된 ID와 표시용 메타데이터를 가져옵니다. requestContext는 서버의 요청 처리 과정에서 전달되며 모델이 제공하는 도구 인수가 아닙니다.

async function getProfile(requestContext) {
  // Your auth/provider integration validates credentials and loads
  // the existing profile. Auth failures use normal MCP auth handling.
  const account = await loadAuthenticatedProfile(requestContext);
  const id = account.profileId;

  if (typeof id !== "string" || id.trim().length === 0) {
    return {
      isError: true,
      content: [{ type: "text", text: "Profile identity unavailable." }],
    };
  }

  // Return the persisted ID unchanged; do not generate an ID per call.
  const profile = {
    id,
    ...(typeof account.name === "string" ? { name: account.name } : {}),
    ...(typeof account.email === "string" ? { email: account.email } : {}),
    ...(typeof account.nickname === "string"
      ? { nickname: account.nickname }
      : {}),
  };

  return {
    isError: false,
    structuredContent: profile,
    content: [{ type: "text", text: JSON.stringify(profile) }],
  };
}

위의 메타데이터 선언과 입출력 스키마를 사용해 이 핸들러를 등록하세요. loadAuthenticatedProfile은 동등한 자격 증명으로 요청할 때나 재연결한 후에도 저장된 동일한 프로필을 찾아야 합니다. OAuth 권한 부여나 세션마다 새 프로필 ID를 생성해서는 안 됩니다. 다른 모든 도구도 요청의 자격 증명을 사용해 같은 프로필의 권한을 적용해야 합니다.

프로필 식별 동작 검증하기

테스트 예상 결과
Moonwaffle 업무용 프로필로 반복 호출 prf_8d7e4b19를 매번 반환
토큰 갱신, 재연결 또는 범위 확대 후 같은 프로필로 호출 prf_8d7e4b19
이메일 또는 표시 레이블 변경 후 같은 프로필로 호출 prf_8d7e4b19 유지, 레이블은 변경 가능
Moonwaffle 개인 프로필로 호출 prf_42a9c6e0 반환, 업무용 프로필과 구분
저장된 프로필 ID가 없거나 빈 문자열 또는 공백뿐인 문자열임 오류 반환. 식별자를 임의로 만들거나 다른 프로필로 대체하지 않음

모든 프로필에 대해 이 식별 규칙을 보장하고 향후 연동이 변경되어도 유지해야 합니다. 표시용 메타데이터, 토큰 내용, 연결 수명 주기의 변화가 프로필 식별에 영향을 주지 않도록 하세요.

테스트 및 출시

  • 로컬 테스트: 유효 기간이 짧은 토큰을 발급하는 개발 테넌트에서 시작해 구현과 테스트를 빠르게 반복하세요.
  • 내부 테스트: 인증이 동작하면 먼저 신뢰할 수 있는 테스터에게만 접근을 허용한 뒤 배포 범위를 넓히세요. 특정 도구 또는 MCP 서버 전체에 계정 연결을 필수로 설정할 수 있습니다.
  • 토큰 교체: 토큰 폐기와 갱신, 범위 변경에 대비하세요. 토큰이 없거나 더 이상 유효하지 않으면 서버는 해당 요청을 인증되지 않은 요청으로 처리하고 원인을 파악하는 데 도움이 되는 오류 메시지를 반환해야 합니다.
  • OAuth 디버깅: 출시 전에 MCP Inspector의 Auth 설정에서 OAuth 단계를 하나씩 실행하며 플로우가 어느 단계에서 실패하는지 확인하세요.

인증을 구현하면 ChatGPT와 Codex 사용자에게 사용자별 데이터와 쓰기 작업을 제공할 수 있습니다.

인증 UI 표시하기

ChatGPT는 MCP 서버가 OAuth를 사용할 수 있거나 OAuth가 필요하다고 알릴 때만 OAuth 계정 연결 UI를 표시합니다.

도구별 OAuth 플로우를 시작하려면 메타데이터(securitySchemes와 리소스 메타데이터 문서)와 _meta["mcp/www_authenticate"]를 포함하는 런타임 오류가 모두 필요합니다. 둘 중 하나라도 없으면 ChatGPT는 해당 도구의 계정 연결 UI를 표시하지 않습니다.

  1. 리소스 메타데이터를 게시하세요. MCP 서버는 https://your-mcp.example.com/.well-known/oauth-protected-resource와 같은 표준 URL에서 OAuth 구성을 제공해야 합니다.

  2. 각 도구의 인증 정책을 securitySchemes로 명시하세요. 도구마다 securitySchemes를 선언하면 ChatGPT는 어떤 도구에 OAuth가 필요하고 어떤 도구를 익명으로 실행할 수 있는지 알 수 있습니다. 서버 전체에 같은 정책을 적용하더라도 도구별로 선언하세요. 서버 수준의 기본값을 사용하면 나중에 개별 도구의 정책을 변경하기 어렵습니다.

    현재 두 가지 유형을 사용할 수 있습니다. 인증을 선택 사항으로 두려면 둘 다 나열하세요.

    • noauth: 익명으로 호출할 수 있는 도구입니다. ChatGPT가 바로 실행할 수 있습니다.
    • oauth2: OAuth 2.0 액세스 토큰이 필요한 도구입니다. 동의 화면에 정확한 권한이 표시되도록 요청할 범위를 포함하세요.

    배열을 완전히 생략하면 도구는 서버가 명시한 기본값을 상속합니다. noauth와 oauth2를 모두 선언하면 ChatGPT는 익명 호출로 시작할 수 있고, 계정을 연결하면 추가 권한이 필요한 기능을 사용할 수 있다는 것을 알 수 있습니다. 클라이언트에 어떤 정책을 알리든 서버는 호출마다 토큰, 범위, 대상을 검증해야 합니다.

    예시(공개 도구, 인증 선택 사항) — TypeScript SDK

    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { z } from "zod";
    
    const server = new McpServer({ name: "auth-example", version: "1.0.0" });
    
    server.registerTool(
      "search",
      {
        title: "Public Search",
        description: "Search public documents.",
        inputSchema: {
          q: z.string(),
        },
        outputSchema: {},
        _meta: {
          securitySchemes: [
            { type: "noauth" },
            { type: "oauth2", scopes: ["search.read"] },
          ],
        },
      },
      async ({ q }) => {
        return {
          content: [{ type: "text", text: `Results for ${q}` }],
          structuredContent: {},
        };
      }
    );

    예시(인증 필수) — TypeScript SDK

    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { z } from "zod";
    
    const server = new McpServer({ name: "auth-example", version: "1.0.0" });
    
    server.registerTool(
      "create_doc",
      {
        title: "Create Document",
        description: "Make a new doc in your account.",
        inputSchema: {
          title: z.string(),
        },
        outputSchema: {},
        _meta: {
          securitySchemes: [{ type: "oauth2", scopes: ["docs.write"] }],
        },
      },
      async ({ title }) => {
        return {
          content: [{ type: "text", text: `Created doc: ${title}` }],
          structuredContent: {},
        };
      }
    );
  3. ChatGPT가 인증 UI를 표시하도록 하려면 도구 핸들러에서 토큰을 확인하고 _meta["mcp/www_authenticate"]를 반환하세요. 토큰을 검사해 발급자, 대상, 만료 시각, 범위를 검증하세요. 유효한 토큰이 없으면 _meta["mcp/www_authenticate"]를 포함한 오류 결과를 반환하고 그 값에 error와 error_description 매개변수가 모두 포함되어 있는지 확인하세요. 1단계와 2단계의 구성을 마치면 ChatGPT는 이 WWW-Authenticate 페이로드를 받았을 때 도구별 OAuth UI를 표시합니다. 챌린지로 재인가를 요청할 때 공급자는 해당 플로우에서 사용자의 기존 로그인 컨텍스트를 유지할 수 있습니다.

    예시

    {
      "jsonrpc": "2.0",
      "id": 4,
      "result": {
        "content": [
          {
            "type": "text",
            "text": "Authentication required: no access token provided."
          }
        ],
        "_meta": {
          "mcp/www_authenticate": [
            "'Bearer resource_metadata=\"https://your-mcp.example.com/.well-known/oauth-protected-resource\", error=\"insufficient_scope\", error_description=\"You need to login to continue\"'"
          ]
        },
        "isError": true
      }
    }