<!--
REGISTER POLICY
This policy implements the dynamic client registration endpoint for OAuth2 flow.
Flow:
1. MCP client sends a registration request with redirect URIs
2. We store the registration information in CosmosDB for persistence
3. We generate and return client credentials with the provided redirect URIs
-->
<policies>
<inbound>
<base />
<!-- STEP 1: Extract client registration data from request -->
<set-variable name="requestBody" value="@(context.Request.Body.As<JObject>(preserveContent: true))" />
<!-- STEP 2: Generate a unique client ID (GUID) -->
<set-variable name="uniqueClientId" value="@(Guid.NewGuid().ToString())" />
<!-- STEP 3: Prepare client info document for CosmosDB -->
<set-variable name="clientDocument" value="@{
var requestBody = context.Variables.GetValueOrDefault<JObject>("requestBody");
var uniqueClientId = context.Variables.GetValueOrDefault<string>("uniqueClientId");
var document = new JObject();
document["id"] = uniqueClientId;
document["clientId"] = uniqueClientId;
document["client_name"] = requestBody["client_name"]?.ToString() ?? "Unknown Application";
document["client_uri"] = requestBody["client_uri"]?.ToString() ?? "";
document["redirect_uris"] = requestBody["redirect_uris"];
document["created_at"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
return document.ToString();
}" />
<!-- STEP 4: Get CosmosDB access token using managed identity -->
<authentication-managed-identity resource="https://cosmos.azure.com" output-token-variable-name="cosmosAccessToken" />
<!-- STEP 5: Store client registration in CosmosDB using AAD token -->
<send-request mode="new" response-variable-name="cosmosResponse" timeout="30" ignore-error="false">
<set-url>@($"{{CosmosDbEndpoint}}/dbs/{{CosmosDbDatabase}}/colls/{{CosmosDbContainer}}/docs")</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-header name="x-ms-version" exists-action="override">
<value>2018-12-31</value>
</set-header>
<set-header name="x-ms-documentdb-partitionkey" exists-action="override">
<value>@($"[\"{context.Variables.GetValueOrDefault<string>("uniqueClientId")}\"]")</value>
</set-header>
<set-header name="Authorization" exists-action="override">
<value>@($"type=aad&ver=1.0&sig={context.Variables.GetValueOrDefault<string>("cosmosAccessToken")}")</value>
</set-header>
<set-body>@(context.Variables.GetValueOrDefault<string>("clientDocument"))</set-body>
</send-request>
<!-- STEP 6: Check if CosmosDB operation was successful -->
<choose>
<when condition="@(((IResponse)context.Variables["cosmosResponse"]).StatusCode >= 400)">
<return-response>
<set-status code="500" reason="Internal Server Error" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@{
return new JObject
{
["error"] = "server_error",
["error_description"] = "Failed to store client registration"
}.ToString();
}</set-body>
</return-response>
</when>
</choose>
<!-- STEP 7: Cache the redirect URI for backward compatibility with other policies -->
<cache-store-value duration="3600"
key="ClientRedirectUri"
value="@(context.Variables.GetValueOrDefault<JObject>("requestBody")["redirect_uris"][0].ToString())" />
<!-- Store client info by client ID for easy lookup during consent -->
<cache-store-value duration="3600"
key="@($"ClientInfo-{context.Variables.GetValueOrDefault<string>("uniqueClientId")}")"
value="@{
var requestBody = context.Variables.GetValueOrDefault<JObject>("requestBody");
var clientInfo = new JObject();
clientInfo["client_name"] = requestBody["client_name"]?.ToString() ?? "Unknown Application";
clientInfo["client_uri"] = requestBody["client_uri"]?.ToString() ?? "";
clientInfo["redirect_uris"] = requestBody["redirect_uris"];
return clientInfo.ToString();
}" />
<!-- STEP 8: Set response content type -->
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<!-- STEP 9: Return client credentials response -->
<return-response>
<set-status code="200" reason="OK" />
<set-header name="access-control-allow-origin" exists-action="override">
<value>*</value>
</set-header>
<set-body template="none">@{
var requestBody = context.Variables.GetValueOrDefault<JObject>("requestBody");
// Generate timestamps dynamically
// Current time in seconds since epoch (Unix timestamp)
long currentTimeSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
// Client ID issued at current time
long clientIdIssuedAt = currentTimeSeconds;
// Client secret expires in 1 year (31536000 seconds = 365 days)
long clientSecretExpiresAt = currentTimeSeconds + 31536000;
// Use the generated client ID from earlier
string uniqueClientId = context.Variables.GetValueOrDefault<string>("uniqueClientId", Guid.NewGuid().ToString());
return new JObject
{
["client_id"] = uniqueClientId,
["client_id_issued_at"] = clientIdIssuedAt,
["client_secret_expires_at"] = clientSecretExpiresAt,
["redirect_uris"] = requestBody["redirect_uris"]?.ToObject<JArray>(),
["client_name"] = requestBody["client_name"]?.ToString() ?? "Unknown Application",
["client_uri"] = requestBody["client_uri"]?.ToString() ?? ""
}.ToString();
}</set-body>
</return-response>
</inbound>
<backend />
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>