Authentication
Octopus authenticates to your external MCP server using a Bearer token resolved at runtime from the credential store via ICredentialResolver. The token is sent on every request — tool discovery and tool calls alike.
Authentication Flow
- Operator stores a shared secret (API key or JWT) in the Octopus credential store via the Credentials API
- The resulting
credentialIdinteger is set in the agent'smcpServers[].credentialIdfield - At agent initialization, Octopus calls
ICredentialResolver.GetPasswordAsync(credentialId) - The resolved secret is sent as
Authorization: Bearer {secret}on all requests to the MCP server - Your MCP server validates the token on every incoming request
// Octopus — how the token is resolved and sent (internal)
var token = await _credentialResolver.GetPasswordAsync(server.CredentialId);
var request = new HttpRequestMessage(HttpMethod.Get, $"{server.BaseUrl}/tools");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.SendAsync(request);
Storing the Credential
// POST /api/credentials — store the MCP server's shared secret
{
"name": "Zendesk MCP Server Token",
"description": "Bearer token for the Zendesk MCP tool server",
"type": "ApiKey",
"value": "sk-zendesk-mcp-prod-1234567890abcdef"
}
// Response
{
"credentialId": 55, // use this in mcpServers[].credentialId
"name": "Zendesk MCP Server Token"
}
appsettings.json, environment variables, or source control. The credential store encrypts values at rest.
Validating the Token on Your MCP Server (Static API Key)
For servers that use a static API key (not JWT), validate the token using a simple middleware or policy:
// C# MinimalAPI — static API key validation middleware
app.Use(async (context, next) =>
{
if (context.Request.Path == "/health")
{
await next(context);
return;
}
var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
if (authHeader is null || !authHeader.StartsWith("Bearer "))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new
{
error = "unauthorized",
message = "Missing or invalid Authorization header."
});
return;
}
var providedToken = authHeader["Bearer ".Length..].Trim();
var expectedToken = context.RequestServices
.GetRequiredService<IConfiguration>()["McpServer:ApiKey"];
if (!CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(providedToken),
Encoding.UTF8.GetBytes(expectedToken ?? "")))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new
{
error = "unauthorized",
message = "Invalid token."
});
return;
}
await next(context);
});
Validating the Token on Your MCP Server (JWT)
If your MCP server is registered as an Azure AD application (recommended for enterprise deployments), validate the JWT using the standard AddJwtBearer middleware:
// Program.cs
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
// Azure AD — validates iss, aud, exp, sig
options.Authority = "https://login.microsoftonline.com/{tenantId}/v2.0";
options.Audience = "api://my-mcp-server";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
});
builder.Services.AddAuthorization(options =>
{
// Optional: require a specific scope on tool calls
options.AddPolicy("McpToolCaller", policy =>
policy.RequireClaim("scp", "MCP.Call"));
});
// Map endpoints with RequireAuthorization
app.MapGet("/tools", ...)
.RequireAuthorization("McpToolCaller");
app.MapPost("/tool/{name}/call", ...)
.RequireAuthorization("McpToolCaller");
Authentication Options Comparison
| Option | Complexity | Token Rotation | Use Case |
|---|---|---|---|
| Static API key | Low | Manual (update credential store) | Internal / single-operator deployments |
| Azure AD JWT (app-to-app) | Medium | Automatic (1-hour JWT expiry) | Enterprise / multi-tenant deployments |
| mTLS (mutual TLS) | High | Certificate rotation | High-security / zero-trust environments |
Rotating Credentials
When you rotate the API key or token on your MCP server, update the Octopus credential store with the new value. Octopus re-reads credentials on each tool-discovery cycle — no agent restart is required for the new token to take effect.
// PATCH /api/credentials/{credentialId}
{
"value": "sk-zendesk-mcp-prod-NEW-key-abcdef1234"
}
Per-Tenant Token Support
If your MCP server manages multiple tenants and each has its own API key, store a separate credential per tenant and register different MCP server entries per agent:
// Agent for tenant A
{
"mcpServers": [
{
"name": "zendesk",
"baseUrl": "https://mcp-zendesk.internal",
"credentialId": 55 // tenant A's Zendesk token
}
]
}
// Agent for tenant B
{
"mcpServers": [
{
"name": "zendesk",
"baseUrl": "https://mcp-zendesk.internal",
"credentialId": 56 // tenant B's Zendesk token
}
]
}
On your MCP server, read the X-Octopus-Tenant-Id header to scope the Zendesk API calls to the correct tenant — and validate that the Bearer token is authorised for that tenant ID.