여러 인증 방법
여러 인증 방법 (Multiple Authentication Methods)
단일 커넥터에 대해 여러 세트의 자격 증명(credentials)을 저장하고 관리한 다음, 런타임에 특정 자격 증명으로 도구를 호출해서 액세스를 제어하는 방법을 다루는 쿡북이에요. 정적 토큰(Bearer)과 OAuth2 대리 인증 흐름 두 가지 패턴을 다뤄요.
출처: 문서
본문
단일 커넥터에 대해 여러 세트의 자격 증명을 저장하고 관리한 다음, 런타임에 특정 자격 증명으로 도구를 호출해서 액세스를 제어해 봅시다.
이 쿡북은 두 가지 인증 패턴을 다뤄요.
- Bearer token — 정적 토큰(GitHub PAT)을 API를 통해 직접 저장.
- OAuth2 —
get_auth_url을 통해 시작되는 위임 인증 흐름(Delegated auth flows, Microsoft 계정).
API 상태 (API status): 자격 증명 관리는 client.beta.connectors를 사용해요. 이는 베타(beta) 엔드포인트로 변경될 수 있어요.
Part 1: 여러 Bearer 토큰 자격 증명 (GitHub MCP)
사전 준비사항 - Bearer (Prerequisites)
설치 (Install)
# Python
pip install mistralai
# or with uv
uv add mistralai
이 쿡북을 완료하려면 Mistral API 키가 필요해요. Studio에서 API keys 섹션으로 이동해서 새 API 키를 만들어요.
MISTRAL_API_KEY=your-mistral-api-key
GITHUB_PAT_FULL=«redacted:ghp_…»
GITHUB_PAT_LIMITED=«redacted:ghp_…»
GITHUB_PAT_FULL—repo읽기 스코프가 있는 PAT로, 이슈 목록을 성공적으로 가져오는 데 사용.GITHUB_PAT_LIMITED— 스코프가 없거나 유효하지 않은 값의 PAT로, 거부된 호출을 보여주는 데 사용.
GitHub 개발자 설정에서 GitHub 개인 액세스 토큰을 만들어요.
스크립트 (Script): python/src/scripts/07_multiple_bearer_authentication.py
여러 Bearer 자격 증명을 쓸 시점 (When to Use Multiple Bearer Credentials)
- 액세스 계층 테스트 — 제한된 토큰이 전체 토큰이 닿을 수 있는 리소스에 접근하지 못하는지 검증.
- 안전하게 자격 증명 교체 — 새 자격 증명을 추가하고, 기본으로 승격하고, 다운타임 없이 이전 것을 삭제.
- 도구 호출을 명시적으로 스코프 지정 —
credentials_name을call_tool에 전달해서 어떤 정체성이 요청을 실행할지 선택.
1. 클라이언트 초기화 (Initialize the Client)
Python:
import os
from mistralai import Mistral
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
curl:
export MISTRAL_API_KEY="your-api-key"
export BASE_URL="https://api.mistral.ai"
2. GitHub MCP 커넥터 만들기 (Create a GitHub MCP Connector)
Bearer 인증이 있는 기존 커넥터를 사용한다면 이 단계는 건너뛰면 돼요.
Python:
import asyncio
import json
import os
import subprocess
BASE_URL = "https://api.mistral.ai"
API_KEY = os.environ["MISTRAL_API_KEY"]
async def main() -> None:
result = subprocess.run(
[
"curl", "-s", "-X", "POST",
f"{BASE_URL}/v1/connectors",
"-H", f"Authorization: Bearer ***",
"-H", "Content-Type: application/json",
"-d", json.dumps({
"name": "my_github",
"description": "GitHub MCP connector for issue and PR management",
"server": "https://api.githubcopilot.com/mcp/",
"visibility": "private",
"auth_scheme": {"type": "http", "scheme": "Bearer"},
}),
],
capture_output=True,
text=True,
check=True,
)
connector = json.loads(result.stdout)
if "id" not in connector:
raise RuntimeError(f"Failed to create connector: {result.stdout}")
print(f"ID: {connector['id']}")
print(f"Name: {connector['name']}")
asyncio.run(main())
curl:
curl -X POST "${BASE_URL}/v1/connectors" \
-H "Authorization: Bearer ${MIST...KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "my_github",
"description": "GitHub MCP connector for issue and PR management",
"server": "https://api.githubcopilot.com/mcp/",
"visibility": "private",
"auth_scheme": {"type": "http", "scheme": "Bearer"}
}'
출력 (Output):
ID: a1b2c3d4-5678-90ab-cdef-1234567890ab
Name: my_github
| Error | Cause | Fix |
|---|---|---|
409 Conflict |
A connector named my_github already exists |
Choose a different name or delete the existing one first |
3. 인증 방법 가져오기 (Get Authentication Methods)
목표 (Goal): 자격 증명을 저장하기 전에 커넥터가 어떤 인증 스킴을 지원하는지 확인해요.
참고 (Note): connector_id_or_name은 다소 투박하며 향후 버전에서 connector_ref로 대체될 거예요.
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
methods = await client.beta.connectors.get_authentication_methods_async(
connector_id_or_name="my_github",
)
for method in methods:
print(f"Auth type: {method.method_type}")
asyncio.run(main())
curl:
curl -X GET "${BASE_URL}/v1/connectors/my_github/authentication_methods" \
-H "Authorization: Bearer ${MIST...KEY}"
출력 예시 (Example output):
Auth type: bearer
4. Bearer 자격 증명 저장 (Store Bearer Credentials)
목표 (Goal): 커넥터에 이름 있는 bearer-token 자격 증명을 저장해요.
자격 증명은 세 가지 스코프로 저장할 수 있어요.
| Scope | SDK method | Who can use it |
|---|---|---|
user |
create_or_update_user_credentials |
Only the authenticated user |
workspace |
create_or_update_workspace_credentials |
Everyone in the workspace |
organization |
create_or_update_organization_credentials |
Everyone in the organization |
Python:
import asyncio
import os
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
# Credentials A — full repo read access, set as default
result = await client.beta.connectors.create_or_update_user_credentials_async(
connector_id_or_name="my_github",
name="github-pat-full",
credentials={"bearer_token": os.environ["GITHUB_PAT_FULL"]},
is_default=True,
)
print(result.message)
# Credentials B — no scopes / invalid token
result = await client.beta.connectors.create_or_update_user_credentials_async(
connector_id_or_name="my_github",
name="github-pat-limited",
credentials={"bearer_token": os.environ["GITHUB_PAT_LIMITED"]},
)
print(result.message)
asyncio.run(main())
curl:
# Credentials A — full repo read access (set as default)
curl -X POST "${BASE_URL}/v1/connectors/my_github/user/credentials" \
-H "Authorization: Bearer ${MIST...KEY}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"github-pat-full\",
\"credentials\": {\"bearer_token\": \"${GITHUB_PAT_FULL}\"},
\"is_default\": true
}"
# Credentials B — no scopes / invalid token
curl -X POST "${BASE_URL}/v1/connectors/my_github/user/credentials" \
-H "Authorization: Bearer ${MIST...KEY}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"github-pat-limited\",
\"credentials\": {\"bearer_token\": \"${GITHUB_PAT_LIMITED}\"}
}"
출력 (Output):
Credentials 'github-pat-full' saved successfully
Credentials 'github-pat-limited' saved successfully
작동 방식 (How it works):
is_default: true는 자격 증명을 기본으로 표시해요 —credentials_name을 생략한 호출은 이것을 사용해요.- 같은
name으로 같은 엔드포인트를 다시 호출하면 저장된 토큰을 그 자리에서 업데이트해요. - 원시 토큰은 list/get 엔드포인트에서 절대 반환되지 않아요.
| Error | Cause | Fix |
|---|---|---|
400 Bad Request |
Empty credentials object | Provide at least bearer_token |
422 Unprocessable Entity |
Invalid credentials name | Use alphanumeric names with hyphens only |
5. 자격 증명 목록 (List Credentials)
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
response = await client.beta.connectors.list_user_credentials_async(
connector_id_or_name="my_github",
)
for cred in response.credentials:
default_marker = " (default)" if cred.is_default else ""
print(f" {cred.name} [{cred.authentication_type}]{default_marker}")
asyncio.run(main())
curl:
curl -X GET "${BASE_URL}/v1/connectors/my_github/user/credentials" \
-H "Authorization: Bearer ${MIST...KEY}"
출력 (Output):
github-pat-full [bearer] (default)
github-pat-limited [bearer]
6. 특정 자격 증명으로 도구 호출 (Call a Tool with Specific Credentials)
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
# Call with the full-access credentials — should succeed
result = await client.beta.connectors.call_tool_async(
connector_id_or_name="my_github",
tool_name="list_issues",
arguments={"owner": "octocat", "repo": "hello-world", "state": "open"},
credentials_name="github-pat-full",
)
print(f"[github-pat-full] {result.content[:200]}")
# Call with the limited/invalid credentials — access error is in the response content
result = await client.beta.connectors.call_tool_async(
connector_id_or_name="my_github",
tool_name="list_issues",
arguments={"owner": "octocat", "repo": "hello-world", "state": "open"},
credentials_name="github-pat-limited",
)
print(f"[github-pat-limited] {result.content[:200]}")
asyncio.run(main())
curl:
curl -X POST "${BASE_URL}/v1/connectors/my_github/call_tool" \
-H "Authorization: Bearer ${MIST...KEY}" \
-H "Content-Type: application/json" \
-d '{
"tool_name": "list_issues",
"arguments": {"owner": "octocat", "repo": "hello-world", "state": "open"},
"credentials_name": "github-pat-full"
}'
출력 예시 (Example output):
[github-pat-full] [{"number": 42, "title": "Fix typo in README", "state": "open", ...}]
[github-pat-limited] {"error": "Bad credentials", "status": 401}
작동 방식 (How it works):
credentials_name은 MCP 서버가 수신할 저장된 자격 증명 중 무엇을 선택할지 결정해요. 생략하면 기본 자격 증명을 사용해요.- 명명된 자격 증명이 존재하지 않으면 호출은
404를 반환해요.
7. 자격 증명 삭제 (Delete Credentials)
참고 (Note): 다른 자격 증명이 존재하는 동안에는 기본 자격 증명을 삭제할 수 없어요. 먼저 다른 자격 증명을 기본으로 승격한 다음 이전 것을 삭제하세요.
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
result = await client.beta.connectors.delete_user_credentials_async(
connector_id_or_name="my_github",
credentials_name="github-pat-limited",
)
print(result.message)
asyncio.run(main())
curl:
curl -X DELETE "${BASE_URL}/v1/connectors/my_github/user/credentials/github-pat-limited" \
-H "Authorization: Bearer ${MIST...KEY}"
출력 (Output):
Credentials 'github-pat-limited' deleted successfully
| Error | Cause | Fix |
|---|---|---|
404 Not Found |
Credentials name does not exist | Check the name with list_user_credentials first |
409 Conflict |
Trying to delete the current default while others exist | Promote another credentials to default first |
Part 2: 여러 OAuth2 자격 증명 (예: Outlook Calendar MCP)
사전 준비사항 - OAuth2 (Prerequisites)
MISTRAL_API_KEY=your-mistral-api-key
outlook_calendar 커넥터가 워크스페이스에서 활성화되어 있어야 해요. Studio에서 활성화하세요.
별도로 인증할 두 개의 Microsoft 계정이 필요해요.
스크립트 (Script): python/src/scripts/08_multiple_oauth_authentication.py
여러 OAuth2 자격 증명을 쓸 시점 (When to Use Multiple OAuth2 Credentials)
- 멀티 계정 액세스 — 단일 사용자가 여러 정체성(예: 업무용과 개인용 Microsoft 계정)으로 인증하고, 호출 시점에 전환.
- 사용자별 위임 — 워크스페이스의 각 사용자가 자기 계정을 인증하고,
credentials_name이 도구 호출을 올바른 것으로 라우팅. - 안전한 교체 — 새 이름으로 새 계정을 인증하고, 기본으로 승격한 다음, 이전 것을 폐기.
1. Outlook Calendar 커넥터 가져오기 (Get the Outlook Calendar Connector)
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
connector = await client.beta.connectors.get_async(
connector_id_or_name="outlook_calendar",
)
print(f"ID: {connector.id}")
print(f"Name: {connector.name}")
asyncio.run(main())
curl:
curl -X GET "${BASE_URL}/v1/connectors/outlook_calendar" \
-H "Authorization: Bearer ${MIST...KEY}"
2. 인증 방법 가져오기 (Get Authentication Methods)
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
methods = await client.beta.connectors.get_authentication_methods_async(
connector_id_or_name="outlook_calendar",
)
for method in methods:
print(f"Auth type: {method.method_type}")
asyncio.run(main())
curl:
curl -X GET "${BASE_URL}/v1/connectors/outlook_calendar/authentication_methods" \
-H "Authorization: Bearer ${MIST...KEY}"
출력 예시 (Example output):
Auth type: oauth2
3. OAuth2로 계정 인증 (Authenticate Accounts via OAuth2)
목표 (Goal): OAuth2 승인 URL을 얻고 각 계정이 브라우저 흐름을 완료하게 해요. credentials_name 매개변수는 결과 토큰이 저장될 이름 지정 슬롯을 제어해요. 생략하면 토큰이 기본 자격 증명으로 저장돼요.
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
# Account A — stored as the default credentials
result = await client.beta.connectors.get_auth_url_async(
connector_id_or_name="outlook_calendar",
# no credentials_name => stored under name="default"
)
print(f"Follow this link to authenticate account A: {result.auth_url}")
input("Press Enter once done")
# Account B — stored under the name "personal"
result = await client.beta.connectors.get_auth_url_async(
connector_id_or_name="outlook_calendar",
credentials_name="personal",
)
print(f"Follow this link to authenticate account B: {result.auth_url}")
input("Press Enter once done")
asyncio.run(main())
curl:
# Account A — default credentials
curl -X GET "${BASE_URL}/v1/connectors/outlook_calendar/auth_url" \
-H "Authorization: Bearer ${MIST...KEY}"
# Account B — named "personal"
curl -X GET "${BASE_URL}/v1/connectors/outlook_calendar/auth_url?credentials_name=personal" \
-H "Authorization: Bearer ${MIST...KEY}"
작동 방식 (How it works):
get_auth_url은 사용자가 OAuth2 동의 흐름을 완료하기 위해 브라우저에서 열어야 하는 URL을 반환해요.- 흐름이 완료되면 토큰이 주어진
credentials_name아래(생략하면default로) 자동 저장돼요. - 스크립트는
input()으로 일시 정지해서 브라우저 흐름을 완료할 시간을 줘요.
4. 자격 증명 목록 (List Credentials)
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
response = await client.beta.connectors.list_user_credentials_async(
connector_id_or_name="outlook_calendar",
)
for cred in response.credentials:
default_marker = " (default)" if cred.is_default else ""
print(f" {cred.name} [{cred.authentication_type}]{default_marker}")
asyncio.run(main())
출력 (Output):
default [oauth2] (default)
personal [oauth2]
5. 특정 자격 증명으로 도구 호출 (Call a Tool with Specific Credentials)
목표 (Goal): 이름 있는 자격 증명을 사용해 캘린더 도구를 호출해서 특정 계정의 캘린더를 조회해요.
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
# Query the default account
result = await client.beta.connectors.call_tool_async(
connector_id_or_name="outlook_calendar",
tool_name="search_calendar_events",
arguments={"query": "meeting"},
credentials_name="default",
)
print(f"[default] {result.content[:300]}")
# Query the personal account
result = await client.beta.connectors.call_tool_async(
connector_id_or_name="outlook_calendar",
tool_name="search_calendar_events",
arguments={"query": "meeting"},
credentials_name="personal",
)
print(f"[personal] {result.content[:300]}")
asyncio.run(main())
curl:
curl -X POST "${BASE_URL}/v1/connectors/outlook_calendar/call_tool" \
-H "Authorization: Bearer ${MIST...KEY}" \
-H "Content-Type: application/json" \
-d '{"tool_name": "search_calendar_events", "arguments": {"query": "meeting"}, "credentials_name": "default"}'
6. 자격 증명을 기본으로 승격 (Promote Credentials to Default)
credentials_name이 생략됐을 때 사용할 계정을 바꾸려면, 새 토큰을 제공하지 않고 is_default만 업데이트하면 돼요 — 저장된 OAuth2 토큰은 보존됩니다.
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
await client.beta.connectors.create_or_update_user_credentials_async(
connector_id_or_name="outlook_calendar",
name="personal",
is_default=True,
)
print("Promoted 'personal' to default")
# Now call without specifying credentials — uses personal account
result = await client.beta.connectors.call_tool_async(
connector_id_or_name="outlook_calendar",
tool_name="search_calendar_events",
arguments={"query": "meeting"},
)
print(f"[default] {result.content[:300]}")
asyncio.run(main())
7. 자격 증명 삭제 (Delete Credentials)
참고 (Note): 다른 자격 증명이 존재하는 동안에는 기본 자격 증명을 삭제할 수 없어요. 먼저 다른 자격 증명을 기본으로 승격하세요.
Python:
import asyncio
from mistralai import Mistral
client = Mistral(api_key="your-api-key")
async def main() -> None:
for name in ("default", "personal"):
result = await client.beta.connectors.delete_user_credentials_async(
connector_id_or_name="outlook_calendar",
credentials_name=name,
)
print(result.message)
asyncio.run(main())
curl:
curl -X DELETE "${BASE_URL}/v1/connectors/outlook_calendar/user/credentials/personal" \
-H "Authorization: Bearer ${MIST...KEY}"
네이밍 규칙 (Naming Conventions)
| Concept | Python |
|---|---|
| Get connector | client.beta.connectors.get_async(connector_id_or_name=) |
| Get auth methods | get_authentication_methods_async(connector_id_or_name=) |
| Get OAuth2 URL | get_auth_url_async(connector_id_or_name=, credentials_name=) |
| Store bearer creds | create_or_update_user_credentials_async(connector_id_or_name=, name=, credentials={"bearer_token": ...}, is_default=) |
| Promote to default | create_or_update_user_credentials_async(connector_id_or_name=, name=, is_default=True) |
| List user creds | list_user_credentials_async(connector_id_or_name=) |
| Delete user creds | delete_user_credentials_async(connector_id_or_name=, credentials_name=) |
| Call tool | call_tool_async(connector_id_or_name=, tool_name=, arguments=, credentials_name=) |
| Scope: workspace | *_workspace_credentials* |
| Scope: organization | *_organization_credentials* |
문제 해결 (Troubleshooting)
도구 호출 시 자격 증명이 적용되지 않음 (Credentials don't take effect when calling a tool)
- 자격 증명이 저장됐는지
list_user_credentials로 확인해요. credentials_name이 저장된 이름과 정확히(대소문자 구분) 일치하는지 확인해요.credentials_name이 생략되면 기본 자격 증명이 사용돼요 —list_user_credentials로 어느 것이 기본인지 확인해요.
OAuth2 브라우저 흐름 완료 후 토큰이 저장되지 않음 (OAuth2 token not stored after completing browser flow)
- 브라우저에서 동의를 완료한 후에 Enter를 눌렀는지 확인하세요(미리 누르지 않도록).
- auth URL이 만료됐다면
get_auth_url을 다시 호출해서 새 URL을 얻으세요.
MCP 서버에서 401 Unauthorized (GitHub)
- PAT가 만료됐을 수 있어요. 같은
create_or_update_user_credentials이름으로 다시 실행해서 그 자리에서 교체해요. - PAT에 필요한 스코프가 없을 수 있어요.
기본 자격 증명을 삭제할 수 없음 (Cannot delete the default credentials)
- 먼저 다른 자격 증명을 기본으로 승격(
create_or_update_user_credentials(..., is_default=True))한 다음 이전 것을 삭제하세요.
자격 증명 관리 엔드포인트에서 403 Forbidden
- 조직 수준 자격 증명은
ModifyConnector조직 권한이 필요해요. - 워크스페이스 수준 자격 증명은
ModifyConnector워크스페이스 권한이 필요해요. - 사용자 수준 자격 증명은 인증만 필요해요.
오류 코드 참조 (Error Codes Reference)
| HTTP Status | When it occurs | What to do |
|---|---|---|
400 Bad Request |
Empty credentials object, or is_default: false on the only existing credentials |
Provide bearer_token; always keep one credentials as default |
401 Unauthorized |
Invalid Mistral API key, or the MCP server rejected the stored token | Check your MISTRAL_API_KEY; rotate the credentials |
403 Forbidden |
Insufficient permissions for the chosen scope | Use a lower scope or request the ModifyConnector permission |
404 Not Found |
Credentials name or connector does not exist | Verify names with list_user_credentials |
409 Conflict |
Connector name already taken, or deleting the active default | Rename the connector or promote a different credentials to default first |
422 Unprocessable Entity |
Invalid credentials name format | Use alphanumeric characters and hyphens only |
요약 (Summary)
이 쿡북은 단일 Connector에 대한 여러 세트의 자격 증명을 저장·관리하는 방법을 다뤘어요. Bearer 토큰(GitHub PAT)과 OAuth2(Outlook Calendar) 모두를 다루고, 런타임에 특정 도구 호출을 특정 자격 증명으로 라우팅하는 방법도 보여줬어요.
이 쿡북이 다루는 내용 (What this cookbook covers):
- GitHub MCP Connector에 여러 Bearer 토큰 자격 증명 저장
- Outlook Calendar MCP Connector에 여러 OAuth2 자격 증명 저장
- 자격 증명 나열, 선택, 삭제
- 자격 증명을 기본으로 승격
- 특정 이름 있는 자격 증명으로 도구 호출
사용한 Mistral 기능 (Mistral features used):
- Connectors API — 자격 증명 관리 (beta)
기타 서비스 (Other services):
- GitHub MCP — Bearer 인증 Connector(개인 액세스 토큰)
- Outlook Calendar MCP — OAuth2 인증 Connector
Connector를 Studio에서 확인할 수 있어요.