Skip to content

MCP 인증·권한 (Authorization)

원문 출처: Model Context Protocol 공식 문서 — Understanding Authorization in MCP

Authorization이 뭔가요?

Authorization(권한 부여) 은 MCP 서버가 노출하는 민감한 리소스와 작업에 대한 접근을 보호하는 역할을 해요. 쉽게 말해, MCP 서버가 사용자 데이터나 관리 작업을 다룬다면, 허용된 사용자만 엔드포인트에 접근할 수 있게 만들어 주는 장치인 셈이죠.

MCP는 표준화된 인증 흐름을 사용해서 MCP 클라이언트와 서버 사이에 신뢰를 만들어요. 특정한 인증·신원 시스템 하나에 집중하는 대신, OAuth 2.1 규약을 따르는 설계를 채택하고 있어요.

언제 Authorization을 써야 하나요?

다음 상황 중 하나라도 해당한다면 권한 부여가 필요해요.

  • 서버가 사용자별 데이터(이메일, 문서, 데이터베이스)에 접근할 때
  • 누가 어떤 작업을 수행했는지 감사(audit)해야 할 때
  • 사용자 동의(consent)가 필요한 API에 서버가 접근 권한을 줄 때
  • 접근 통제가 엄격한 엔터프라이즈 환경을 대상으로 개발할 때

참고로 STDIO 전송 방식을 쓰는 MCP 서버라면, 환경 변수 기반 자격 증명이나 MCP 서버에 직접 내장된 서드파티 라이브러리의 자격 증명을 사용할 수 있어요.

Authorization 흐름: 단계별로 살펴보기

인증이 어떻게 진행되는지 순서대로 따라가 볼게요. 크게 세 단계로 나뉘어요.

1단계: 초기 핸드셰이크 (Initial Handshake)

MCP 클라이언트가 처음 연결을 시도하면, 서버는 401 Unauthorized로 응답하면서 클라이언트에게 인증 정보를 어디서 찾을 수 있는지 알려줘요. 이 정보는 Protected Resource Metadata (PRM) 문서에 담겨 있어요.

PRM 문서는 MCP 서버가 호스팅하고, 예측 가능한 경로 패턴을 따르며, WWW-Authenticate 헤더의 resource_metadata 파라미터를 통해 클라이언트에 전달돼요.

즉, 이 단계에서 클라이언트는 "이 MCP 서버는 인증이 필요하고, 인증 흐름을 시작하려면 여기서 정보를 가져와야 한다" 는 사실을 알게 되는 거예요.

2단계: Protected Resource Metadata 발견 (Discovery)

PRM 문서를 가리키는 URI를 받은 클라이언트는, 인증 서버·지원되는 스코프·기타 리소스 정보를 알아내기 위해 metadata를 가져와요. 데이터는 보통 아래와 같은 JSON 블롭(blob)으로 캡슐화돼요.

{
  "resource": "https://your-server.com/mcp",
  "authorization_servers": ["https://auth.your-server.com"],
  "scopes_supported": ["mcp:tools", "mcp:resources"]
}

3단계: 인증 서버 발견 (Authorization Server Discovery)

다음으로 클라이언트는 인증 서버의 metadata를 가져와서, 그 인증 서버가 무엇을 할 수 있는지 파악해요. PRM 문서에 인증 서버가 여러 개 나열되어 있다면, 클라이언트가 그중 하나를 선택할 수 있어요.

Tip: 만약 MCP 클라이언트가 DCR(Dynamic Client Registration)을 지원하지 않는 인증 서버에 연결하고, 클라이언트가 해당 인증 서버에 사전 등록(pre-registered)되어 있지 않다면, 클라이언트 개발자가 최종 사용자가 클라이언트 정보를 수동으로 입력할 수 있는 장치(affordance)를 제공해야 할 책임이 있어요.

검증이 끝나면 클라이언트는 아래처럼 Bearer 토큰을 담아 요청을 보내요.

GET /mcp HTTP/1.1
Host: your-server.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

그러면 MCP 서버는 토큰을 검증하고, 토큰이 유효하고 필요한 권한을 갖추었을 때만 요청을 처리해요.

구현 예시 (Implementation Example)

실제 구현에 바로 들어가기 전에, Docker 컨테이너에 호스팅된 Keycloak 인증 서버를 사용해 볼게요. Keycloak은 오픈소스 인증 서버라서 로컬에 쉽게 배포해서 테스트·실험할 수 있어요.

Keycloak 설정

Keycloak 대시보드의 Client scopes로 이동해서 새 mcp:tools 스코프를 만들어요. 이 스코프를 사용해 MCP 서버의 모든 도구에 접근할 거예요.

mcp:tools 클라이언트 스코프를 열고 Mappers를 클릭한 뒤 Configure a new mapper를 선택하고 Audience를 선택하면 돼요.

토큰에 mcp:tools audience가 추가되는지, 클라이언트 등록 정보가 올바른지 확인하고 클라이언트를 새로 만들어주면 돼요. (이 과정은 Keycloak 대시보드에서 그래픽으로 진행되므로, 화면 안내를 따라가면 됩니다.)

