curl --request POST 'https://api.voicecheap.ai/oauth/register' \
--header 'Content-Type: application/json' \
--data '{"client_name":"Acme assistant","redirect_uris":["https://assistant.example.com/oauth/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"],"scope":"account:read projects:read offline_access"}'
const input = {
client_name: 'Acme assistant',
redirect_uris: ['https://assistant.example.com/oauth/callback'],
token_endpoint_auth_method: 'none',
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
scope: 'account:read projects:read offline_access',
};
const response = await fetch('https://api.voicecheap.ai/oauth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(input),
redirect: 'manual',
});
console.log(response.status, await response.text());
import os
import requests
response = requests.request(
"POST",
"https://api.voicecheap.ai/oauth/register",
headers={"Content-Type":"application/json"},
json={"client_name": "Acme assistant", "redirect_uris": ["https://assistant.example.com/oauth/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "account:read projects:read offline_access"},
timeout=120,
allow_redirects=False,
)
print(response.status_code, response.text)
OAuth 및 MCP 프로토콜
MCP OAuth 클라이언트 등록
정확한 콜백 URL과 제한된 제품 권한 집합으로 공개 클라이언트를 등록합니다.
POST
/
oauth
/
register
curl --request POST 'https://api.voicecheap.ai/oauth/register' \
--header 'Content-Type: application/json' \
--data '{"client_name":"Acme assistant","redirect_uris":["https://assistant.example.com/oauth/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"],"scope":"account:read projects:read offline_access"}'
const input = {
client_name: 'Acme assistant',
redirect_uris: ['https://assistant.example.com/oauth/callback'],
token_endpoint_auth_method: 'none',
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
scope: 'account:read projects:read offline_access',
};
const response = await fetch('https://api.voicecheap.ai/oauth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(input),
redirect: 'manual',
});
console.log(response.status, await response.text());
import os
import requests
response = requests.request(
"POST",
"https://api.voicecheap.ai/oauth/register",
headers={"Content-Type":"application/json"},
json={"client_name": "Acme assistant", "redirect_uris": ["https://assistant.example.com/oauth/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "account:read projects:read offline_access"},
timeout=120,
allow_redirects=False,
)
print(response.status_code, response.text)
정확한 콜백 URL과 제한된 제품 권한 집합으로 공개 클라이언트를 등록합니다.
POST https://api.voicecheap.ai/oauth/register
인증
없습니다. 등록은 사용자를 로그인시키거나 계정 연결에 권한을 부여하지 않습니다.요청 본문
array
필수
중복을 제거하기 전 콜백 수는 1개부터 10개 사이입니다.최소 항목 수: 1. 최대 항목 수: 10.
string
선택적 클라이언트 라벨입니다. 저장 전에 앞뒤 공백을 제거하며, 제거 후에도 비어 있지 않아야 합니다.최소 길이: 1. 최대 길이: 120. 패턴:
^(?=.*\S)[^\u0000-\u001F\u007F]+$. 기본값: “MCP client”.field
공개 클라이언트만 허용됩니다.필수 값: “none”. 기본값: “none”.
union
authorization_code를 포함해야 하며 refresh_token은 선택 사항입니다. 생략하거나 null이면 둘 다 선택됩니다. 중복 값은 제거됩니다.기본값: [“authorization_code”,“refresh_token”].
array
값 목록입니다. 항목 구조는 전체 입력 스키마를 참고하세요.최소 항목 수: 1. 최대 항목 수: 1. 기본값: [“code”].
string
scopes_supported의 권한을 공백으로 구분하며 선택적으로 offline_access를 포함할 수 있습니다. 지원되지 않는 이름과 공백만 있는 값은 거부되며 중복은 제거됩니다. scope를 생략하거나 openid만 또는 offline_access만 지정하는 등 제품 권한을 제공하지 않으면 지원되는 모든 제품 범위가 등록됩니다. email에는 openid가 필요하며 email만 지정하면 거부됩니다. OpenID Connect를 사용할 수 있으면 scope를 생략해도 openid와 email이 클라이언트 기능으로 등록됩니다. 등록만으로 접근 권한이 부여되지는 않으며 신원 권한에는 명시적인 권한 부여 요청과 사용자 동의가 필요합니다.최소 길이: 1. 최대 길이: 2048. 패턴:
^ *(?:account:read|usage:read|settings:read|settings:write|team:read|team:write|projects:read|projects:write|projects:delete|translations:write|transcription:read|transcription:write|subtitles:read|subtitles:write|tts:read|tts:write|voices:read|voices:write|images:read|images:write|thumbnails:read|thumbnails:write|folders:read|folders:write|glossary:read|glossary:write|webhooks:read|webhooks:write|publishing:read|publishing:write|offline_access|openid|email)(?: +(?:account:read|usage:read|settings:read|settings:write|team:read|team:write|projects:read|projects:write|projects:delete|translations:write|transcription:read|transcription:write|subtitles:read|subtitles:write|tts:read|tts:write|voices:read|voices:write|images:read|images:write|thumbnails:read|thumbnails:write|folders:read|folders:write|glossary:read|glossary:write|webhooks:read|webhooks:write|publishing:read|publishing:write|offline_access|openid|email))* *$.기술 스키마
기술 스키마
{
"type": "object",
"properties": {
"redirect_uris": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 2048,
"pattern": "^[^\\s\\u0000-\\u001F\\u007F-\\u009F#\\\\]+$",
"format": "uri",
"anyOf": [
{
"pattern": "^[Hh][Tt][Tt][Pp][Ss]://"
},
{
"pattern": "^http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::[0-9]{1,5})?(?:/|\\?|$)"
},
{
"const": "cursor://anysphere.cursor-mcp/oauth/callback"
}
],
"description": "An absolute HTTPS callback, an HTTP callback using the exact lowercase loopback spelling localhost, 127.0.0.1, or [::1], or exactly cursor://anysphere.cursor-mcp/oauth/callback. Must parse as a URL with no username/password, whitespace, control characters, backslashes, or fragment. A loopback port must be a valid URL port. The exact spelling is retained."
},
"description": "Between 1 and 10 callbacks before duplicates are removed."
},
"client_name": {
"type": "string",
"minLength": 1,
"maxLength": 120,
"pattern": "^(?=.*\\S)[^\\u0000-\\u001F\\u007F]+$",
"default": "MCP client",
"description": "Optional client label; trimmed before saving and must remain nonempty."
},
"token_endpoint_auth_method": {
"const": "none",
"default": "none",
"description": "Public clients only."
},
"grant_types": {
"anyOf": [
{
"type": "array",
"minItems": 1,
"items": {
"enum": [
"authorization_code",
"refresh_token"
]
},
"contains": {
"const": "authorization_code"
}
},
{
"type": "null"
}
],
"default": [
"authorization_code",
"refresh_token"
],
"description": "Must include authorization_code; refresh_token is optional. Omitted or null selects both. Duplicate values are removed."
},
"response_types": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"const": "code"
},
"default": [
"code"
]
},
"scope": {
"type": "string",
"minLength": 1,
"maxLength": 2048,
"pattern": "^ *(?:account:read|usage:read|settings:read|settings:write|team:read|team:write|projects:read|projects:write|projects:delete|translations:write|transcription:read|transcription:write|subtitles:read|subtitles:write|tts:read|tts:write|voices:read|voices:write|images:read|images:write|thumbnails:read|thumbnails:write|folders:read|folders:write|glossary:read|glossary:write|webhooks:read|webhooks:write|publishing:read|publishing:write|offline_access|openid|email)(?: +(?:account:read|usage:read|settings:read|settings:write|team:read|team:write|projects:read|projects:write|projects:delete|translations:write|transcription:read|transcription:write|subtitles:read|subtitles:write|tts:read|tts:write|voices:read|voices:write|images:read|images:write|thumbnails:read|thumbnails:write|folders:read|folders:write|glossary:read|glossary:write|webhooks:read|webhooks:write|publishing:read|publishing:write|offline_access|openid|email))* *$",
"description": "Space-delimited permissions from scopes_supported, optionally including offline_access. Unsupported names and whitespace-only values are rejected; duplicates are removed. Omitting scope or supplying no product scopes, such as openid alone or offline_access alone, registers all supported product scopes. email requires openid; email alone is rejected. When OpenID Connect is available, registration includes openid and email as client capabilities even when scope is omitted. Registration grants no access; identity permissions require an explicit authorization request and user consent.",
"examples": [
"account:read projects:read offline_access"
]
}
},
"required": [
"redirect_uris"
],
"additionalProperties": true
}
요청 예제
이 경로에 명시된 인코딩 및 인증 방식을 따르세요. 액션 API의 응답 구조와 멱등성 규칙은 액션 호출에만 적용됩니다.curl --request POST 'https://api.voicecheap.ai/oauth/register' \
--header 'Content-Type: application/json' \
--data '{"client_name":"Acme assistant","redirect_uris":["https://assistant.example.com/oauth/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"],"scope":"account:read projects:read offline_access"}'
const input = {
client_name: 'Acme assistant',
redirect_uris: ['https://assistant.example.com/oauth/callback'],
token_endpoint_auth_method: 'none',
grant_types: ['authorization_code', 'refresh_token'],
response_types: ['code'],
scope: 'account:read projects:read offline_access',
};
const response = await fetch('https://api.voicecheap.ai/oauth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(input),
redirect: 'manual',
});
console.log(response.status, await response.text());
import os
import requests
response = requests.request(
"POST",
"https://api.voicecheap.ai/oauth/register",
headers={"Content-Type":"application/json"},
json={"client_name": "Acme assistant", "redirect_uris": ["https://assistant.example.com/oauth/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "account:read projects:read offline_access"},
timeout=120,
allow_redirects=False,
)
print(response.status_code, response.text)
응답
등록된 공개 클라이언트 메타데이터입니다. client_id를 저장하고 정확히 등록된 redirect_uri와 함께 사용하세요. client_secret은 발급되지 않습니다.HTTP 201
{
"client_id": "mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"client_id_issued_at": 1792152000,
"client_name": "Acme assistant",
"redirect_uris": ["https://assistant.example.com/oauth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "account:read projects:read offline_access"
}
응답 헤더
{
"Cache-Control": "no-store",
"Pragma": "no-cache"
}
작업 참고 사항
- 인식되는 스칼라 매개변수는 ASCII 제어 문자나 DEL이 없는 비어 있지 않은 문자열로 한 번만 제공해야 합니다. 반복된 쿼리/폼 필드는 배열로 디코딩되며, 스칼라로 읽을 때 거부됩니다.
- Content-Type으로 application/json을 사용하세요. URL 인코딩된 등록 본문은 OAuth invalid_request 응답으로 거부됩니다. 지원하지 않는 미디어 유형은 통합 파서에서 더 일찍 거부될 수 있습니다.
- 리디렉션 URI는 그대로 유지되며 정확히 비교됩니다. 대소문자, 포트 표기, 경로, 쿼리 또는 마지막 슬래시를 변경하려면 해당 URI를 정확히 등록해야 합니다.
- HTTP 콜백은 루프백 이름을 localhost, 127.0.0.1 및 [::1] 표기 그대로만 허용합니다. 사설 네트워크 호스트, 다른 IP 표기, *.localhost 및 대문자 LOCALHOST는 해당하지 않습니다.
- 허용되는 사용자 지정 URI 스킴 콜백은 정확히 cursor://anysphere.cursor-mcp/oauth/callback뿐입니다.
- 모든 콜백이 검증을 통과해야 하며, 유효하지 않은 콜백이 하나라도 있으면 전체 등록이 거부됩니다. URL 내 인증 정보, 공백, 제어 문자, 프래그먼트 및 역슬래시는 허용되지 않습니다.
- 클라이언트는 지원되는 모든 제품 범위를 등록할 수 있지만, 사용자는 인증 시 요청된 하위 집합을 별도로 승인해야 합니다. 등록만으로 접근 권한이 부여되지는 않습니다.
- authorization_code 및 선택적 refresh_token 부여 유형만 지원됩니다. refresh_token 없이 등록된 클라이언트는 갱신 토큰을 받지 않습니다.
- 현재 구현은 알 수 없는 최상위 메타데이터 필드를 무시합니다. 알려진 필드에는 스키마에 표시된 제약 조건이 그대로 적용됩니다.
- 공개 등록 한도는 클라이언트 IP당 10분에 10회 요청이며 워커 간에 공유됩니다. JSON 본문은 1 MiB로 제한됩니다.
- 등록에 성공하면 HTTP 201을 반환합니다.
- openid와 email은 제품 권한과 별개인 신원 권한이며, 지원이 명시된 경우에만 허용됩니다. email에는 openid가 필요합니다. 최초 권한 부여 시 신원 범위만 요청하면 동의 화면에 표시되는 기본 제품 권한이 사용됩니다.
오류
| HTTP 상태 | 코드 | 의미 또는 복구 방법 |
|---|---|---|
| 400 | invalid_request | 본문 또는 인식된 매개변수가 유효하지 않거나, 누락되었거나, 중복되었거나, 인코딩이 잘못되었습니다. |
| 400 | invalid_client_metadata | 콜백 수가 유효하지 않거나, 클라이언트 레이블이 비어 있거나, 클라이언트 인증, 권한 부여 유형 또는 응답 유형이 지원되지 않습니다. |
| 400 | invalid_redirect_uri | 콜백 URL이 지원되지 않거나 안전하지 않은 형식을 사용합니다. |
| 400 | invalid_scope | 범위 문자열에 지원되지 않는 권한이 포함되어 있거나 요청된 범위가 없습니다. |
| 413 | REQUEST_TOO_LARGE | 요청이 허용된 본문 크기를 초과합니다. JSON은 1 MiB, URL 인코딩된 양식은 32 KiB로 제한됩니다. |
| 415 | UNSUPPORTED_CONTENT_TYPE | 통합 본문 파서는 JSON 또는 URL 인코딩된 폼만 받습니다. 경로에 따라 이 인코딩 중 하나만 요구할 수 있습니다. |
| 429 | temporarily_unavailable | 공개 OAuth 요청 속도 제한에 도달했습니다. 현재 제한 시간 구간이 끝날 때까지 기다린 후 다시 시도하세요. |
| 500 | server_error | 일시적으로 권한 부여를 사용할 수 없습니다. 나중에 다시 시도하세요. |
이 페이지가 도움이 되었나요?

