MCP makes it possible for AI applications to discover and call tools exposed by external servers. That flexibility is one of its biggest strengths, but it also creates an important security question:
Should an MCP client that can connect to a server automatically be allowed to call every tool on that server?
In a production environment, the answer should usually be no.
A user may be allowed to read customer information but not modify it. An agent may be allowed to search documents but not delete them. A support workflow may need access to ticket APIs while a reporting agent should have read-only access.
This is where zero-trust tool authorization becomes important.
The basic principle is straightforward:
Establish identity, authenticate the request, authorize the specific capability, and grant only the permissions required for that operation.
The current MCP specification has strengthened its OAuth authorization model, including authorization-server discovery, issuer validation, resource-bound credentials, scope challenges, and a stronger emphasis on least-privilege scope selection.
For .NET developers, this means MCP authorization should be designed as an explicit security boundary rather than relying only on whether a client successfully connected to the server.
Why Server-Level Authorization Is Not Enough
Consider an MCP server exposing these tools:
Customer MCP Server
│
├── get_customer
├── search_orders
├── update_customer
├── refund_order
└── delete_customer
A simple server-level policy might say:
Authenticated client = access to MCP server
That is too broad.
A reporting agent might only need:
customer:read
orders:read
A customer-service agent might need:
customer:read
orders:read
customer:write
A finance agent might additionally need:
orders:refund
The security model should therefore distinguish authentication from authorization.
Client
|
v
Authentication
|
v
Identity + Claims
|
v
Authorization Policy
|
v
Tool Permission
|
v
Tool Execution
A valid token answers:
Who is calling?
Authorization answers:
What is this caller allowed to do?
Understanding Zero-Trust for MCP
Zero-trust does not mean denying every request.
It means that trust is not automatically inherited from network location, connection status, or previous requests.
For MCP, a practical zero-trust model can apply these checks:
Authenticate the client.
Validate the token issuer.
Validate the token audience/resource.
Validate expiration.
Validate required scopes.
Apply user or workload permissions.
Authorize the requested tool.
Execute only after authorization succeeds.
The current MCP authorization specification requires clients and servers to follow OAuth-based authorization and discovery mechanisms, while access tokens are bound to the resource they were issued for.
Designing Tool Scopes
Scopes provide one mechanism for expressing permissions.
A simple model could be:
customer:read
customer:write
orders:read
orders:write
orders:refund
Then map tools to required scopes:
| MCP Tool | Required Scope |
|---|---|
| get_customer | customer:read |
| search_orders | orders:read |
| update_customer | customer:write |
| create_order | orders:write |
| refund_order | orders:refund |
| delete_customer | customer:write |
The names are application-specific. The important point is that permissions should represent meaningful capabilities rather than simply copying internal implementation details.
The MCP authorization specification explicitly recommends least-privilege scope selection and allows servers to communicate the scopes required for a particular operation through the WWW-Authenticate challenge.
Implementing Authentication in ASP.NET Core
An MCP server built on ASP.NET Core can use the standard authentication middleware.
A simplified configuration looks like:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority =
builder.Configuration["Identity:Authority"];
options.Audience =
builder.Configuration["Identity:Audience"];
options.RequireHttpsMetadata = true;
});
builder.Services.AddAuthorization();
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapMcp();
app.Run();
The exact identity-provider configuration will depend on the organization's OAuth or OpenID Connect infrastructure.
The important architectural point is that MCP sits behind an established identity boundary rather than implementing token validation manually inside every tool.
Do Not Put Authorization Logic Only Inside Tools
A common implementation is:
public async Task<string> RefundOrder(
string orderId,
ClaimsPrincipal user)
{
if (!user.IsInRole("Finance"))
{
throw new UnauthorizedAccessException();
}
// Refund order...
}
This can work, but it mixes business logic and security policy.
A better design is to establish authorization policies centrally.
For example:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(
"OrdersRead",
policy => policy.RequireClaim(
"scope",
"orders:read"));
options.AddPolicy(
"OrdersRefund",
policy => policy.RequireClaim(
"scope",
"orders:refund"));
});
Then the MCP boundary can enforce the appropriate policy before the tool reaches business logic.
This separation makes security policy easier to audit and change.
Per-Server vs Per-Tool Authorization
There are two useful models.
Per-Server Authorization
Every MCP request requires authentication.
MCP Server
|
+-- Authentication required
|
+-- get_customer
+-- search_orders
+-- refund_order
This is simpler when every capability is sensitive.
Per-Tool Authorization
Only selected tools require authorization.
MCP Server
│
├── ping Public
├── documentation Public
├── get_customer Protected
├── update_customer Protected
└── refund_order Highly Protected
The MCP authorization ecosystem supports both approaches. Per-tool authorization allows public capabilities to remain accessible while sensitive operations trigger authorization requirements.
For enterprise systems, per-tool authorization is often useful when a single server exposes capabilities with different risk levels.
Mapping Tools to Policies
A practical .NET implementation can maintain an explicit policy map.
public static class McpAuthorizationPolicies
{
public const string CustomerRead = "customer:read";
public const string CustomerWrite = "customer:write";
public const string OrdersRead = "orders:read";
public const string OrdersRefund = "orders:refund";
}
Then:
var toolPolicies = new Dictionary<string, string>
{
["get_customer"] =
McpAuthorizationPolicies.CustomerRead,
["update_customer"] =
McpAuthorizationPolicies.CustomerWrite,
["search_orders"] =
McpAuthorizationPolicies.OrdersRead,
["refund_order"] =
McpAuthorizationPolicies.OrdersRefund
};
This creates a visible security inventory.
When a new tool is added, the development team has to decide what permission it requires.
That is much safer than allowing every newly registered tool to inherit broad server permissions.
Handling Insufficient Scope
Suppose a client has:
orders:read
and attempts:
refund_order
The server should not execute the tool and then return a generic application error.
The authorization layer should reject the request because the required permission is missing.
The current MCP specification defines 403 Forbidden with an insufficient_scope challenge for cases where the presented token lacks the required permission.
Conceptually:
Client
|
| refund_order
v
MCP Server
|
| Token = orders:read
|
| Required = orders:refund
v
403 insufficient_scope
This is different from authentication failure.
401 = Authentication is missing or invalid
403 = Authentication succeeded, but permission is insufficient
Keeping these states distinct makes both client behavior and operational troubleshooting clearer.
Step-Up Authorization
A useful pattern is step-up authorization.
A client may initially have:
orders:read
It can therefore search orders.
Later, the user asks the agent to issue a refund.
The server requires:
orders:refund
The client can initiate a new authorization flow with the additional scope rather than requesting every possible permission at startup.
The MCP authorization specification describes scope accumulation during step-up authorization and recommends that clients retain previously granted scopes when requesting additional permissions.
This supports a better user experience while maintaining least privilege.
Protecting High-Risk Tools
Not every tool has the same security impact.
A useful classification is:
Join the conversation! Your thoughts help the community grow.