MCP 서버 설정

이제 로컬에서 실행 중인 Keycloak 인증 서버를 사용하도록 MCP 서버를 설정해 볼게요. 선호하는 프로그래밍 언어에 따라 지원되는 MCP SDK 중 하나를 사용하면 돼요.

TypeScript

전체 TypeScript 프로젝트는 샘플 저장소에서 확인할 수 있어요.

아래 코드를 실행하기 전에, 다음 내용이 담긴 .env 파일이 있어야 해요.

# Server host/port
HOST=localhost
PORT=3000

# Auth server location
AUTH_HOST=localhost
AUTH_PORT=8080
AUTH_REALM=master

# Keycloak OAuth client credentials
OAUTH_CLIENT_ID=<YOUR_SERVER_CLIENT_ID>
OAUTH_CLIENT_SECRET=<YOUR_SERVER_CLIENT_SECRET>

OAUTH_CLIENT_IDOAUTH_CLIENT_SECRET은 앞서 만든 MCP 서버 클라이언트와 연결되는 값이에요.

서버 코드에서는 아래처럼 OAuth URL을 만들고, express 앱을 구성하며, MCP 인증 관련 미들웨어를 붙이는 흐름을 따르게 돼요.

import "dotenv/config";
import express from "express";
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
      "protocol/openid-connect/token/introspect",
      authBaseUrl,
    ).toString(),
    authorization_endpoint: new URL(
      "protocol/openid-connect/auth",
      authBaseUrl,
    ).toString(),
    token_endpoint: new URL(
      "protocol/openid-connect/token",
      authBaseUrl,
    ).toString(),
  };
}
const app = express();

app.use(
  express.json({
    verify: (req: any, _res, buf) => {
      req.rawBody = buf?.toString() ?? "";
    },
  }),
);

app.use(
  cors({
    origin: "*",
    exposedHeaders: ["Mcp-Session-Id"],
  }),
);

app.use(createRequestLogger());

토큰 검증 시에는 introspection 요청을 보내고, 응답이 정상(ok)인지 확인한 뒤 처리하는 식이에요.

          "Content-Type": "application/x-www-form-urlencoded",
        },
        body: params.toString(),
      });
    } catch (e) {
      console.error("[auth] introspection fetch threw", e);
      throw e;
    }

    if (!response.ok) {
      const txt = await response.text();

마지막으로 다음처럼 OAuth metadata 라우터와 Bearer 인증 미들웨어를 연결해요.

    };
  },
};
app.use(
  mcpAuthMetadataRouter({
    oauthMetadata,
    resourceServerUrl: mcpServerUrl,
    scopesSupported: ["mcp:tools"],
    resourceName: "MCP Demo Server",
  }),
);

const authMiddleware = requireBearerAuth({
  verifier: tokenVerifier,
  requiredScopes: [],
    `🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`,
  );
});

서버를 실행하고 나면, MCP 서버 엔드포인트를 지정해서 Visual Studio Code 같은 MCP 클라이언트에 추가할 수 있어요.

Python

Python 시나리오에서는 인증 상호작용을 단순화하기 위해 Python SDKMCPServer 클래스를 사용해요. 이 클래스는 Protected Resource Metadata 문서를 게시하고, 인증되지 않은 요청에는 해당 문서를 가리키는 WWW-Authenticate 헤더가 포함된 401로 응답하며, 모든 Bearer 토큰을 우리가 제공한 verifier에 넘겨줘요. 인증에 관한 많은 규약(엔드포인트나 토큰 검증 로직 같은)이 언어 전반에 걸쳐 일관되지만, 일부 언어는 프로덕션 통합을 더 간단하게 만들어 주는 방식도 제공해요.

실제 서버를 작성하기 전에 먼저 config.py에 설정을 구성해야 해요. 내용은 전적으로 로컬 서버 설정에 기반해요.

"""Configuration settings for the MCP auth server."""

import os

class Config:
from pydantic import AnyHttpUrl

from mcp.server import MCPServer
from mcp.server.auth.settings import AuthSettings

from .config import config
from .token_verifier import IntrospectionTokenVerifier

logger = logging.getLogger(__name__)

def create_oauth_urls() -> dict[str, str]:
    """Create OAuth URLs based on configuration (Keycloak-style)."""
    auth_base_url = config.auth_base_url

    return {
        "issuer": auth_base_url,
        "introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"),
        "authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"),
        "token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"),
    }

