여러 인증 방법

여러 인증 방법 (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에서 확인할 수 있어요.

더 알아보기 (Learn more)