Securing your Model Context Protocol (MCP) server is as important as locking the front door of your house.
Leaving your MCP server open exposes your tools and data to unauthorized access, which can lead to security breaches.
Microsoft Entra ID provides a robust cloud-based identity and access management solution, helping ensure that only authorized users and applications can interact with your MCP server.
In this section, youβll learn how to protect your AI workflows using Entra ID authentication.
By the end of this section, you will be able to:
Just as you wouldn't leave the front door of your house unlocked, you shouldn't leave your MCP server open for anyone to access.
Securing your AI workflows is essential for building robust, trustworthy, and safe applications.
This chapter will introduce you to using Microsoft Entra ID to secure your MCP servers, ensuring that only authorized users and applications can interact with your tools and data.
Imagine your MCP server has a tool that can send emails or access a customer database. An unsecured server would mean anyone could potentially use that tool, leading to unauthorized data access, spam, or other malicious activities.
By implementing authentication, you ensure that every request to your server is verified, confirming the identity of the user or application making the request. This is the first and most critical step in securing your AI workflows.
By using Entra ID, you can:
For MCP servers, Entra ID provides a robust and widely-trusted solution to manage who can access your server's capabilities.
---
Entra ID uses open standards like OAuth 2.0 to handle authentication. While the details can be complex, the core concept is simple and can be understood with an analogy.
Think of OAuth 2.0 like a valet service for your car.
When you arrive at a restaurant, you don't give the valet your master key.
Instead, you provide a valet key that has limited permissionsβit can start the car and lock the doors, but it can't open the trunk or the glove compartment.
In this analogy:
The access token is a secure string of text that the MCP client receives from Entra ID after you sign in.
The client then presents this token to the MCP server with every request.
The server can verify the token to ensure the request is legitimate and that the client has the necessary permissions, all without ever needing to handle your actual credentials (like your password).
Hereβs how the process works in practice:
sequenceDiagram
actor User as π€ User
participant Client as π₯οΈ MCP Client
participant Entra as π Microsoft Entra ID
participant Server as π§ MCP Server
Client->>+User: Please sign in to continue.
User->>+Entra: Enters credentials (username/password).
Entra-->>Client: Here is your access token.
User-->>-Client: (Returns to the application)
Client->>+Server: I need to use a tool. Here is my access token.
Server->>+Entra: Is this access token valid?
Entra-->>-Server: Yes, it is.
Server-->>-Client: Token is valid. Here is the result of the tool.
Before we dive into the code, it's important to introduce a key component you'll see in the examples: the Microsoft Authentication Library (MSAL).
MSAL is a library developed by Microsoft that makes it much easier for developers to handle authentication.
Instead of you having to write all the complex code to handle security tokens, manage sign-ins, and refresh sessions, MSAL takes care of the heavy lifting.
Using a library like MSAL is highly recommended because:
MSAL supports a wide variety of languages and application frameworks, including .NET, JavaScript/TypeScript, Python, Java, Go, and mobile platforms like iOS and Android.
This means you can use the same consistent authentication patterns across your entire technology stack.
To learn more about MSAL, you can check out the official MSAL overview documentation.
---
Now, let's walk through how to secure a local MCP server (one that communicates over stdio) using Entra ID.
This example uses a public client, which is suitable for applications running on a user's machine, like a desktop app or a local development server.
In this scenario, we'll look at an MCP server that runs locally, communicates over stdio, and uses Entra ID to authenticate the user before allowing access to its tools.
The server will have a single tool that fetches the user's profile information from the Microsoft Graph API.
Before writing any code, you need to register your application in Microsoft Entra ID. This tells Entra ID about your application and grants it permission to use the authentication service.
1. Navigate to the Microsoft Entra portal.
2. Go to App registrations and click New registration.
3. Give your application a name (e.g., "My Local MCP Server").
4. For Supported account types, select Accounts in this organizational directory only.
5. You can leave the Redirect URI blank for this example.
6. Click Register.
Once registered, take note of the Application (client) ID and Directory (tenant) ID. You'll need these in your code.
Let's look at the key parts of the code that handle authentication.
The full code for this example is available in the Entra ID - Local - WAM folder of the mcp-auth-servers GitHub repository.
AuthenticationService.cs
This class is responsible for handling the interaction with Entra ID.
CreateAsync: This method initializes the PublicClientApplication from the MSAL (Microsoft Authentication Library). It's configured with your application's clientId and tenantId.WithBroker: This enables the use of a broker (like the Windows Web Account Manager), which provides a more secure and seamless single sign-on experience.AcquireTokenAsync: This is the core method. It first tries to get a token silently (meaning the user won't have to sign in again if they already have a valid session). If a silent token can't be acquired, it will prompt the user to sign in interactively.
// Simplified for clarity
public static async Task<AuthenticationService> CreateAsync(ILogger<AuthenticationService> logger)
{
var msalClient = PublicClientApplicationBuilder
.Create(_clientId) // Your Application (client) ID
.WithAuthority(AadAuthorityAudience.AzureAdMyOrg)
.WithTenantId(_tenantId) // Your Directory (tenant) ID
.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows))
.Build();
// ... cache registration ...
return new AuthenticationService(logger, msalClient);
}
public async Task<string> AcquireTokenAsync()
{
try
{
// Try silent authentication first
var accounts = await _msalClient.GetAccountsAsync();
var account = accounts.FirstOrDefault();
AuthenticationResult? result = null;
if (account != null)
{
result = await _msalClient.AcquireTokenSilent(_scopes, account).ExecuteAsync();
}
else
{
// If no account, or silent fails, go interactive
result = await _msalClient.AcquireTokenInteractive(_scopes).ExecuteAsync();
}
return result.AccessToken;
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while acquiring the token.");
throw; // Optionally rethrow the exception for higher-level handling
}
}
Program.cs
This is where the MCP server is set up and the authentication service is integrated.
AddSingleton: This registers the AuthenticationService with the dependency injection container, so it can be used by other parts of the application (like our tool).GetUserDetailsFromGraph tool: This tool requires an instance of AuthenticationService. Before it does anything, it calls authService.AcquireTokenAsync() to get a valid access token. If authentication is successful, it uses the token to call the Microsoft Graph API and fetch the user's details.
// Simplified for clarity
[McpServerTool(Name = "GetUserDetailsFromGraph")]
public static async Task<string> GetUserDetailsFromGraph(
AuthenticationService authService)
{
try
{
// This will trigger the authentication flow
var accessToken = await authService.AcquireTokenAsync();
// Use the token to create a GraphServiceClient
var graphClient = new GraphServiceClient(
new BaseBearerTokenAuthenticationProvider(new TokenProvider(authService)));
var user = await graphClient.Me.GetAsync();
return System.Text.Json.JsonSerializer.Serialize(user);
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
1.
When the MCP client tries to use the GetUserDetailsFromGraph tool, the tool first calls AcquireTokenAsync.
2. AcquireTokenAsync triggers the MSAL library to check for a valid token.
3. If no token is found, MSAL, through the broker, will prompt the user to sign in with their Entra ID account.
4. Once the user signs in, Entra ID issues an access token.
5. The tool receives the token and uses it to make a secure call to the Microsoft Graph API.
6. The user's details are returned to the MCP client.
This process ensures that only authenticated users can use the tool, effectively securing your local MCP server.
When your MCP server is running on a remote machine (like a cloud server) and communicates over a protocol like HTTP Streaming, the security requirements are different.
In this case, you should use a confidential client and the Authorization Code Flow.
This is a more secure method because the application's secrets are never exposed to the browser.
This example uses a TypeScript-based MCP server that uses Express.js to handle HTTP requests.
The setup in Entra ID is similar to the public client, but with one key difference: you need to create a client secret.
1. Navigate to the Microsoft Entra portal.
2. In your app registration, go to the Certificates & secrets tab.
3. Click New client secret, give it a description, and click Add.
4. Important: Copy the secret value immediately. You will not be able to see it again.
5.
You also need to configure a Redirect URI.
Go to the Authentication tab, click Add a platform, select Web, and enter the redirect URI for your application (e.g., http://localhost:3001/auth/callback).
> β οΈ Important Security Note: For production applications, Microsoft strongly recommends using secretless authentication methods such as Managed Identity or Workload Identity Federation instead of client secrets.
Client secrets pose security risks as they can be exposed or compromised.
Managed identities provide a more secure approach by eliminating the need to store credentials in your code or configuration.
>
> For more information about managed identities and how to implement them, see the Managed identities for Azure resources overview.
This example uses a session-based approach.
When the user authenticates, the server stores the access token and refresh token in a session and gives the user a session token.
This session token is then used for subsequent requests.
The full code for this example is available in the Entra ID - Confidential client folder of the mcp-auth-servers GitHub repository.
Server.ts
This file sets up the Express server and the MCP transport layer.
requireBearerAuth: This is middleware that protects the /sse and /message endpoints. It checks for a valid bearer token in the Authorization header of the request.EntraIdServerAuthProvider: This is a custom class that implements the McpServerAuthorizationProvider interface. It's responsible for handling the OAuth 2.0 flow./auth/callback: This endpoint handles the redirect from Entra ID after the user has authenticated. It exchanges the authorization code for an access token and a refresh token.
// Simplified for clarity
const app = express();
const { server } = createServer();
const provider = new EntraIdServerAuthProvider();
// Protect the SSE endpoint
app.get("/sse", requireBearerAuth({
provider,
requiredScopes: ["User.Read"]
}), async (req, res) => {
// ... connect to the transport ...
});
// Protect the message endpoint
app.post("/message", requireBearerAuth({
provider,
requiredScopes: ["User.Read"]
}), async (req, res) => {
// ... handle the message ...
});
// Handle the OAuth 2.0 callback
app.get("/auth/callback", (req, res) => {
provider.handleCallback(req.query.code, req.query.state)
.then(result => {
// ... handle success or failure ...
});
});
Tools.ts
This file defines the tools that the MCP server provides.
The getUserDetails tool is similar to the one in the previous example, but it gets the access token from the session.
// Simplified for clarity
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name } = request.params;
const context = request.params?.context as { token?: string } | undefined;
const sessionToken = context?.token;
if (name === ToolName.GET_USER_DETAILS) {
if (!sessionToken) {
throw new AuthenticationError("Authentication token is missing or invalid. Ensure the token is provided in the request context.");
}
// Get the Entra ID token from the session store
const tokenData = tokenStore.getToken(sessionToken);
const entraIdToken = tokenData.accessToken;
const graphClient = Client.init({
authProvider: (done) => {
done(null, entraIdToken);
}
});
const user = await graphClient.api('/me').get();
// ... return user details ...
}
});
auth/EntraIdServerAuthProvider.ts
This class handles the logic for:
tokenStore.1.
When a user first tries to connect to the MCP server, the requireBearerAuth middleware will see that they don't have a valid session and will redirect them to the Entra ID sign-in page.
2. The user signs in with their Entra ID account.
3. Entra ID redirects the user back to the /auth/callback endpoint with an authorization code.
4. The server exchanges the code for an access token and a refresh token, stores them, and creates a session token which is sent to the client.
5. The client can now use this session token in the Authorization header for all future requests to the MCP server.
6.
When the getUserDetails tool is called, it uses the session token to look up the Entra ID access token and then uses that to call the Microsoft Graph API.
This flow is more complex than the public client flow, but is required for internet-facing endpoints.
Since remote MCP servers are accessible over the public internet, they need stronger security measures to protect against unauthorized access and potential attacks.
1. Think about an MCP server you might build. Would it be a local server or a remote server?
2. Based on your answer, would you use a public or confidential client?
3. What permission would your MCP server request for performing actions against Microsoft Graph?
Navigate to the Microsoft Entra portal.
Register a new application for your MCP server.
Record the Application (client) ID and Directory (tenant) ID.
1. MSAL Overview Documentation
Learn how the Microsoft Authentication Library (MSAL) enables secure token acquisition across platforms:
MSAL Overview on Microsoft Learn
2. Azure-Samples/mcp-auth-servers GitHub Repository
Reference implementations of MCP servers demonstrating authentication flows:
Azure-Samples/mcp-auth-servers on GitHub
3. Managed Identities for Azure Resources Overview
Understand how to eliminate secrets by using system- or user-assigned managed identities:
Managed Identities Overview on Microsoft Learn
4. Azure API Management: Your Auth Gateway for MCP Servers
A deep dive into using APIM as a secure OAuth2 gateway for MCP servers:
Azure API Management Your Auth Gateway For MCP Servers
5. Microsoft Graph Permissions Reference
Comprehensive list of delegated and application permissions for Microsoft Graph:
Microsoft Graph Permissions Reference
After completing this section, you will be able to:
λͺ¨λΈ 컨ν μ€νΈ νλ‘ν μ½(MCP) μλ²λ₯Ό 보νΈνλ κ²μ μ§μ νκ΄λ¬Έμ μ κ·Έλ κ²λ§νΌ μ€μν©λλ€.
MCP μλ²λ₯Ό μ΄μ΄λλ©΄ λꡬμ λ°μ΄ν°κ° λ¬΄λ¨ μ κ·Όμ λ ΈμΆλμ΄ λ³΄μ μ¬κ³ λ‘ μ΄μ΄μ§ μ μμ΅λλ€.
Microsoft Entra IDλ κ°λ ₯ν ν΄λΌμ°λ κΈ°λ° μμ΄λ΄ν°ν° λ° μ κ·Ό κ΄λ¦¬ μ루μ μ μ 곡νμ¬, κΆνμ΄ μλ μ¬μ©μμ μ ν리μΌμ΄μ λ§ MCP μλ²μ μνΈμμ©ν μ μλλ‘ λμμ€λλ€.
μ΄ μΉμ μμλ Entra ID μΈμ¦μ μ¬μ©ν΄ AI μν¬νλ‘μ°λ₯Ό 보νΈνλ λ°©λ²μ λ°°μλλ€.
μ΄ μΉμ μ λ§μΉλ©΄ λ€μμ ν μ μμ΅λλ€:
μ§μ νκ΄λ¬Έμ μ κ·Έμ§ μκ³ λμ§ μλ κ²μ²λΌ, MCP μλ²λ λꡬλ μ κ·Όν μ μλλ‘ μ΄μ΄λλ©΄ μ λ©λλ€. AI μν¬νλ‘μ°λ₯Ό μμ νκ² λ³΄νΈνλ κ²μ κ²¬κ³ νκ³ μ λ’°ν μ μμΌλ©° μμ ν μ ν리μΌμ΄μ μ λ§λλ λ° νμμ μ λλ€. μ΄ μ₯μμλ Microsoft Entra IDλ₯Ό μ¬μ©ν΄ MCP μλ²λ₯Ό 보νΈνλ λ°©λ²μ μκ°νλ©°, κΆνμ΄ μλ μ¬μ©μμ μ ν리μΌμ΄μ λ§ λꡬμ λ°μ΄ν°μ μ κ·Όν μ μλλ‘ ν©λλ€.
MCP μλ²μ μ΄λ©μΌμ 보λ΄κ±°λ κ³ κ° λ°μ΄ν°λ² μ΄μ€μ μ κ·Όν μ μλ λκ΅¬κ° μλ€κ³ κ°μ ν΄ λ³΄μΈμ. 보μμ΄ μ·¨μ½ν μλ²λΌλ©΄ λꡬλ κ·Έ λꡬλ₯Ό μ¬μ©ν μ μμ΄ λ¬΄λ¨ λ°μ΄ν° μ κ·Ό, μ€νΈ λ°μ‘, κΈ°ν μ μμ νμκ° λ°μν μ μμ΅λλ€.
μΈμ¦μ ꡬννλ©΄ μλ²μ λν λͺ¨λ μμ²μ΄ κ²μ¦λμ΄ μμ²μ νλ μ¬μ©μλ μ ν리μΌμ΄μ μ μ μμ νμΈν μ μμ΅λλ€. μ΄λ AI μν¬νλ‘μ° λ³΄μμ 첫 λ²μ§Έμ΄μ κ°μ₯ μ€μν λ¨κ³μ λλ€.
Entra IDλ₯Ό μ¬μ©νλ©΄ λ€μμ΄ κ°λ₯ν©λλ€:
MCP μλ²μ κ²½μ°, Entra IDλ μλ² κΈ°λ₯μ μ κ·Όν μ μλ μ¬μ©μλ₯Ό κ΄λ¦¬νλ κ°λ ₯νκ³ μ λ’°λ°λ μ루μ μ μ 곡ν©λλ€.
---
Entra IDλ OAuth 2.0 κ°μ μ€ν νμ€μ μ¬μ©ν΄ μΈμ¦μ μ²λ¦¬ν©λλ€. μΈλΆ μ¬νμ 볡μ‘ν μ μμ§λ§, ν΅μ¬ κ°λ μ λΉμ λ₯Ό ν΅ν΄ μ½κ² μ΄ν΄ν μ μμ΅λλ€.
OAuth 2.0μ μλμ°¨ λ°λ μλΉμ€μ λΉμ ν΄ λ³΄μΈμ. μλΉμ λμ°©νμ λ, λ§μ€ν° ν€λ₯Ό λ°λ μκ² μ£Όμ§ μκ³ μ νλ κΆνλ§ κ°μ§ λ°λ ν€λ₯Ό μ€λλ€. μ΄ ν€λ μ°¨λ₯Ό μλ κ±Έκ³ λ¬Έμ μ κΈ μ μμ§λ§, νΈλ ν¬λ κΈλ¬λΈ λ°μ€λ μ΄ μ μμ΅λλ€.
μ΄ λΉμ μμ:
μ‘μΈμ€ ν ν°μ μ¬μ©μκ° λ‘κ·ΈμΈν ν MCP ν΄λΌμ΄μΈνΈκ° Entra IDλ‘λΆν° λ°λ μμ ν λ¬Έμμ΄μ λλ€. ν΄λΌμ΄μΈνΈλ μ΄ ν ν°μ λ§€ μμ² μ MCP μλ²μ μ μνλ©°, μλ²λ ν ν°μ κ²μ¦ν΄ μμ²μ΄ ν©λ²μ μ΄κ³ νμν κΆνμ΄ μλμ§ νμΈν©λλ€. μ΄ κ³Όμ μμ μ€μ μ¬μ©μ μ격 μ¦λͺ (μ: λΉλ°λ²νΈ)μ λ€λ£° νμκ° μμ΅λλ€.
μ€μ κ³Όμ μ λ€μκ³Ό κ°μ΅λλ€:
sequenceDiagram
actor User as π€ User
participant Client as π₯οΈ MCP Client
participant Entra as π Microsoft Entra ID
participant Server as π§ MCP Server
Client->>+User: Please sign in to continue.
User->>+Entra: Enters credentials (username/password).
Entra-->>Client: Here is your access token.
User-->>-Client: (Returns to the application)
Client->>+Server: I need to use a tool. Here is my access token.
Server->>+Entra: Is this access token valid?
Entra-->>-Server: Yes, it is.
Server-->>-Client: Token is valid. Here is the result of the tool.
μ½λ μμ λ₯Ό μ΄ν΄λ³΄κΈ° μ μ μ€μν κ΅¬μ± μμμΈ Microsoft μΈμ¦ λΌμ΄λΈλ¬λ¦¬(MSAL)λ₯Ό μκ°ν©λλ€.
MSALμ κ°λ°μκ° μΈμ¦μ μ½κ² μ²λ¦¬ν μ μλλ‘ Microsoftμμ λ§λ λΌμ΄λΈλ¬λ¦¬μ λλ€. 볡μ‘ν 보μ ν ν° κ΄λ¦¬, λ‘κ·ΈμΈ μ²λ¦¬, μΈμ κ°±μ μ½λλ₯Ό μ§μ μμ±ν νμ μμ΄ MSALμ΄ μ΄λ₯Ό λμ μ²λ¦¬ν©λλ€.
MSAL μ¬μ©μ κΆμ₯νλ μ΄μ λ:
MSALμ .NET, JavaScript/TypeScript, Python, Java, Go, iOS, Android λ± λ€μν μΈμ΄μ νλ μμν¬λ₯Ό μ§μν΄ μ 체 κΈ°μ μ€νμμ μΌκ΄λ μΈμ¦ ν¨ν΄μ μ¬μ©ν μ μμ΅λλ€.
MSALμ λν΄ λ μκ³ μΆλ€λ©΄ 곡μ MSAL κ°μ λ¬Έμλ₯Ό μ°Έκ³ νμΈμ.
---
μ΄μ Entra IDλ₯Ό μ¬μ©ν΄ λ‘컬 MCP μλ²(stdio ν΅μ )λ₯Ό 보νΈνλ λ°©λ²μ μ΄ν΄λ³΄κ² μ΅λλ€. μ΄ μμ λ μ¬μ©μμ μ»΄ν¨ν°μμ μ€νλλ λ°μ€ν¬ν± μ±μ΄λ λ‘컬 κ°λ° μλ²μ μ ν©ν κ³΅κ° ν΄λΌμ΄μΈνΈλ₯Ό μ¬μ©ν©λλ€.
μ΄ μλ리μ€μμλ λ‘컬μμ μ€νλκ³ stdioλ‘ ν΅μ νλ MCP μλ²κ° Entra IDλ‘ μ¬μ©μλ₯Ό μΈμ¦ν ν λꡬ μ κ·Όμ νμ©νλ κ³Όμ μ λ€λ£Ήλλ€. μλ²μλ Microsoft Graph APIμμ μ¬μ©μ νλ‘ν μ 보λ₯Ό κ°μ Έμ€λ λ¨μΌ λκ΅¬κ° μμ΅λλ€.
μ½λλ₯Ό μμ±νκΈ° μ μ Microsoft Entra IDμ μ ν리μΌμ΄μ μ λ±λ‘ν΄μΌ ν©λλ€. μ΄λ Entra IDμ μ ν리μΌμ΄μ μ 보λ₯Ό μλ € μΈμ¦ μλΉμ€λ₯Ό μ¬μ©ν κΆνμ λΆμ¬νλ κ³Όμ μ λλ€.
1. Microsoft Entra ν¬νΈμ μ μν©λλ€.
2. μ± λ±λ‘(App registrations)μΌλ‘ μ΄λν΄ μ λ±λ‘(New registration)μ ν΄λ¦ν©λλ€.
3. μ ν리μΌμ΄μ μ΄λ¦(μ: "My Local MCP Server")μ μ λ ₯ν©λλ€.
4. μ§μλλ κ³μ μ ν(Supported account types)μμ μ΄ μ‘°μ§ λλ ν°λ¦¬μ κ³μ λ§(Accounts in this organizational directory only)μ μ νν©λλ€.
5. μ΄ μμ μμλ 리λλ μ URI(Redirect URI)λ₯Ό λΉμλ‘λλ€.
6. λ±λ‘(Register)μ ν΄λ¦ν©λλ€.
λ±λ‘ ν μ ν리μΌμ΄μ (ν΄λΌμ΄μΈνΈ) IDμ λλ ν°λ¦¬(ν λνΈ) IDλ₯Ό κΈ°λ‘ν΄ λμΈμ. μ½λμμ νμν©λλ€.
μΈμ¦μ μ²λ¦¬νλ ν΅μ¬ μ½λλ₯Ό μ΄ν΄λ³΄κ² μ΅λλ€.
μ 체 μ½λλ mcp-auth-servers GitHub μ μ₯μμ Entra ID - Local - WAM ν΄λμμ νμΈν μ μμ΅λλ€.
AuthenticationService.cs
μ΄ ν΄λμ€λ Entra IDμμ μνΈμμ©μ λ΄λΉν©λλ€.
CreateAsync: MSALμ PublicClientApplicationμ μ΄κΈ°νν©λλ€. μ ν리μΌμ΄μ
μ clientIdμ tenantIdλ‘ κ΅¬μ±λ©λλ€.WithBroker: Windows Web Account Manager κ°μ λΈλ‘컀 μ¬μ©μ νμ±νν΄ λ μμ νκ³ μνν μ±κΈ μ¬μΈμ¨ κ²½νμ μ 곡ν©λλ€.AcquireTokenAsync: ν΅μ¬ λ©μλλ‘, λ¨Όμ μ‘°μ©ν ν ν°μ μ»μΌλ € μλν©λλ€(μ΄λ―Έ μ ν¨ν μΈμ
μ΄ μμΌλ©΄ λ‘κ·ΈμΈ κ³Όμ μμ΄ ν ν° νλ). μ€ν¨νλ©΄ μ¬μ©μμκ² λ‘κ·ΈμΈ μ°½μ λμ μΈμ¦μ μ§νν©λλ€.
// Simplified for clarity
public static async Task<AuthenticationService> CreateAsync(ILogger<AuthenticationService> logger)
{
var msalClient = PublicClientApplicationBuilder
.Create(_clientId) // Your Application (client) ID
.WithAuthority(AadAuthorityAudience.AzureAdMyOrg)
.WithTenantId(_tenantId) // Your Directory (tenant) ID
.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows))
.Build();
// ... cache registration ...
return new AuthenticationService(logger, msalClient);
}
public async Task<string> AcquireTokenAsync()
{
try
{
// Try silent authentication first
var accounts = await _msalClient.GetAccountsAsync();
var account = accounts.FirstOrDefault();
AuthenticationResult? result = null;
if (account != null)
{
result = await _msalClient.AcquireTokenSilent(_scopes, account).ExecuteAsync();
}
else
{
// If no account, or silent fails, go interactive
result = await _msalClient.AcquireTokenInteractive(_scopes).ExecuteAsync();
}
return result.AccessToken;
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while acquiring the token.");
throw; // Optionally rethrow the exception for higher-level handling
}
}
Program.cs
MCP μλ²λ₯Ό μ€μ νκ³ μΈμ¦ μλΉμ€λ₯Ό ν΅ν©νλ λΆλΆμ λλ€.
AddSingleton: AuthenticationServiceλ₯Ό μμ‘΄μ± μ£Όμ
컨ν
μ΄λμ λ±λ‘ν΄ λ€λ₯Έ λΆλΆ(μ: λꡬ)μμ μ¬μ©ν μ μκ² ν©λλ€.GetUserDetailsFromGraph λꡬ: μ΄ λꡬλ AuthenticationService μΈμ€ν΄μ€λ₯Ό νμλ‘ ν©λλ€. μ€ν μ μ authService.AcquireTokenAsync()λ₯Ό νΈμΆν΄ μ ν¨ν μ‘μΈμ€ ν ν°μ μ»μ΅λλ€. μΈμ¦μ μ±κ³΅νλ©΄ ν ν°μ μ¬μ©ν΄ Microsoft Graph APIλ₯Ό νΈμΆν΄ μ¬μ©μ μ 보λ₯Ό κ°μ Έμ΅λλ€.
// Simplified for clarity
[McpServerTool(Name = "GetUserDetailsFromGraph")]
public static async Task<string> GetUserDetailsFromGraph(
AuthenticationService authService)
{
try
{
// This will trigger the authentication flow
var accessToken = await authService.AcquireTokenAsync();
// Use the token to create a GraphServiceClient
var graphClient = new GraphServiceClient(
new BaseBearerTokenAuthenticationProvider(new TokenProvider(authService)));
var user = await graphClient.Me.GetAsync();
return System.Text.Json.JsonSerializer.Serialize(user);
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
1.
MCP ν΄λΌμ΄μΈνΈκ° GetUserDetailsFromGraph λꡬλ₯Ό μ¬μ©νλ € ν λ, λꡬλ λ¨Όμ AcquireTokenAsyncλ₯Ό νΈμΆν©λλ€.
2. AcquireTokenAsyncλ MSAL λΌμ΄λΈλ¬λ¦¬λ₯Ό ν΅ν΄ μ ν¨ν ν ν°μ΄ μλμ§ νμΈν©λλ€.
3. ν ν°μ΄ μμΌλ©΄ MSALμ΄ λΈλ‘컀λ₯Ό ν΅ν΄ μ¬μ©μμκ² Entra ID κ³μ μΌλ‘ λ‘κ·ΈμΈνλΌλ μ°½μ λμλλ€.
4. μ¬μ©μκ° λ‘κ·ΈμΈνλ©΄ Entra IDκ° μ‘μΈμ€ ν ν°μ λ°κΈν©λλ€.
5. λꡬλ ν ν°μ λ°μ Microsoft Graph APIμ μμ νκ² μμ²μ 보λ λλ€.
6. μ¬μ©μ μ λ³΄κ° MCP ν΄λΌμ΄μΈνΈμ λ°νλ©λλ€.
μ΄ κ³Όμ μΌλ‘ μΈμ¦λ μ¬μ©μλ§ λꡬλ₯Ό μ¬μ©ν μ μμ΄ λ‘컬 MCP μλ²κ° μμ νκ² λ³΄νΈλ©λλ€.
MCP μλ²κ° μ격 λ¨Έμ (μ: ν΄λΌμ°λ μλ²)μμ μ€νλκ³ HTTP μ€νΈλ¦¬λ° κ°μ νλ‘ν μ½λ‘ ν΅μ ν λλ 보μ μꡬμ¬νμ΄ λ€λ¦ λλ€. μ΄ κ²½μ° κΈ°λ° ν΄λΌμ΄μΈνΈμ Authorization Code Flowλ₯Ό μ¬μ©ν΄μΌ ν©λλ€. μ΄ λ°©λ²μ μ ν리μΌμ΄μ λΉλ°μ΄ λΈλΌμ°μ μ λ ΈμΆλμ§ μμ λ μμ ν©λλ€.
μ΄ μμ λ Express.jsλ₯Ό μ¬μ©ν΄ HTTP μμ²μ μ²λ¦¬νλ TypeScript κΈ°λ° MCP μλ²λ₯Ό λ€λ£Ήλλ€.
μ€μ μ κ³΅κ° ν΄λΌμ΄μΈνΈμ λΉμ·νμ§λ§, ν΄λΌμ΄μΈνΈ λΉλ°(client secret)μ μμ±ν΄μΌ νλ€λ μ μ΄ λ€λ¦ λλ€.
1. Microsoft Entra ν¬νΈμ μ μν©λλ€.
2. μ± λ±λ‘μμ μΈμ¦μ λ° λΉλ°(Certificates & secrets) νμΌλ‘ μ΄λν©λλ€.
3. μ ν΄λΌμ΄μΈνΈ λΉλ°(New client secret)μ ν΄λ¦νκ³ μ€λͺ μ μ λ ₯ν ν μΆκ°(Add)λ₯Ό ν΄λ¦ν©λλ€.
4. μ€μ: μμ±λ λΉλ° κ°μ μ¦μ 볡μ¬νμΈμ. λ€μ λ³Ό μ μμ΅λλ€.
5. 리λλ μ
URIλ μ€μ ν΄μΌ ν©λλ€. μΈμ¦(Authentication) νμμ νλ«νΌ μΆκ°(Add a platform)λ₯Ό ν΄λ¦νκ³ μΉ(Web)μ μ νν λ€ μ ν리μΌμ΄μ
μ 리λλ μ
URI(μ: http://localhost:3001/auth/callback)λ₯Ό μ
λ ₯ν©λλ€.
> β οΈ μ€μν 보μ μ°Έκ³ : μ΄μ νκ²½μμλ ν΄λΌμ΄μΈνΈ λΉλ° λμ Managed Identityλ Workload Identity Federation κ°μ λΉλ° μλ μΈμ¦ λ°©μμ μ¬μ©νλ κ²μ Microsoftκ° κ°λ ₯ν κΆμ₯ν©λλ€.
ν΄λΌμ΄μΈνΈ λΉλ°μ λ ΈμΆλκ±°λ νμ·¨λ μνμ΄ μμ΅λλ€.
κ΄λ¦¬ν μμ΄λ΄ν°ν°λ μ½λλ μ€μ μ μ격 μ¦λͺ μ μ μ₯ν νμκ° μμ΄ λ μμ ν©λλ€.
>
> κ΄λ¦¬ν μμ΄λ΄ν°ν°μ λν μμΈν λ΄μ©κ³Ό ꡬν λ°©λ²μ Azure 리μμ€μ© κ΄λ¦¬ν μμ΄λ΄ν°ν° κ°μλ₯Ό μ°Έκ³ νμΈμ.
μ΄ μμ λ μΈμ κΈ°λ° λ°©μμ μ¬μ©ν©λλ€.
μ¬μ©μκ° μΈμ¦νλ©΄ μλ²κ° μ‘μΈμ€ ν ν°κ³Ό κ°±μ ν ν°μ μΈμ μ μ μ₯νκ³ , μ¬μ©μμκ² μΈμ ν ν°μ μ 곡ν©λλ€.
μ΄ν μμ²μ μ΄ μΈμ ν ν°μ μ¬μ©ν©λλ€.
μ 체 μ½λλ mcp-auth-servers GitHub μ μ₯μμ Entra ID - Confidential client ν΄λμμ νμΈν μ μμ΅λλ€.
Server.ts
Express μλ²μ MCP μ μ‘ κ³μΈ΅μ μ€μ ν©λλ€.
requireBearerAuth: /sseμ /message μλν¬μΈνΈλ₯Ό 보νΈνλ λ―Έλ€μ¨μ΄μ
λλ€. μμ²μ Authorization ν€λμ μ ν¨ν λ² μ΄λ¬ ν ν°μ΄ μλμ§ νμΈν©λλ€.EntraIdServerAuthProvider: McpServerAuthorizationProvider μΈν°νμ΄μ€λ₯Ό ꡬνν 컀μ€ν
ν΄λμ€μ
λλ€. OAuth 2.0 νλ¦μ μ²λ¦¬ν©λλ€./auth/callback: μ¬μ©μκ° μΈμ¦ ν Entra IDμμ 리λλ μ
λ λ νΈμΆλλ μλν¬μΈνΈμ
λλ€. κΆν μ½λλ₯Ό μ‘μΈμ€ ν ν°κ³Ό κ°±μ ν ν°μΌλ‘ κ΅νν©λλ€.
// Simplified for clarity
const app = express();
const { server } = createServer();
const provider = new EntraIdServerAuthProvider();
// Protect the SSE endpoint
app.get("/sse", requireBearerAuth({
provider,
requiredScopes: ["User.Read"]
}), async (req, res) => {
// ... connect to the transport ...
});
// Protect the message endpoint
app.post("/message", requireBearerAuth({
provider,
requiredScopes: ["User.Read"]
}), async (req, res) => {
// ... handle the message ...
});
// Handle the OAuth 2.0 callback
app.get("/auth/callback", (req, res) => {
provider.handleCallback(req.query.code, req.query.state)
.then(result => {
// ... handle success or failure ...
});
});
Tools.ts
MCP μλ²κ° μ 곡νλ λꡬλ€μ μ μν©λλ€. getUserDetails λꡬλ μ΄μ μμ μ λΉμ·νμ§λ§, μ‘μΈμ€ ν ν°μ μΈμ
μμ κ°μ Έμ΅λλ€.
// Simplified for clarity
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name } = request.params;
const context = request.params?.context as { token?: string } | undefined;
const sessionToken = context?.token;
if (name === ToolName.GET_USER_DETAILS) {
if (!sessionToken) {
throw new AuthenticationError("Authentication token is missing or invalid. Ensure the token is provided in the request context.");
}
// Get the Entra ID token from the session store
const tokenData = tokenStore.getToken(sessionToken);
const entraIdToken = tokenData.accessToken;
const graphClient = Client.init({
authProvider: (done) => {
done(null, entraIdToken);
}
});
const user = await graphClient.api('/me').get();
// ... return user details ...
}
});
auth/EntraIdServerAuthProvider.ts
μ΄ ν΄λμ€λ λ€μ λ‘μ§μ μ²λ¦¬ν©λλ€:
tokenStoreμ μ μ₯1. μ¬μ©μκ° μ²μ MCP μλ²μ μ°κ²°νλ € νλ©΄, requireBearerAuth λ―Έλ€μ¨μ΄κ° μ ν¨ν μΈμ
μ΄ μμμ κ°μ§νκ³ Entra ID λ‘κ·ΈμΈ νμ΄μ§λ‘ 리λλ μ
ν©λλ€.
2. μ¬μ©μκ° Entra ID κ³μ μΌλ‘ λ‘κ·ΈμΈν©λλ€.
3. Entra IDκ° κΆν μ½λλ₯Ό ν¬ν¨ν΄ μ¬μ©μλ₯Ό /auth/callback μλν¬μΈνΈλ‘ 리λλ μ
ν©λλ€.
4. μλ²λ μ½λλ₯Ό μ‘μΈμ€ ν ν°κ³Ό 리νλ μ ν ν°μΌλ‘ κ΅ννμ¬ μ μ₯νκ³ , μΈμ ν ν°μ μμ±νμ¬ ν΄λΌμ΄μΈνΈμ μ μ‘ν©λλ€.
5. ν΄λΌμ΄μΈνΈλ μ΄μ μ΄ μΈμ
ν ν°μ Authorization ν€λμ ν¬ν¨μμΌ MCP μλ²μ λν λͺ¨λ ν₯ν μμ²μ μ¬μ©ν μ μμ΅λλ€.
6. getUserDetails λκ΅¬κ° νΈμΆλλ©΄ μΈμ
ν ν°μ μ¬μ©ν΄ Entra ID μ‘μΈμ€ ν ν°μ μ‘°ννκ³ , μ΄λ₯Ό μ΄μ©ν΄ Microsoft Graph APIλ₯Ό νΈμΆν©λλ€.
μ΄ νλ¦μ κ³΅κ° ν΄λΌμ΄μΈνΈ νλ¦λ³΄λ€ 볡μ‘νμ§λ§, μΈν°λ·μ λ ΈμΆλ μλν¬μΈνΈμλ νμμ μ λλ€. μ격 MCP μλ²λ κ³΅μ© μΈν°λ·μ ν΅ν΄ μ κ·Ό κ°λ₯νλ―λ‘, λ¬΄λ¨ μ κ·Όκ³Ό μ μ¬μ 곡격μΌλ‘λΆν° 보νΈνκΈ° μν΄ λ κ°λ ₯ν 보μ μ‘°μΉκ° νμν©λλ€.
1. μ¬λ¬λΆμ΄ ꡬμΆν MCP μλ²λ λ‘컬 μλ²μΈκ°μ, μ격 μλ²μΈκ°μ?
2. λ΅λ³μ λ°λΌ κ³΅κ° ν΄λΌμ΄μΈνΈ λλ λΉλ° ν΄λΌμ΄μΈνΈλ₯Ό μ¬μ©νμκ² μ΅λκΉ?
3. Microsoft Graphμ λν΄ μμ μ μννκΈ° μν΄ MCP μλ²κ° μμ²ν κΆνμ 무μμΈκ°μ?
Microsoft Entra ν¬νΈλ‘ μ΄λνμΈμ.
MCP μλ²μ© μ μ ν리μΌμ΄μ μ λ±λ‘νμΈμ.
μ ν리μΌμ΄μ (ν΄λΌμ΄μΈνΈ) IDμ λλ ν°λ¦¬(ν λνΈ) IDλ₯Ό κΈ°λ‘νμΈμ.
1. MSAL κ°μ λ¬Έμ
Microsoft Authentication Library(MSAL)κ° νλ«νΌ μ λ°μμ μμ ν ν ν° νλμ μ΄λ»κ² μ§μνλμ§ μμ보μΈμ:
MSAL Overview on Microsoft Learn
2. Azure-Samples/mcp-auth-servers GitHub μ μ₯μ
μΈμ¦ νλ¦μ 보μ¬μ£Όλ MCP μλ² μ°Έμ‘° ꡬν μμ :
Azure-Samples/mcp-auth-servers on GitHub
3. Azure 리μμ€μ© κ΄λ¦¬ ID κ°μ
μμ€ν λλ μ¬μ©μ ν λΉ κ΄λ¦¬ IDλ₯Ό μ¬μ©ν΄ λΉλ° μ 보λ₯Ό μ κ±°νλ λ°©λ²μ μ΄ν΄νμΈμ:
Managed Identities Overview on Microsoft Learn
4. Azure API Management: MCP μλ²μ© μΈμ¦ κ²μ΄νΈμ¨μ΄
MCP μλ²λ₯Ό μν μμ ν OAuth2 κ²μ΄νΈμ¨μ΄λ‘ APIMμ μ¬μ©νλ λ°©λ² μ¬μΈ΅ λΆμ:
Azure API Management Your Auth Gateway For MCP Servers
5. Microsoft Graph κΆν μ°Έμ‘°
Microsoft Graphμ λν μμ λ° μ ν리μΌμ΄μ κΆνμ ν¬κ΄μ λͺ©λ‘:
Microsoft Graph Permissions Reference
μ΄ μΉμ μ μλ£νλ©΄ λ€μμ ν μ μμ΅λλ€:
λ©΄μ± μ‘°ν:
μ΄ λ¬Έμλ AI λ²μ μλΉμ€ Co-op Translatorλ₯Ό μ¬μ©νμ¬ λ²μλμμ΅λλ€.
μ νμ±μ μν΄ μ΅μ μ λ€νκ³ μμΌλ, μλ λ²μμλ μ€λ₯λ λΆμ νν λΆλΆμ΄ μμ μ μμμ μ μνμκΈ° λ°λλλ€.
μλ¬Έμ ν΄λΉ μΈμ΄μ μλ³Έ λ¬Έμκ° κΆμ μλ μΆμ²λ‘ κ°μ£Όλμ΄μΌ ν©λλ€.
μ€μν μ 보μ κ²½μ° μ λ¬Έμ μΈ μΈκ° λ²μμ κΆμ₯ν©λλ€.
λ³Έ λ²μ μ¬μ©μΌλ‘ μΈν΄ λ°μνλ μ€ν΄λ μλͺ»λ ν΄μμ λν΄ λΉμ¬λ μ± μμ μ§μ§ μμ΅λλ€.