def create_server() -> MCPServer:
    """Create and configure the MCP server."""

    oauth_urls = create_oauth_urls()

    token_verifier = IntrospectionTokenVerifier(
        introspection_endpoint=oauth_urls["introspection_endpoint"],
        server_url=config.server_url,
        client_id=config.OAUTH_CLIENT_ID,
        client_secret=config.OAUTH_CLIENT_SECRET,
    )

    app = MCPServer(
        name="MCP Resource Server",
def main() -> int:
    """
    Run the MCP Resource Server.

    This server:
    - Provides RFC 9728 Protected Resource Metadata
    - Validates tokens via Authorization Server introspection
    - Serves MCP tools requiring authentication

    Configuration is loaded from config.py and environment variables.
    """
        return check_resource_allowed(requested_resource=self.resource_url, configured_resource=resource)

더 자세한 내용은 Python SDK 문서를 참고하면 돼요.

Python MCP 서버의 의존성은 아래처럼 구성돼요.

requires-python = ">=3.10"
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
license = { text = "MIT" }
dependencies = [
  "httpx2>=2.5.0",
  "mcp>=2.0.0rc1",
  "pydantic>=2.0",
]

[project.scripts]
mcp-simple-auth-rs = "mcp_server.server:main"

[build-system]
requires = ["hatchling"]

C

MCP C# SDK로 인증을 설정할 때는 표준 ASP.NET Core 빌더 패턴을 활용하면 돼요. Keycloak이 제공하는 introspection 엔드포인트 대신, ASP.NET Core에 내장된 토큰 검증 기능을 사용해요.

전체 C# 프로젝트는 샘플 저장소에서 확인할 수 있어요.

using ProtectedMcpServer.Tools;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

var serverUrl = "http://localhost:3000/";
var authorizationServerUrl = "http://localhost:8080/realms/master/";

builder.Services.AddAuthentication(options =>
{
    options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.Authority = authorizationServerUrl;
    var normalizedServerAudience = serverUrl.TrimEnd('/');
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidIssuer = authorizationServerUrl,
        ValidAudiences = new[] { normalizedServerAudience, serverUrl },
        AudienceValidator = (audiences, securityToken, validationParameters) =>
        {
            return false;
        }
    };

    options.RequireHttpsMetadata = false; // Set to true in production

    options.Events = new JwtBearerEvents
    {
        OnTokenValidated = context =>
        {
            var name = context.Principal?.Identity?.Name ?? "unknown";
            var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
            Console.WriteLine($"Token validated for: {name} ({email})");
            return Task.CompletedTask;
        },
        OnAuthenticationFailed = context =>
        {
            Console.WriteLine($"Authentication failed: {context.Exception.Message}");
            return Task.CompletedTask;
        },
    };
})
.AddMcp(options =>
{
    options.ResourceMetadata = new()
    {
        Resource = serverUrl,
        ResourceDocumentation = "https://docs.example.com/api/math",
        AuthorizationServers = { authorizationServerUrl },
        ScopesSupported = ["mcp:tools"]
    };
});

builder.Services.AddAuthorization();

builder.Services.AddHttpContextAccessor();
builder.Services.AddMcpServer()
    .WithTools<MathTools>()
    .WithHttpTransport();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapMcp().RequireAuthorization();

Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");

C# 프로젝트 파일에는 아래 패키지 참조가 포함돼요.

    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.18" />
    <PackageReference Include="ModelContextProtocol" Version="2.0.0" />
    <PackageReference Include="ModelContextProtocol.AspNetCore" Version="2.0.0" />

MCP 서버 테스트하기

테스트를 위해 Visual Studio Code를 사용할게요. 물론 MCP와 새 인증 사양을 지원하는 어떤 클라이언트든 사용 가능해요.

Cmd + Shift + P를 누르고 MCP: Add server... 를 선택한 뒤 HTTP를 선택하고 http://localhost:3000을 입력해요. 그리고 Visual Studio Code 안에서 사용할 고유한 서버 이름을 지정해요. 그러면 mcp.json에 아래와 같은 항목이 생겨요.

"my-mcp-server-18676652": {
  "url": "http://localhost:3000",
  "type": "http"
}

연결하면 브라우저로 이동하게 되고, Visual Studio Code가 mcp:tools 스코프에 접근하는 데 동의할지 묻는 화면이 뜨게 돼요.

흔한 함정과 피하는 방법

포괄적인 보안 지침(공격 벡터, 완화 전략, 구현 모범 사례 포함)은 Security Best Practices 문서를 꼭 읽어봐야 해요. 몇 가지 핵심 이슈만 짚어볼게요.

  • 토큰을 안전하고 암호화된 저장소에 보관하세요. 특정 시나리오에서는 서버 측에서 토큰을 캐시해야 할 수도 있어요.
  • 클라이언트로부터 받은 값이 요구되는 제약 조건과 일치하는지 항상 검증하세요.
  • Mcp-Session-Id신뢰할 수 없는 입력으로 취급하고, 여기에 인증을 절대 연결하지 마세요. 인증이 바뀌면 재생성하고, 수명 주기를 서버 측에서 검증하세요.

관련 표준과 문서

  • OAuth 2.1: 핵심 인증 프레임워크
  • RFC 8414: Authorization Server Metadata 발견
  • RFC 7591: 동적 클라이언트 등록 (Dynamic Client Registration)
  • RFC 9728: Protected Resource Metadata
  • RFC 8707: Resource Indicators

추가로 참고할 문서는 아래와 같아요.

  • Authorization Specification
  • Security Best Practices
  • Available MCP SDKs

이 표준들을 이해해 두면 인증을 올바르게 구현하고, 문제가 생겼을 때도 더 쉽게 해결할 수 있어요.