Agent Registry – Microsoft Agent Framework
Integrate LumoAuth agent identity with the Microsoft Agent Framework to authenticate your agent, enforce capability-based authorization, and perform token exchange for secured MCP servers.
Prerequisites
Set the environment variables LUMOAUTH_URL, LUMOAUTH_TENANT,
AGENT_CLIENT_ID, and AGENT_CLIENT_SECRET from your LumoAuth tenant portal.
See Agent Registry for registration and credential setup.
Install
- Python
- .NET
pip install agent-framework lumoauth
Python 3.10+ required.
dotnet add package Microsoft.Agents.AI
dotnet add package Azure.AI.OpenAI
.NET 10 SDK or later required.
Example
- Python
- .NET
import asyncio
from typing import Annotated
from pydantic import Field
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from lumoauth import LumoAuthAgent
# 1. Authenticate the LumoAuth agent (client-credentials flow)
lumo = LumoAuthAgent()
lumo.authenticate()
# 2. Define tools guarded by LumoAuth capability checks
def search_company_documents(
query: Annotated[str, Field(description="The search query for company documents")],
) -> str:
"""Search internal company documents for the given query."""
if not lumo.has_capability("read:documents"):
return "Error: agent lacks 'read:documents' capability."
return f"Found 3 documents matching '{query}'"
def query_financial_mcp(
metric: Annotated[str, Field(description="The financial metric to retrieve")],
) -> str:
"""Query the secured financial metrics MCP server."""
if not lumo.has_capability("mcp:financial"):
return "Error: agent lacks 'mcp:financial' capability."
mcp_token = lumo.get_mcp_token("urn:mcp:financial-data")
if not mcp_token:
return "Error: failed to obtain MCP token."
return f"Retrieved {metric}: $1.2M (authenticated via MCP token exchange)"
# 3. Abort early if the agent's daily budget is exhausted
if lumo.is_budget_exhausted():
raise RuntimeError("Agent budget exhausted for today.")
# 4. Create the Microsoft Agent Framework agent with LumoAuth-guarded tools
agent = Agent(
client=OpenAIChatClient(),
name="Research Analyst",
instructions="You are a senior analyst. Use tools to search documents and query financial metrics.",
tools=[search_company_documents, query_financial_mcp],
)
# 5. Run the agent
async def main():
response = await agent.run(
"Search documents for Q3 performance, then query the financial MCP for EBITDA."
)
print(response)
if __name__ == "__main__":
asyncio.run(main())
// Copyright (c) Microsoft. All rights reserved.
// Integrates LumoAuth agent identity with Microsoft Agent Framework.
// The agent authenticates via OAuth 2.0 client credentials, checks capabilities
// before executing tools, and exchanges its token for MCP-scoped access.
using System.ComponentModel;
using System.Net.Http.Headers;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
// ── LumoAuth configuration ────────────────────────────────────────────────────
var lumoUrl = Environment.GetEnvironmentVariable("LUMOAUTH_URL") ?? throw new InvalidOperationException("LUMOAUTH_URL is not set.");
var tenant = Environment.GetEnvironmentVariable("LUMOAUTH_TENANT") ?? throw new InvalidOperationException("LUMOAUTH_TENANT is not set.");
var clientId = Environment.GetEnvironmentVariable("AGENT_CLIENT_ID") ?? throw new InvalidOperationException("AGENT_CLIENT_ID is not set.");
var clientSecret = Environment.GetEnvironmentVariable("AGENT_CLIENT_SECRET") ?? throw new InvalidOperationException("AGENT_CLIENT_SECRET is not set.");
// ── Azure OpenAI configuration ────────────────────────────────────────────────
var aoaiEndpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// ── 1. Obtain a LumoAuth access token (client credentials grant) ──────────────
using var http = new HttpClient();
var tokenEndpoint = $"{lumoUrl}/t/{tenant}/api/v1/oauth/token";
var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = clientId,
["client_secret"] = clientSecret,
["scope"] = "read:documents mcp:financial",
});
var tokenResponse = await http.PostAsync(tokenEndpoint, tokenRequest);
tokenResponse.EnsureSuccessStatusCode();
var tokenJson = await tokenResponse.Content.ReadAsStringAsync();
var tokenDoc = JsonDocument.Parse(tokenJson);
var agentToken = tokenDoc.RootElement.GetProperty("access_token").GetString()!;
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", agentToken);
// ── 2. Helper: check a LumoAuth capability ────────────────────────────────────
async Task<bool> HasCapabilityAsync(string permission)
{
var body = JsonSerializer.Serialize(new { permission });
var content = new StringContent(body, System.Text.Encoding.UTF8, "application/json");
var checkEndpoint = $"{lumoUrl}/t/{tenant}/api/v1/authz/check";
var response = await http.PostAsync(checkEndpoint, content);
if (!response.IsSuccessStatusCode) return false;
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
return doc.RootElement.TryGetProperty("allowed", out var allowed) && allowed.GetBoolean();
}
// ── 3. Helper: exchange agent token for an MCP-scoped token (RFC 8693) ────────
async Task<string?> GetMcpTokenAsync(string mcpResource)
{
var exchangeRequest = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "urn:ietf:params:oauth:grant-type:token-exchange",
["subject_token"] = agentToken,
["subject_token_type"] = "urn:ietf:params:oauth:token-type:access_token",
["audience"] = mcpResource,
});
var exchangeEndpoint = $"{lumoUrl}/t/{tenant}/api/v1/oauth/token";
var response = await http.PostAsync(exchangeEndpoint, exchangeRequest);
if (!response.IsSuccessStatusCode) return null;
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
return doc.RootElement.TryGetProperty("access_token", out var token) ? token.GetString() : null;
}
// ── 4. Define tools guarded by LumoAuth capability checks ────────────────────
[Description("Search internal company documents for the given query.")]
async Task<string> SearchCompanyDocuments(
[Description("The search query for company documents")] string query)
{
if (!await HasCapabilityAsync("read:documents"))
return "Error: agent lacks 'read:documents' capability.";
return $"Found 3 documents matching '{query}'";
}
[Description("Query the secured financial metrics MCP server.")]
async Task<string> QueryFinancialMcp(
[Description("The financial metric to retrieve")] string metric)
{
if (!await HasCapabilityAsync("mcp:financial"))
return "Error: agent lacks 'mcp:financial' capability.";
var mcpToken = await GetMcpTokenAsync("urn:mcp:financial-data");
if (mcpToken is null)
return "Error: failed to obtain MCP token.";
return $"Retrieved {metric}: $1.2M (authenticated via MCP token exchange)";
}
// ── 5. Create the Microsoft Agent Framework agent ─────────────────────────────
// WARNING: DefaultAzureCredential is convenient for development but requires
// careful consideration in production. Use ManagedIdentityCredential in prod.
AIAgent agent = new AzureOpenAIClient(new Uri(aoaiEndpoint), new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsAIAgent(
name: "Research Analyst",
instructions: "You are a senior analyst. Use tools to search documents and query financial metrics.",
tools:
[
AIFunctionFactory.Create(SearchCompanyDocuments, name: nameof(SearchCompanyDocuments)),
AIFunctionFactory.Create(QueryFinancialMcp, name: nameof(QueryFinancialMcp)),
]);
// ── 6. Run the agent ──────────────────────────────────────────────────────────
var result = await agent.RunAsync(
"Search documents for Q3 performance, then query the financial MCP for EBITDA."
);
Console.WriteLine(result);
Set the required environment variables before running:
LUMOAUTH_URL=https://app.lumoauth.dev
LUMOAUTH_TENANT=your-tenant-slug
AGENT_CLIENT_ID=your-agent-client-id
AGENT_CLIENT_SECRET=your-agent-client-secret
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4o-mini
dotnet run
How It Works
- Python
- .NET
| Step | What happens |
|---|---|
LumoAuthAgent() | Reads credentials from env vars and initialises the client |
lumo.authenticate() | Performs OAuth 2.0 client-credentials flow, stores the access token |
Plain functions in tools=[] | Agent automatically wraps annotated functions as callable tools |
lumo.has_capability(...) | Checks the permission against LumoAuth before executing the tool |
lumo.get_mcp_token(...) | Exchanges the agent token for an MCP-specific token (RFC 8693) |
await agent.run(...) | Runs the agent and returns the final text response |
| Step | What happens |
|---|---|
FormUrlEncodedContent + token endpoint | Performs OAuth 2.0 client-credentials flow to get an agent access token |
HasCapabilityAsync(...) | Posts to /authz/check and reads the allowed boolean before the tool executes |
GetMcpTokenAsync(...) | Calls the token endpoint with grant_type=urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693) |
AIFunctionFactory.Create(...) | Wraps an async delegate as a typed AIFunction the agent can invoke |
.AsAIAgent(...) | Extension method from Microsoft.Agents.AI that builds an AIAgent on top of a chat client |
agent.RunAsync(...) | Runs the agent loop and returns the final text response |
Next Steps
- Agent Registry overview — manage registrations, budgets, and capabilities
- AAuth Quick Start — add cryptographic agent identity (JWT proof-of-possession)
- JIT Permissions — request human-in-the-loop approval for sensitive operations
- Workload Federation — authenticate using Azure Managed Identity instead of client secrets