From 1813a24dd987a5a23eee18b04344768d49a8df47 Mon Sep 17 00:00:00 2001 From: Will Velida Date: Wed, 2 Jul 2025 20:18:40 +1000 Subject: [PATCH 1/8] Creating Redis Cache and attaching it to APIM Signed-off-by: Will Velida --- infra/apim/apim.bicep | 16 ++++++++++++++++ infra/cache/azureRedis.bicep | 37 ++++++++++++++++++++++++++++++++++++ infra/deployMcpServer.bicep | 10 ++++++++++ 3 files changed, 63 insertions(+) create mode 100644 infra/cache/azureRedis.bicep diff --git a/infra/apim/apim.bicep b/infra/apim/apim.bicep index 2c5e38a..1deabdb 100644 --- a/infra/apim/apim.bicep +++ b/infra/apim/apim.bicep @@ -24,6 +24,9 @@ param publisherName string @description('The name of the App Insights workspace that this APIM will use') param appInsightsName string +@description('The name of the Azure Redis Cache that will be used as the external cache for APIM') +param redisCacheName string + var apimName = 'apim-${baseName}-${environmentName}' var loggerName = '${apimName}-logger' var loggerDescription = 'APIM Logger for MCP Servers' @@ -32,6 +35,10 @@ resource appInsights 'Microsoft.Insights/components@2020-02-02' existing = { name: appInsightsName } +resource redisCache 'Microsoft.Cache/redis@2024-11-01' existing = { + name: redisCacheName +} + resource apim 'Microsoft.ApiManagement/service@2024-06-01-preview' = { name: apimName location: location @@ -60,6 +67,15 @@ resource apimLogger 'Microsoft.ApiManagement/service/loggers@2024-06-01-preview' } } +resource apimCache 'Microsoft.ApiManagement/service/caches@2024-06-01-preview' = { + name: 'apim-${redisCache.name}' + parent: apim + properties: { + connectionString: '${redisCache.properties.hostName}:${redisCache.properties.sslPort},password=${redisCache.listKeys().primaryKey},ssl=True,abortConnect=False' + useFromLocation: location + } +} + @description('The resource ID of the deployed APIM Service') output id string = apim.id diff --git a/infra/cache/azureRedis.bicep b/infra/cache/azureRedis.bicep new file mode 100644 index 0000000..55fe84e --- /dev/null +++ b/infra/cache/azureRedis.bicep @@ -0,0 +1,37 @@ +@description('Base name used for all resources') +param baseName string + +@description('The Azure region where this Azure Redis Cache resource will be deployed. Default is the resource group location') +param location string = resourceGroup().location + +@description('The name of the environment that this Azure Redis Cache will be deployed to. Default value is "prod"') +@allowed([ + 'dev' + 'test' + 'prod' +]) +param environmentName string = 'prod' + +@description('The tags that will be applied to the Azure Redis Cache resource') +param tags object + +var redisCacheName string = 'cache-${baseName}-${environmentName}' + +resource redisCache 'Microsoft.Cache/redis@2024-11-01' = { + name: redisCacheName + location: location + tags: tags + properties: { + sku: { + name: 'Basic' + capacity: 0 + family: 'C' + } + } +} + +@description('The resource ID of the deployed Azure Redis Cache') +output id string = redisCache.id + +@description('The name of the deployed Azure Redis Cache') +output name string = redisCache.name diff --git a/infra/deployMcpServer.bicep b/infra/deployMcpServer.bicep index 304f0c0..bcd61f1 100644 --- a/infra/deployMcpServer.bicep +++ b/infra/deployMcpServer.bicep @@ -57,6 +57,15 @@ module containerAppEnvironment 'host/containerAppEnvironment.bicep' = { } } +module redisCache 'cache/azureRedis.bicep' = { + scope: resourceGroup + name: 'redis' + params: { + tags: tags + baseName: baseName + } +} + module mcpEntraApp 'apim/mcp-entra-app.bicep' = { scope: resourceGroup name: 'mcpEntraApp' @@ -75,6 +84,7 @@ module apim 'apim/apim.bicep' = { baseName: baseName emailAddress: emailAddress publisherName: publisherName + redisCacheName: redisCache.outputs.name } } From 48e7c6ef0bc31cf330d8af6abb0cf823bc7b25d8 Mon Sep 17 00:00:00 2001 From: Will Velida Date: Wed, 2 Jul 2025 21:08:33 +1000 Subject: [PATCH 2/8] Updating policy for caching Signed-off-by: Will Velida --- infra/apim/mcp-api.policy.xml | 138 ++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/infra/apim/mcp-api.policy.xml b/infra/apim/mcp-api.policy.xml index 18cffd6..f7c3bbf 100644 --- a/infra/apim/mcp-api.policy.xml +++ b/infra/apim/mcp-api.policy.xml @@ -12,12 +12,150 @@ {{McpClientId}} + + + + + + Accept + Authorization + + + + + + Accept + Authorization + gameId + + + + + + Accept + Authorization + roundNumber + year + sourceId + + + + + + Accept + Authorization + roundNumber + year + + + + + + Accept + Authorization + teamId + + + + + + @{ + string operationName = context.Operation.Name; + return operationName switch { + "getCurrentStandings" => "public, max-age=1800, stale-while-revalidate=300", + "getGameResult" => "public, max-age=86400, immutable", + "getPowerRankingByRoundAndYear" => "public, max-age=7200, stale-while-revalidate=600", + "getFutureTips" or "getTipsByRoundAndYear" => "public, max-age=3600, stale-while-revalidate=300", + "getTeamInfo" => "public, max-age=21600, stale-while-revalidate=1800", + _ => "public, max-age=300" + }; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @(context.Variables.ContainsKey("cache-hit") ? "HIT" : "MISS") + + + + @(context.Variables.GetValueOrDefault("cache-key", "none").ToString()) + + + + + @(context.Elapsed.TotalMilliseconds.ToString())ms + + + From 5dcee823bf9f5edd4ff80e535def2653381e89d5 Mon Sep 17 00:00:00 2001 From: Will Velida Date: Wed, 2 Jul 2025 21:21:50 +1000 Subject: [PATCH 3/8] Trying new policy Signed-off-by: Will Velida --- infra/apim/mcp-api.policy.xml | 123 ++++++++++------------------------ 1 file changed, 35 insertions(+), 88 deletions(-) diff --git a/infra/apim/mcp-api.policy.xml b/infra/apim/mcp-api.policy.xml index f7c3bbf..df6792e 100644 --- a/infra/apim/mcp-api.policy.xml +++ b/infra/apim/mcp-api.policy.xml @@ -1,8 +1,7 @@ + If validation fails, it returns HTTP 401 with proper WWW-Authenticate header.--> @@ -14,133 +13,81 @@ - - + + + caching-type="internal"> Accept Authorization - - - - Accept - Authorization - gameId - + + + + - - + + + + caching-type="internal"> Accept - Authorization - roundNumber - year - sourceId - - - - - - Accept - Authorization - roundNumber - year - - - - - - Accept - Authorization - teamId + @{ string operationName = context.Operation.Name; return operationName switch { - "getCurrentStandings" => "public, max-age=1800, stale-while-revalidate=300", - "getGameResult" => "public, max-age=86400, immutable", - "getPowerRankingByRoundAndYear" => "public, max-age=7200, stale-while-revalidate=600", - "getFutureTips" or "getTipsByRoundAndYear" => "public, max-age=3600, stale-while-revalidate=300", - "getTeamInfo" => "public, max-age=21600, stale-while-revalidate=1800", + "mcp-sse" => "public, max-age=300, stale-while-revalidate=60", + "mcp-health-check" => "public, max-age=60", + "mcp-message" => "no-cache, no-store, must-revalidate", + "mcp-streamable-get" => "public, max-age=300", + "mcp-streamable-post" => "no-cache, no-store, must-revalidate", _ => "public, max-age=300" }; } + + + - - - - - - - - + + + - - - + + + - - - - - - - - + + + + @(context.Variables.ContainsKey("cache-hit") ? "HIT" : "MISS") @@ -157,6 +104,7 @@ + @@ -169,6 +117,5 @@ - \ No newline at end of file From b0622f1c75cba2bfb0e37e793d8b46f6067efa90 Mon Sep 17 00:00:00 2001 From: Will Velida Date: Wed, 2 Jul 2025 21:34:35 +1000 Subject: [PATCH 4/8] Fixing policy Signed-off-by: Will Velida --- infra/apim/mcp-api.policy.xml | 86 +++-------------------------------- 1 file changed, 6 insertions(+), 80 deletions(-) diff --git a/infra/apim/mcp-api.policy.xml b/infra/apim/mcp-api.policy.xml index df6792e..0224210 100644 --- a/infra/apim/mcp-api.policy.xml +++ b/infra/apim/mcp-api.policy.xml @@ -11,50 +11,10 @@ {{McpClientId}} - - - - - - Accept - Authorization - - - - - - - - - - - - Accept - - - - + - @{ - string operationName = context.Operation.Name; - return operationName switch { - "mcp-sse" => "public, max-age=300, stale-while-revalidate=60", - "mcp-health-check" => "public, max-age=60", - "mcp-message" => "no-cache, no-store, must-revalidate", - "mcp-streamable-get" => "public, max-age=300", - "mcp-streamable-post" => "no-cache, no-store, must-revalidate", - _ => "public, max-age=300" - }; - } + public, max-age=300 @@ -65,44 +25,10 @@ - - - - - - - - - - - - - - - - - - - - - - - @(context.Variables.ContainsKey("cache-hit") ? "HIT" : "MISS") - - - - @(context.Variables.GetValueOrDefault("cache-key", "none").ToString()) - - - - - @(context.Elapsed.TotalMilliseconds.ToString())ms - - - + + + @(context.Elapsed.TotalMilliseconds.ToString())ms + From 8741210e956d8d11075e5d4513257618e0e1a558 Mon Sep 17 00:00:00 2001 From: Will Velida Date: Wed, 2 Jul 2025 21:42:30 +1000 Subject: [PATCH 5/8] Fixing response time header Signed-off-by: Will Velida --- infra/apim/mcp-api.policy.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/apim/mcp-api.policy.xml b/infra/apim/mcp-api.policy.xml index 0224210..73cec44 100644 --- a/infra/apim/mcp-api.policy.xml +++ b/infra/apim/mcp-api.policy.xml @@ -27,7 +27,7 @@ - @(context.Elapsed.TotalMilliseconds.ToString())ms + @(context.Elapsed.TotalMilliseconds.ToString() + "ms") From 0f663877af3364806b116657676503ceda177c5b Mon Sep 17 00:00:00 2001 From: Will Velida Date: Wed, 2 Jul 2025 22:07:53 +1000 Subject: [PATCH 6/8] Updating policy Signed-off-by: Will Velida --- infra/apim/mcp-api.policy.xml | 89 ++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/infra/apim/mcp-api.policy.xml b/infra/apim/mcp-api.policy.xml index 73cec44..7421e59 100644 --- a/infra/apim/mcp-api.policy.xml +++ b/infra/apim/mcp-api.policy.xml @@ -1,7 +1,7 @@ + Azure API Management Policy for MCP Server + Handles authentication and operation-specific caching for MCP endpoints +--> @@ -11,10 +11,55 @@ {{McpClientId}} + + + + + + + Accept + + + + + + + Accept + Authorization + * + + + - + - public, max-age=300 + @{ + string operationName = context.Operation.Name; + if (operationName == "mcp-health-check") { + return "public, max-age=30"; + } else if (operationName == "mcp-streamable-get") { + return "public, max-age=300, stale-while-revalidate=60"; + } else if (operationName == "mcp-sse") { + return "no-cache, no-store, must-revalidate"; + } else if (operationName == "mcp-message" || operationName == "mcp-streamable-post") { + return "no-cache, no-store, must-revalidate"; + } else { + return "public, max-age=60"; + } + } + + + + + @(context.Operation.Name) @@ -25,10 +70,42 @@ - + + + + + + + + + + + + + + + + + + + + + + @(context.Variables.ContainsKey("cache-hit") ? "HIT" : "MISS") + + + + @(context.Variables.GetValueOrDefault("cache-key", "none").ToString()) + + @(context.Elapsed.TotalMilliseconds.ToString() + "ms") + + + + AFL-MCP-Server + From 66db0aaf300a8b63565b8681bd1933ab3a858eb4 Mon Sep 17 00:00:00 2001 From: Will Velida Date: Wed, 2 Jul 2025 22:30:08 +1000 Subject: [PATCH 7/8] Adding single cache lookup for cacheable operations Signed-off-by: Will Velida --- infra/apim/mcp-api.policy.xml | 101 +++++++++++++--------------------- 1 file changed, 37 insertions(+), 64 deletions(-) diff --git a/infra/apim/mcp-api.policy.xml b/infra/apim/mcp-api.policy.xml index 7421e59..6eb709c 100644 --- a/infra/apim/mcp-api.policy.xml +++ b/infra/apim/mcp-api.policy.xml @@ -1,63 +1,47 @@ - + - + + {{McpClientId}} - + + - + - - - - Accept - - - - - - - Accept + + Authorization * - + @{ string operationName = context.Operation.Name; - if (operationName == "mcp-health-check") { - return "public, max-age=30"; - } else if (operationName == "mcp-streamable-get") { - return "public, max-age=300, stale-while-revalidate=60"; - } else if (operationName == "mcp-sse") { - return "no-cache, no-store, must-revalidate"; - } else if (operationName == "mcp-message" || operationName == "mcp-streamable-post") { - return "no-cache, no-store, must-revalidate"; - } else { - return "public, max-age=60"; + switch (operationName) { + case "mcp-health-check": + return "public, max-age=30"; + case "mcp-streamable-get": + return "public, max-age=300"; + case "mcp-sse": + case "mcp-message": + case "mcp-streamable-post": + return "no-cache, no-store, must-revalidate"; + default: + return "public, max-age=60"; } } - + @(context.Operation.Name) @@ -70,42 +54,31 @@ - + - - - - - - - - - - - - - - + + + @{ + string operationName = context.Operation.Name; + if (operationName == "mcp-health-check") { + return 30; + } else if (operationName == "mcp-streamable-get") { + return 300; + } + return 60; + } + - + - @(context.Variables.ContainsKey("cache-hit") ? "HIT" : "MISS") - - - - @(context.Variables.GetValueOrDefault("cache-key", "none").ToString()) + @(context.Variables.ContainsKey("cachedResponse") ? "HIT" : "MISS") @(context.Elapsed.TotalMilliseconds.ToString() + "ms") - - - - AFL-MCP-Server - From c66083612cb9d2e2a232b508aa7de18ccf6d3036 Mon Sep 17 00:00:00 2001 From: Will Velida Date: Wed, 2 Jul 2025 22:43:51 +1000 Subject: [PATCH 8/8] Reverting to original policy for now Signed-off-by: Will Velida --- infra/apim/mcp-api.policy.xml | 77 ++++------------------------------- 1 file changed, 8 insertions(+), 69 deletions(-) diff --git a/infra/apim/mcp-api.policy.xml b/infra/apim/mcp-api.policy.xml index 6eb709c..18cffd6 100644 --- a/infra/apim/mcp-api.policy.xml +++ b/infra/apim/mcp-api.policy.xml @@ -1,86 +1,24 @@ - + - - + {{McpClientId}} - - - - - - - - Authorization - * - - - - - - - @{ - string operationName = context.Operation.Name; - switch (operationName) { - case "mcp-health-check": - return "public, max-age=30"; - case "mcp-streamable-get": - return "public, max-age=300"; - case "mcp-sse": - case "mcp-message": - case "mcp-streamable-post": - return "no-cache, no-store, must-revalidate"; - default: - return "public, max-age=60"; - } - } - - - - - @(context.Operation.Name) - + - - - - - - - - @{ - string operationName = context.Operation.Name; - if (operationName == "mcp-health-check") { - return 30; - } else if (operationName == "mcp-streamable-get") { - return 300; - } - return 60; - } - - - - - - - @(context.Variables.ContainsKey("cachedResponse") ? "HIT" : "MISS") - - - - @(context.Elapsed.TotalMilliseconds.ToString() + "ms") - - @@ -93,5 +31,6 @@ + \ No newline at end of file