Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .vscode/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
"type": "promptString",
"id": "functionapp-name",
"description": "Azure Functions App Name"
},
{
"type": "promptString",
"id": "functionapp-key",
"description": "MCP Extension System Key",
"password": true
}
],
"servers": {
"remote-mcp-function": {
"type": "http",
"url": "https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp"
"url": "https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp?code=${input:functionapp-key}"
},
"local-mcp-function": {
"type": "http",
Expand Down
8 changes: 8 additions & 0 deletions azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ services:
project: ./samples/McpWeatherApp/
language: java
host: function
hooks:
prepackage:
windows:
shell: pwsh
run: cd app && npm install && npm run build
posix:
shell: sh
run: cd app && npm install && npm run build
48 changes: 44 additions & 4 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ param environmentName string
param location string
param vnetEnabled bool
param apiServiceName string = ''
param weatherServiceName string = ''
param apiUserAssignedIdentityName string = ''
param applicationInsightsName string = ''
param appServicePlanName string = ''
Expand All @@ -29,7 +30,9 @@ var abbrs = loadJsonContent('./abbreviations.json')
var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
var tags = { 'azd-env-name': environmentName }
var functionAppName = !empty(apiServiceName) ? apiServiceName : '${abbrs.webSitesFunctions}api-${resourceToken}'
var deploymentStorageContainerName = 'app-package-${take(functionAppName, 32)}-${take(toLower(uniqueString(functionAppName, resourceToken)), 7)}'
var deploymentStorageContainerName = 'app-package-${take(toLower(functionAppName), 32)}-${take(toLower(uniqueString(functionAppName, resourceToken)), 7)}'
var weatherFunctionAppName = !empty(weatherServiceName) ? weatherServiceName : '${abbrs.webSitesFunctions}weather-${resourceToken}'
var weatherDeploymentStorageContainerName = 'app-package-${take(toLower(weatherFunctionAppName), 32)}-${take(toLower(uniqueString(weatherFunctionAppName, resourceToken)), 7)}'

// Organize resources in a resource group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
Expand Down Expand Up @@ -64,6 +67,21 @@ module appServicePlan './core/host/appserviceplan.bicep' = {
}
}

// Separate plan for weather app (Flex Consumption allows only one site per plan)
module weatherAppServicePlan './core/host/appserviceplan.bicep' = {
name: 'weatherappserviceplan'
scope: rg
params: {
name: '${abbrs.webServerFarms}weather-${resourceToken}'
location: location
tags: tags
sku: {
name: 'FC1'
tier: 'FlexConsumption'
}
}
}

module api './app/api.bicep' = {
name: 'api'
scope: rg
Expand All @@ -79,8 +97,29 @@ module api './app/api.bicep' = {
deploymentStorageContainerName: deploymentStorageContainerName
identityId: apiUserAssignedIdentity.outputs.identityId
identityClientId: apiUserAssignedIdentity.outputs.identityClientId
appSettings: {
}
appSettings: {}
virtualNetworkSubnetId: !vnetEnabled ? '' : serviceVirtualNetwork.outputs.appSubnetID
}
}

// The weather app is a separate function app
module weather './app/api.bicep' = {
name: 'weather'
scope: rg
params: {
name: weatherFunctionAppName
location: location
tags: tags
applicationInsightsName: monitoring.outputs.applicationInsightsName
appServicePlanId: weatherAppServicePlan.outputs.id
runtimeName: 'java'
runtimeVersion: '17'
storageAccountName: storage.outputs.name
deploymentStorageContainerName: weatherDeploymentStorageContainerName
identityId: apiUserAssignedIdentity.outputs.identityId
identityClientId: apiUserAssignedIdentity.outputs.identityClientId
serviceName: 'weather'
appSettings: {}
virtualNetworkSubnetId: !vnetEnabled ? '' : serviceVirtualNetwork.outputs.appSubnetID
}
}
Expand All @@ -93,7 +132,7 @@ module storage './core/storage/storage-account.bicep' = {
name: !empty(storageAccountName) ? storageAccountName : '${abbrs.storageStorageAccounts}${resourceToken}'
location: location
tags: tags
containers: [{name: deploymentStorageContainerName}, {name: 'snippets'}]
containers: [{name: deploymentStorageContainerName}, {name: weatherDeploymentStorageContainerName}, {name: 'snippets'}]
publicNetworkAccess: vnetEnabled ? 'Disabled' : 'Enabled'
networkAcls: !vnetEnabled ? {} : {
defaultAction: 'Deny'
Expand Down Expand Up @@ -179,5 +218,6 @@ module appInsightsRoleAssignmentApi './core/monitor/appinsights-access.bicep' =
output AZURE_LOCATION string = location
output AZURE_TENANT_ID string = tenant().tenantId
output SERVICE_API_NAME string = api.outputs.SERVICE_API_NAME
output SERVICE_WEATHER_NAME string = weather.outputs.SERVICE_API_NAME
output AZURE_FUNCTION_NAME string = api.outputs.SERVICE_API_NAME
output AZURE_RESOURCE_GROUP string = rg.name
2 changes: 1 addition & 1 deletion samples/FunctionsMcpTool/host.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.29.0, 5.0.0)"
"version": "[4.*, 5.0.0)"
}
}
2 changes: 1 addition & 1 deletion samples/McpWeatherApp/host.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.32.0, 4.33.0)"
"version": "[4.*, 5.0.0)"
}
}
7 changes: 7 additions & 0 deletions samples/McpWeatherApp/local.settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "java"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,24 @@ public String getWeatherWidget(

executionContext.getLogger().info("GetWeatherWidget: serving weather widget UI");

// Try loading from the filesystem (app/dist/index.html placed next to the function)
// Try loading relative to the jar location (required on Azure where CWD differs)
java.io.File jarRelative = null;
try {
java.io.File jarDir = new java.io.File(
WeatherFunction.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile();
jarRelative = new java.io.File(jarDir, "app/dist/index.html");
} catch (Exception e) {
executionContext.getLogger().log(Level.WARNING, "Failed to resolve jar-relative path", e);
}
Comment thread
ahmedmuhsin marked this conversation as resolved.
if (jarRelative != null && jarRelative.exists()) {
try {
return java.nio.file.Files.readString(jarRelative.toPath(), StandardCharsets.UTF_8);
} catch (IOException e) {
executionContext.getLogger().log(Level.WARNING, "Failed to read UI from jar-relative file", e);
}
}

// Fallback: try CWD-relative path (works for local dev)
java.io.File file = new java.io.File("app/dist/index.html");
if (file.exists()) {
try {
Expand Down