MCP 클라이언트용 OAuth
사용자를 TwexAPI에 로그인시키는 MCP 클라이언트를 만들기 위한 레퍼런스: 디스커버리, 동적 클라이언트 등록, PKCE, 스코프, 토큰, 갱신, 취소.
TwexAPI는 MCP 서버를 위한 OAuth 2.1 인가 서버를 운영합니다. Claude, ChatGPT, Cursor 같은 MCP 클라이언트는 이를 자동으로 사용합니다. 직접 클라이언트를 만들거나 클라이언트 문제를 디버깅할 때 이 페이지를 참고하세요.
- PKCE를 사용하는 인가 코드 흐름(
S256만 지원). - 퍼블릭 클라이언트만 지원하며 클라이언트 시크릿이 없습니다. 동적 클라이언트 등록(RFC 7591)으로 등록합니다.
- 사용자는
https://twexapi.io/oauth/consent에서 TwexAPI 계정으로 로그인하고 승인합니다. - 토큰은 불투명한 문자열이며
https://api.twexapi.io/mcp에서만 동작합니다. TwexAPI API 키가 아니므로 REST API를 호출할 수 없습니다. - 호출은 사용자의 API 키를 통해 해당 사용자의 크레딧에서 차감되므로, 승인하려면 크레딧이 남은 활성 키가 필요합니다.
스크립트나 백엔드에서 TwexAPI를 호출하기만 한다면 API 키를 사용하세요.
디스커버리
자격 증명 없이 https://api.twexapi.io/mcp를 요청하면 다음 헤더와 함께 401이 반환됩니다.
WWW-Authenticate: Bearer realm="twexapi", resource_metadata="https://api.twexapi.io/.well-known/oauth-protected-resource/mcp", scope="mcp:read mcp:write"
보호된 리소스 메타데이터
GET https://api.twexapi.io/.well-known/oauth-protected-resource/mcp(/.well-known/oauth-protected-resource에서도 같은 내용):
{
"resource": "https://api.twexapi.io/mcp",
"authorization_servers": ["https://api.twexapi.io"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["mcp:read", "mcp:write"],
"resource_name": "TwexAPI"
}
인가 서버 메타데이터
GET https://api.twexapi.io/.well-known/oauth-authorization-server:
{
"issuer": "https://api.twexapi.io",
"authorization_endpoint": "https://api.twexapi.io/oauth/authorize",
"token_endpoint": "https://api.twexapi.io/oauth/token",
"registration_endpoint": "https://api.twexapi.io/oauth/register",
"revocation_endpoint": "https://api.twexapi.io/oauth/revoke",
"response_types_supported": ["code"],
"response_modes_supported": ["query"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"revocation_endpoint_auth_methods_supported": ["none"],
"scopes_supported": ["mcp:read", "mcp:write"],
"authorization_response_iss_parameter_supported": true
}
스코프
| 스코프 | 허용 범위 |
|---|---|
mcp:read |
explore와 twexapi_request(읽기 전용 엔드포인트). 항상 부여됩니다. |
mcp:write |
twexapi_write(트윗과 답글 게시). 사용자가 동의 페이지에서 거부할 수 있습니다. |
두 가지를 모두 요청하려면 mcp:read mcp:write를 지정합니다. 알 수 없는 스코프는 무시되며, 인식 가능한 스코프가 하나도 없으면 두 가지를 모두 요청한 것으로 처리합니다. 사용자가 실제로 허용한 범위는 토큰 응답의 scope 필드로 알 수 있습니다.
1. 클라이언트 등록
POST https://api.twexapi.io/oauth/register
Content-Type: application/json
{
"client_name": "My Agent",
"redirect_uris": ["http://127.0.0.1:53682/callback"]
}
{
"client_id": "twx_client_...",
"client_id_issued_at": 1792600000,
"client_name": "My Agent",
"redirect_uris": ["http://127.0.0.1:53682/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
| 필드 | 규칙 |
|---|---|
redirect_uris |
필수. URI 1~10개이며 각각 다음 중 하나여야 합니다: https://, localhost·127.0.0.1·[::1]의 http://, 또는 cursor:// 같은 사설 스킴. 프래그먼트나 사용자 정보는 허용되지 않습니다. |
client_name |
선택. 최대 100자. 동의 페이지와 사용자의 Authorized apps(승인된 앱) 목록에 표시됩니다. |
token_endpoint_auth_method |
선택. none만 허용됩니다. |
grant_types |
선택. authorization_code와 refresh_token의 부분 집합. |
response_types |
선택. code만 허용됩니다. |
등록은 IP 주소당 시간당 30개 클라이언트로 제한됩니다. 클라이언트 등록은 180일 동안 유효하며, 사용자가 해당 클라이언트로 인가를 시작할 때마다 연장됩니다. 한 번 등록한 뒤 client_id를 재사용하세요.
2. PKCE 쌍 만들기
Node.js:
import crypto from "node:crypto";
const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
Python:
import base64, hashlib, secrets
verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode()
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
verifier는 A-Z a-z 0-9 - . _ ~로 이루어진 43~128자여야 합니다.
3. 사용자를 인가 페이지로 보내기
사용자의 브라우저에서 다음 URL을 엽니다.
https://api.twexapi.io/oauth/authorize
?response_type=code
&client_id=twx_client_...
&redirect_uri=http%3A%2F%2F127.0.0.1%3A53682%2Fcallback
&code_challenge=CHALLENGE
&code_challenge_method=S256
&state=RANDOM_STATE
&scope=mcp%3Aread%20mcp%3Awrite
&resource=https%3A%2F%2Fapi.twexapi.io%2Fmcp
| 파라미터 | 필수 | 참고 |
|---|---|---|
response_type |
예 | code. |
client_id |
예 | 등록 시 받은 값. |
redirect_uri |
예 | 등록된 URI와 정확히 일치해야 합니다. 루프백 http URI는 어떤 포트든 사용할 수 있습니다. |
code_challenge, code_challenge_method |
예 | S256만 지원합니다. |
state |
권장 | 그대로 반환됩니다. CSRF 방지를 위해 검증하세요. |
scope |
아니요 | 스코프를 참고하세요. |
resource |
아니요 | 보내는 경우 https://api.twexapi.io/mcp여야 합니다. |
TwexAPI는 사용자를 twexapi.io의 동의 페이지로 보냅니다. 사용자는 로그인하고, 앱 이름과 돌아갈 호스트를 확인하고, 쓰기 권한 부여 여부를 선택한 뒤 승인하거나 거부합니다. 요청은 10분 후 만료됩니다.
4. 코드 받기
승인하면 브라우저가 리디렉션 URI로 돌아옵니다.
http://127.0.0.1:53682/callback?code=CODE&state=RANDOM_STATE&iss=https%3A%2F%2Fapi.twexapi.io
state가 일치하는지, iss가 https://api.twexapi.io인지 확인하세요. 거부하면 code 대신 error=access_denied가 전달됩니다. 코드는 한 번만 사용할 수 있으며 2분 후 만료됩니다.
client_id를 알 수 없거나 redirect_uri가 등록되지 않은 경우, TwexAPI는 리디렉션하지 않고 400 오류를 표시합니다. 따라서 잘못 설정된 클라이언트가 사용자를 검증되지 않은 주소로 보내는 일은 없습니다.
5. 코드를 토큰으로 교환
POST https://api.twexapi.io/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=CODE
&redirect_uri=http%3A%2F%2F127.0.0.1%3A53682%2Fcallback
&client_id=twx_client_...
&code_verifier=VERIFIER
{
"access_token": "twx_at_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "twx_rt_...",
"scope": "mcp:read mcp:write"
}
redirect_uri와 client_id는 인가 요청과 같아야 합니다. resource=https://api.twexapi.io/mcp를 함께 보낼 수도 있습니다. JSON 본문도 허용됩니다.
6. MCP 서버 호출
POST https://api.twexapi.io/mcp
Authorization: Bearer twx_at_...
Content-Type: application/json
Accept: application/json, text/event-stream
액세스 토큰은 /mcp에서만 동작합니다. 만료되었거나 취소되었거나 알 수 없는 토큰에는 WWW-Authenticate 헤더에 error="invalid_token"이 포함된 401이 반환됩니다. 토큰을 갱신하거나 사용자에게 다시 로그인하도록 요청하세요.
토큰 갱신
POST https://api.twexapi.io/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=twx_rt_...
&client_id=twx_client_...
갱신할 때마다 새 액세스 토큰과 새 리프레시 토큰이 반환되고, 이전 리프레시 토큰은 더 이상 동작하지 않습니다. 매번 새로 받은 쌍을 저장하세요. 이전 리프레시 토큰이 다시 사용되면 TwexAPI는 유출된 것으로 간주해 인가 전체를 취소합니다. 그 인가의 모든 토큰이 동작을 멈추고 사용자는 다시 로그인해야 합니다.
취소
POST https://api.twexapi.io/oauth/revoke
Content-Type: application/x-www-form-urlencoded
token=twx_rt_...
리프레시 토큰을 취소하면 인가 전체가 종료됩니다. 액세스 토큰을 취소하면 해당 토큰만 무효가 됩니다. 이 엔드포인트는 알 수 없는 토큰에도 항상 200을 반환합니다. 사용자는 대시보드의 Authorized apps(승인된 앱)에서도 앱을 취소할 수 있으며, 즉시 적용됩니다.
토큰 유효 기간
| 항목 | 유효 기간 |
|---|---|
| 인가 요청(동의 페이지) | 10분 |
| 인가 코드 | 2분, 1회용 |
| 액세스 토큰 | 1시간 |
| 리프레시 토큰 | 30일, 사용할 때마다 교체 |
| 30일 동안 사용되지 않은 인가 | 만료 |
| 클라이언트 등록 | 180일, 인가할 때마다 연장 |
오류
오류는 표준 OAuth 형식을 따릅니다.
{
"error": "invalid_grant",
"error_description": "The authorization code is invalid, expired or already used"
}
| 엔드포인트 | error |
원인 |
|---|---|---|
| 등록 | invalid_redirect_uri |
리디렉션 URI가 없거나 형식이 잘못되었거나, 허용되지 않는 스킴이나 호스트를 사용합니다. |
| 등록 | invalid_client_metadata |
지원하지 않는 token_endpoint_auth_method, grant_types, response_types. |
| 등록 | temporarily_unavailable(429) |
해당 IP 주소의 등록이 너무 많습니다. client_id를 재사용하세요. |
| 인가 | invalid_client(400, 리디렉션 없음) |
알 수 없는 client_id입니다. 먼저 등록하세요. |
| 인가 | invalid_request(400, 리디렉션 없음) |
redirect_uri가 이 클라이언트에 등록되지 않았습니다. |
| 인가 | unsupported_response_type |
response_type이 code가 아닙니다. |
| 인가 | invalid_request |
PKCE 파라미터가 없거나 잘못되었습니다. |
| 인가 | invalid_target |
resource가 https://api.twexapi.io/mcp가 아닙니다. |
| 인가 | access_denied |
사용자가 접근을 거부했습니다. |
| 토큰 | invalid_grant |
코드나 리프레시 토큰이 잘못되었거나, 만료되었거나, 이미 사용되었거나, 다른 클라이언트에 발급된 것입니다. 또는 PKCE 검증에 실패했거나 인가가 취소되었습니다. |
| 토큰 | invalid_target |
resource가 일치하지 않습니다. |
| 토큰 | unsupported_grant_type |
authorization_code 또는 refresh_token을 사용하세요. |
사용자 계정에 활성 API 키나 크레딧이 없으면 동의 페이지가 승인 전에 막기 때문에, 호출할 수 없는 토큰이 클라이언트에 전달되는 일은 없습니다.