sandeepuppalapati/dotnet-mcp-identityserver
If you are the rightful owner of dotnet-mcp-identityserver and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.
An ASP.NET Core implementation of the Model Context Protocol (MCP) server with Identity Server JWT authentication, featuring weather tools and Claude AI integration.
.NET MCP Identity Server
A production-ready ASP.NET Core implementation of the Model Context Protocol (MCP) with enterprise-grade JWT authentication, demonstrating how to build secure, context-aware AI applications.
β οΈ Note: This is a demonstration/educational project showcasing MCP integration patterns. Review security settings and test thoroughly before production deployment.
π Table of Contents
- The Problem
- The Solution
- Features
- Architecture
- Quick Start
- Configuration
- Usage & Examples
- Technical Decisions
- Development
- Contributing
π― The Problem
Modern AI applications need to:
- Authenticate Users: Integrate with enterprise identity systems (OAuth2, SAML, JWT)
- Provide Context: Give AI agents access to user-specific data, APIs, and resources
- Execute Tools: Allow AI to perform actions on behalf of authenticated users
- Maintain Security: Enforce proper authorization and API key management
- Scale Gracefully: Support multiple users with different permission levels
The Model Context Protocol (MCP) standardizes how AI applications access context and tools, but implementing it with enterprise authentication is complex.
Common Challenges
- Authentication Gap: MCP servers often lack production-ready auth mechanisms
- User Context: Tools need access to authenticated user identity and claims
- API Key Management: Different users may have different API keys for external services
- Testing Complexity: Difficult to test MCP flows without proper tooling
π‘ The Solution
This project provides a reference implementation demonstrating:
- β Enterprise Authentication: JWT Bearer token validation via Identity Server
- β User-Scoped Tools: MCP tools that use authenticated user context
- β Hierarchical API Keys: User-specific, role-based, and default API key fallbacks
- β Built-in Test Client: Web UI for testing MCP flows without external tools
- β Production Patterns: Proper DI, logging, error handling, and configuration
Real-World Use Cases
- Enterprise AI Assistants: Deploy Claude/GPT with company SSO integration
- Multi-Tenant SaaS: Different customers get different API keys and data access
- Personalized Tools: Weather, calendar, CRM tools that respect user permissions
- Secure Agent Frameworks: Build LangChain/AutoGPT agents with proper auth
β¨ Features
π Authentication & Authorization
- JWT Bearer Authentication with configurable Identity Server integration
- Claims-Based Authorization extracting user identity, roles, and company context
- Flexible Token Validation with development-friendly settings
π€ AI Integration
- Claude AI Integration with tool calling support (Anthropic SDK)
- Multi-Turn Conversations with automatic tool execution
- Demo Mode for testing without API keys
π€οΈ Weather Tools (Example Integration)
- Open-Meteo API integration (free, no auth required)
- Location-Based Units: Automatic Celsius/Fahrenheit based on country
- Geocoding: City name to coordinates resolution
- User-Specific API Keys: Premium users can bring their own keys
π‘ MCP Protocol Implementation
- Complete MCP Server: Tools, resources, initialization endpoints
- Tool Discovery: Dynamic tool listing with JSON schemas
- Resource Access: User-scoped resources (profile, permissions)
- Protocol Version: MCP 2024-11-05 specification
π Web Test Client
- Interactive UI: Test all MCP endpoints from browser
- Token Management: JWT input and validation
- Response Viewer: Formatted JSON with syntax highlighting
- Tool Testing: Quick access to all implemented tools
π§ Developer Experience
- Swagger/OpenAPI: Auto-generated API documentation
- Structured Logging: Debug-level logging for services
- Configuration Flexibility: Environment-based settings
- Demo Mode: Run without external dependencies
ποΈ Architecture
System Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client Layer β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Browser β β MCP Client β β Mobile App β β
β β Test UI β β (Claude β β β β
β β β β Desktop) β β β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
ββββββββββββββ¬βββββββββββββββββ¬βββββββββββββββββ¬ββββββββββββββββββ
β β β
β JWT Bearer β JWT Bearer β JWT Bearer
β Token β Token β Token
β β β
ββββββββββββββΌβββββββββββββββββΌβββββββββββββββββΌββββββββββββββββββ
β ASP.NET Core MCP Auth Server β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β JWT Middleware (Program.cs) β β
β β β’ Validate token signature β β
β β β’ Extract claims (sub, email, role, etc.) β β
β β β’ Set HttpContext.User β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ β
β β Controller Layer β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β β β MCP β β Chat β β User β β β
β β β Controller β β Controller β β Controller β β β
β β β β β β β β β β
β β β β’ Initialize β β β’ Claude β β β’ Profile β β β
β β β β’ Tools β β Chat β β β’ Claims β β β
β β β β’ Resources β β β’ Tools β β β’ API Keys β β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β βββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββ β
β β β β β
β βββββββββββΌβββββββββββββββββββΌβββββββββββββββββββΌββββββββββ β
β β Service Layer β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β β β Weather β β Claude β β UserAPIKey β β β
β β β Service β β Service β β Service β β β
β β β β β β β β β β
β β β β’ Geocoding β β β’ Messages β β β’ Key β β β
β β β β’ Weather β β β’ Tool Use β β Resolution β β β
β β β Data β β β’ Response β β β’ Hierarchicalβ β β
β β β β’ Unit Conv. β β Parsing β β Fallback β β β
β β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β β
β βββββββββββ¬βββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββ β
ββββββββββββββΌβββββββββββββββββββΌβββββββββββββββββββΌββββββββββββ
β β β
ββββββββββββββΌββββββ βββββββββββΌββββββ ββββββββββΌβββββββββββ
β Open-Meteo β β Anthropic β β Identity Server β
β Weather API β β Claude API β β β
β β β β β β’ Token Issuer β
β β’ Geocoding β β β’ Messages β β β’ User Store β
β β’ Forecast β β β’ Tool Call β β β’ OIDC/OAuth2 β
ββββββββββββββββββββ βββββββββββββββββ βββββββββββββββββββββ
Data Flow: Weather Tool with User Context
1. User β MCP Client: "What's the weather in London?"
2. MCP Client β Server: POST /api/mcp/tools/call
Headers: Authorization: Bearer <JWT>
Body: { "name": "get_weather", "arguments": { "city": "London" } }
3. Server: JWT Middleware validates token, extracts claims
β’ sub: "user123"
β’ email: "john@company.com"
β’ role: "premium"
4. McpController β WeatherService.GetWeatherAsync("London", "user123")
5. WeatherService: Resolve API key
β’ Check Weather:UserApiKeys:user123 β not found
β’ Check role "premium" β Weather:RoleApiKeys:Premium β found!
β’ Use premium API key
6. WeatherService β Open-Meteo:
a. Geocode "London" β {lat: 51.5074, lon: -0.1278, country: "UK"}
b. Get weather β {temp: 18Β°C, humidity: 72%, clouds: 40%}
7. WeatherService: Determine unit (UK β Celsius)
8. WeatherService β McpController: WeatherData object
9. McpController: Format MCP response
{
"content": [{
"type": "text",
"text": "Hi john! π€οΈ Weather in London, UK\nTemp: 18Β°C..."
}]
}
10. Server β MCP Client: HTTP 200 with formatted weather
Key Architectural Patterns
1. Authentication Middleware Chain
Request β HTTPS Redirect β Static Files β Authentication β Authorization β Controllers
2. Service Layer Abstraction
All business logic lives in services implementing interfaces:
IWeatherService
: Weather data retrievalIClaudeService
: AI interactionsIUserApiKeyService
: Key management
3. Dependency Injection
// Program.cs
builder.Services.AddHttpClient<IWeatherService, WeatherService>();
builder.Services.AddHttpClient<IClaudeService, ClaudeService>();
builder.Services.AddSingleton<IUserApiKeyService, UserApiKeyService>();
4. Configuration-Driven API Keys
{
"Weather": {
"ApiKey": "default-key", // Fallback
"UserApiKeys": { "user123": "user-key" }, // User-specific
"RoleApiKeys": { "Premium": "premium-key" } // Role-based
}
}
π Quick Start
Prerequisites
- .NET 8.0 SDK or later
- Identity Server instance for JWT issuing (optional for demo mode)
- Claude API Key from Anthropic (optional, demo mode available)
Installation
-
Clone the repository
git clone https://github.com/yourusername/dotnet-mcp-identityserver.git cd dotnet-mcp-identityserver
-
Configure settings
# Create development settings (gitignored) cp appsettings.json appsettings.Development.json # Edit with your values nano appsettings.Development.json
-
Minimal configuration for demo
{ "IdentityServer": { "Authority": "https://your-identity-server.com", "Audience": "your-client-id", "RequireHttpsMetadata": false // For local development }, "Weather": { "ApiKey": "open-meteo" // Free, no auth required }, "Claude": { "ApiKey": "demo", // Use demo mode "Model": "claude-3-5-sonnet-20241022" } }
-
Build and run
dotnet build dotnet run
-
Access the application
- Web UI: https://localhost:7000
- Swagger: https://localhost:7000/swagger
First Test (Without Identity Server)
If you don't have an Identity Server, use the test endpoint:
# Get a test token (development only!)
curl https://localhost:7000/api/test/token
# Use the token
curl -H "Authorization: Bearer <token-from-above>" \
https://localhost:7000/api/mcp/tools
βοΈ Configuration
Identity Server Setup
Configure your Identity Server in appsettings.json
:
{
"IdentityServer": {
"Authority": "https://your-identity-server.com",
"Audience": "mcp-server-api",
"RequireHttpsMetadata": true
}
}
Required Identity Server Configuration:
- Client ID (Audience):
mcp-server-api
- Allowed Scopes:
openid
,profile
,email
- Token Type: JWT (not reference tokens)
Recommended Claims to Include:
sub
: User identifier (required)email
: User emailname
orfirstName
/lastName
: Display namerole
: User role (for role-based API keys)companyId
: Tenant/organization context (optional)
Weather API Configuration
Basic (Free Open-Meteo)
{
"Weather": {
"ApiKey": "open-meteo"
}
}
Advanced (User-Specific Keys)
{
"Weather": {
"ApiKey": "open-meteo", // Default fallback
"PremiumApiKey": "premium-key", // For premium users
"UserApiKeys": {
"user123": "user-specific-key", // Specific user
"admin": "admin-key"
},
"PremiumUsers": {
"user123": true // Mark as premium
},
"RoleApiKeys": {
"Admin": "admin-tier-key", // Role-based
"Premium": "premium-tier-key"
}
}
}
Resolution Order:
UserApiKeys:{userId}
- User-specific keyRoleApiKeys:{userRole}
- Role-based keyPremiumApiKey
(ifPremiumUsers:{userId}
is true)ApiKey
- Default fallback
Claude AI Configuration
{
"Claude": {
"ApiKey": "sk-ant-api03-...", // Your Anthropic API key
"Model": "claude-3-5-sonnet-20241022"
}
}
Supported Models:
claude-3-5-sonnet-20241022
(recommended)claude-3-opus-20240229
claude-3-sonnet-20240229
claude-3-haiku-20240307
Demo Mode:
Set ApiKey: "demo"
to use canned responses without API calls.
Logging Configuration
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"McpAuthServer.Services.ClaudeService": "Debug",
"McpAuthServer.Services.WeatherService": "Debug"
}
}
}
π§ Usage & Examples
Web Test Client
The built-in web client at https://localhost:7000
provides:
- JWT Token Input: Paste your token from Identity Server
- User Info: View extracted claims from token
- Tool Testing: Quick buttons for all MCP tools
- Resource Browser: View user-scoped resources
- Response Viewer: Formatted JSON output
API Endpoints
MCP Protocol Endpoints
Endpoint | Method | Auth | Description |
---|---|---|---|
/api/mcp/initialize | POST | β | MCP protocol handshake |
/api/mcp/tools | GET | β | List available tools |
/api/mcp/tools/call | POST | β | Execute a tool |
/api/mcp/resources | GET | β | List user resources |
/api/mcp/resources/read | POST | β | Read resource data |
User Management Endpoints
Endpoint | Method | Auth | Description |
---|---|---|---|
/api/user/details | GET | β | User profile and claims |
/api/user/claims | GET | β | All JWT claims |
/api/user/apikeys | GET | β | Configured API keys |
Chat Endpoints
Endpoint | Method | Auth | Description |
---|---|---|---|
/api/chat/completions | POST | β | Simple Claude chat |
/api/chat/tools | POST | β | Claude with tool access |
Example: Weather Tool
Request:
curl -X POST https://localhost:7000/api/mcp/tools/call \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "get_weather",
"arguments": {
"city": "London"
}
}'
Response:
{
"content": [
{
"type": "text",
"text": "Hi John! π€οΈ Here's the weather in London, United Kingdom\nTemperature: 18Β°C\nCondition: Partly cloudy\nHumidity: 72%\nPressure: 1013 hPa\nWind Speed: 5.5 m/s\nCloud Cover: 40%"
}
]
}
Example: Claude with Tools
Request:
curl -X POST https://localhost:7000/api/mcp/tools/call \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "claude_with_tools",
"arguments": {
"message": "What is the weather like in Paris?"
}
}'
What Happens:
- Request sent to Claude with tool definitions
- Claude responds with
tool_use
forget_weather(city: "Paris")
- Server executes weather API call
- Results sent back to Claude
- Claude generates natural language response
- Final response returned to client
Response:
{
"content": [
{
"type": "text",
"text": "π§ Claude with tools:\nThe weather in Paris is currently 22Β°C with clear skies. It's a beautiful day with 45% humidity and light winds at 3.2 m/s. Perfect weather for a walk along the Seine!"
}
]
}
Example: User Resources
Request:
curl https://localhost:7000/api/mcp/resources \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Response:
{
"resources": [
{
"uri": "user://profile",
"name": "User Profile",
"description": "Current user profile information"
},
{
"uri": "user://permissions",
"name": "User Permissions",
"description": "Current user permissions and roles"
}
]
}
Read Resource:
curl -X POST https://localhost:7000/api/mcp/resources/read \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "uri": "user://profile" }'
π§ Technical Decisions
Why ASP.NET Core?
Decision: Use ASP.NET Core instead of Python FastAPI or Node.js Express
Rationale:
- Enterprise Ready: Built-in JWT authentication, DI, configuration
- Performance: Faster than Python/Node for I/O-bound workloads
- Type Safety: C# strong typing prevents runtime errors
- Tooling: Excellent IDE support (Visual Studio, Rider, VS Code)
- .NET 8 Features: Native AOT, improved async, minimal APIs
Trade-offs: Larger initial footprint, fewer MCP examples in C#
Why Identity Server for Auth?
Decision: Use external Identity Server (OAuth2/OIDC) instead of built-in auth
Rationale:
- Enterprise Standard: Most companies already have Identity Server/Azure AD/Okta
- Centralized Auth: Single source of truth for users
- Production Ready: Battle-tested token validation
- Flexibility: Works with any OIDC-compliant provider
Trade-offs: Requires external service, more complex local setup
Why Open-Meteo for Weather?
Decision: Use Open-Meteo API instead of OpenWeatherMap/WeatherAPI
Rationale:
- No API Key Required: Free tier doesn't need registration
- Good Coverage: Global weather data
- No Rate Limits: Generous free usage
- Simple API: Easy to demonstrate concepts
Trade-offs: Less detailed than paid services, no historical data
Why User-Specific API Keys?
Decision: Implement hierarchical API key resolution (user β role β default)
Rationale:
- Multi-Tenancy: Different customers can use their own keys
- Cost Attribution: Track API usage per user/department
- Rate Limits: Avoid hitting global rate limits
- Premium Tiers: Easy to offer paid features
Implementation: WeatherService.GetApiKeyForUser()
checks config hierarchy
Why Demo Mode?
Decision: Support "demo" API key for testing without external services
Rationale:
- Developer Experience: Run project immediately after clone
- CI/CD Testing: No secrets needed for tests
- Demonstrations: Show architecture without API setup
Implementation: Services detect ApiKey: "demo"
and return canned data
JWT Validation Settings
Decision: Relaxed validation in Program.cs
(ValidateIssuerSigningKey=false)
Rationale:
- Development Ease: Some Identity Server setups have certificate issues
- Flexibility: Works with various token issuers
- Clear Warning: Code comments explain this is for development
β οΈ Production: Set ValidateIssuerSigningKey: true
and RequireSignedTokens: true
Service Layer Pattern
Decision: Separate controllers from business logic via service interfaces
Rationale:
- Testability: Easy to mock services in tests
- Reusability: Services used by multiple controllers
- Separation of Concerns: HTTP logic vs business logic
- Dependency Injection: Lifetime management (Singleton/Scoped)
Example:
public interface IWeatherService {
Task<WeatherData?> GetWeatherAsync(string city, string? userId);
}
Temperature Unit Localization
Decision: Automatically use Fahrenheit for US, Celsius elsewhere
Rationale:
- User Experience: Show familiar units automatically
- No User Input: Infer from location, not explicit setting
- Simple Logic: Country-based lookup (
GetTemperatureUnit()
)
Implementation: Open-Meteo geocoding returns country, lookup table maps to unit
Structured Logging
Decision: Use ILogger with structured parameters, debug level for services
Rationale:
- Debugging: Trace API calls and responses
- Production: Info level for high-level operations
- Searchable: Structured logs work with Seq/ELK/Azure Monitor
Example:
_logger.LogInformation("Weather request for {City}, user {UserId}", city, userId);
π§ͺ Development
Project Structure
dotnet-mcp-identityserver/
βββ Controllers/ # API Controllers
β βββ McpController.cs # MCP protocol (tools, resources)
β βββ ChatController.cs # Claude chat endpoints
β βββ UserController.cs # User info, claims, API keys
β βββ UserApiKeyController.cs # API key management
β βββ WeatherTestController.cs # Weather testing
β βββ AuthController.cs # Auth helpers
β βββ TestController.cs # Test token generation
βββ Services/ # Business Logic
β βββ WeatherService.cs # Open-Meteo integration
β βββ ClaudeService.cs # Anthropic Claude SDK
β βββ UserApiKeyService.cs # API key resolution
βββ wwwroot/ # Static Files
β βββ index.html # Test client UI
β βββ mcp-client.js # Client-side JavaScript
βββ Program.cs # App startup & middleware
βββ McpAuthServer.csproj # Project file
βββ appsettings.json # Configuration (template)
βββ appsettings.Development.json # Local settings (gitignored)
βββ README.md # This file
βββ CLAUDE.md # AI assistant guidance
βββ CONTRIBUTING.md # Contribution guidelines
Running Tests
# Run all tests
dotnet test
# Run with detailed output
dotnet test --logger "console;verbosity=detailed"
# Run with code coverage
dotnet test --collect:"XPlat Code Coverage"
# Generate coverage report (requires reportgenerator)
reportgenerator -reports:**/coverage.cobertura.xml -targetdir:coverage
Building for Production
# Optimized release build
dotnet publish -c Release -o ./publish
# Self-contained deployment (includes .NET runtime)
dotnet publish -c Release -r linux-x64 --self-contained
# Single-file executable
dotnet publish -c Release -r linux-x64 -p:PublishSingleFile=true
Docker Deployment
Dockerfile:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["McpAuthServer.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet build -c Release -o /app/build
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY /app/publish .
ENTRYPOINT ["dotnet", "McpAuthServer.dll"]
Build and run:
# Build image
docker build -t mcp-auth-server:latest .
# Run container
docker run -d -p 8080:80 -p 8081:443 \
-e IdentityServer__Authority=https://your-ids.com \
-e IdentityServer__Audience=mcp-api \
-e Claude__ApiKey=$CLAUDE_API_KEY \
--name mcp-server \
mcp-auth-server:latest
# View logs
docker logs -f mcp-server
Environment Variables
Override configuration via environment variables (Docker/Kubernetes):
# Format: Section__Property
export IdentityServer__Authority="https://ids.company.com"
export IdentityServer__Audience="mcp-api"
export IdentityServer__RequireHttpsMetadata="true"
export Claude__ApiKey="sk-ant-..."
export Weather__ApiKey="open-meteo"
Adding a New Tool
-
Define tool in
McpController.GetTools()
new { name = "get_stock_price", description = "Get current stock price", inputSchema = new { type = "object", properties = new { symbol = new { type = "string", description = "Stock ticker" } }, required = new[] { "symbol" } } }
-
Add case to
McpController.CallTool()
"get_stock_price" => await HandleStockPriceTool(request.Arguments)
-
Implement handler
private async Task<IActionResult> HandleStockPriceTool(object? arguments) { var symbol = ExtractStringFromArguments(arguments, "symbol"); var price = await _stockService.GetPriceAsync(symbol); return Ok(new { content = new[] { new { type = "text", text = $"${symbol}: ${price}" } } }); }
-
Create service (optional)
public interface IStockService { Task<decimal> GetPriceAsync(string symbol); }
-
Register service in
Program.cs
builder.Services.AddHttpClient<IStockService, StockService>();
Debugging Tips
Enable detailed logging:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Debug"
}
}
}
View JWT claims:
curl https://localhost:7000/api/user/claims \
-H "Authorization: Bearer YOUR_TOKEN"
Test without auth:
Use [AllowAnonymous]
attribute on controller during development:
[AllowAnonymous] // Remove before production!
public class TestController : ControllerBase { }
View Swagger docs: Navigate to https://localhost:7000/swagger
π€ Contributing
We welcome contributions! See for guidelines.
Quick Contribution Guide
- Fork the repository
- Create a branch:
git checkout -b feature/amazing-tool
- Make changes with tests and documentation
- Commit:
git commit -m "feat: add stock price tool"
- Push:
git push origin feature/amazing-tool
- Open PR with description of changes
Code Style
- Follow standard C# conventions (PascalCase for public members)
- Add XML documentation to public APIs
- Use dependency injection for all services
- Write unit tests for business logic
- Keep controllers thin (business logic in services)
π License
This project is licensed under the MIT License - see the file for details.
π Acknowledgments
- Anthropic - Model Context Protocol specification and Claude AI
- Open-Meteo - Free weather API without auth requirements
- Identity Server - Enterprise authentication standard
- ASP.NET Core Team - Excellent framework
π Additional Resources
- MCP Specification - Protocol details
- Claude API Docs - Anthropic API reference
- Identity Server Docs - OIDC/OAuth2 setup
- ASP.NET Core Docs - Framework documentation
- Open-Meteo API - Weather API reference
π Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: your-email@example.com
β Star this repository if you find it helpful!
π Share with others building AI applications with enterprise auth!