
{"id":53,"date":"2026-09-10T14:40:40","date_gmt":"2026-09-10T14:40:40","guid":{"rendered":"https:\/\/roboticsmaestro.com\/ai\/?p=53"},"modified":"2026-09-10T16:39:55","modified_gmt":"2026-09-10T16:39:55","slug":"instructions-to-setup-mcp-server-with-function-app-for-microsoft-foundry-agent-tools-with-app-insights","status":"publish","type":"post","link":"https:\/\/roboticsmaestro.com\/ai\/blog\/2026\/09\/10\/instructions-to-setup-mcp-server-with-function-app-for-microsoft-foundry-agent-tools-with-app-insights\/","title":{"rendered":"Instructions to setup mcp server with function app for microsoft foundry agent tools with app insights"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">walk through this in a&nbsp;<strong>clear, step-by-step<\/strong>&nbsp;way so you can set up a&nbsp;<strong>new MCP (Microsoft Foundry Custom Plugin)<\/strong>&nbsp;using an&nbsp;<strong>Azure Function App<\/strong>, integrate it with&nbsp;<strong>Microsoft Foundry Tools<\/strong>, and enable&nbsp;<strong>Application Insights<\/strong>&nbsp;for monitoring.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I\u2019ll break it into&nbsp;<strong>three main phases<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Azure Function App Setup<\/strong><\/li>\n\n\n\n<li><strong>MCP (Custom Tool) Registration in Foundry<\/strong><\/li>\n\n\n\n<li><strong>Application Insights Integration<\/strong><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1. Create and Configure the Azure Function App<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1 \u2014 Create a Resource Group<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>az group create --name my-foundry-mcp-rg --location eastus<br><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2 \u2014 Create a Storage Account<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">BashCopy code<code>az storage account create \\\n  --name mystorageacctmcp \\\n  --location eastus \\\n  --resource-group my-foundry-mcp-rg \\\n  --sku Standard_LRS\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 3 \u2014 Create the Function App<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>az functionapp create \\<br>  --resource-group my-foundry-mcp-rg \\<br>  --consumption-plan-location eastus \\<br>  --runtime python \\<br>  --runtime-version 3.10 \\<br>  --functions-version 4 \\<br>  --name my-foundry-mcp-func \\<br>  --storage-account mystorageacctmcp<br><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2. Build the MCP Logic in the Function App<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Example:&nbsp;<code>HttpTrigger<\/code>&nbsp;Function for File Retrieval<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<br><br><code>import logging<br>import azure.functions as func<br>from azure.storage.blob import BlobServiceClient<br>import json<br><br>def main(req: func.HttpRequest) -> func.HttpResponse:<br>    logging.info(\"MCP Function triggered.\")<br><br>    source_type = req.params.get('sourceType')<br>    if not source_type:<br>        return func.HttpResponse(\"Missing 'sourceType' parameter\", status_code=400)<br><br>    results = []<br><br>    if source_type.lower() == \"blob\":<br>        blob_service_client = BlobServiceClient.from_connection_string(<br>            \"&lt;BLOB_CONNECTION_STRING>\"<br>        )<br>        container_client = blob_service_client.get_container_client(\"my-container\")<br>        for blob in container_client.list_blobs():<br>            results.append({<br>                \"sourceType\": \"Blob\",<br>                \"fileName\": blob.name.split(\"\/\")[-1],<br>                \"filePath\": blob.name<br>            })<br><br>    elif source_type.lower() == \"sharepoint\":<br>        # Placeholder for SharePoint retrieval logic<br>        results.append({<br>            \"sourceType\": \"SharePoint\",<br>            \"fileName\": \"example.docx\",<br>            \"filePath\": \"\/Shared Documents\/example.docx\"<br>        })<br><br>    return func.HttpResponse(<br>        json.dumps(results),<br>        mimetype=\"application\/json\",<br>        status_code=200<br>    )<br><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Deploy this function:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>func azure functionapp publish my-foundry-mcp-func<br><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3. Enable Application Insights<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1 \u2014 Create Application Insights<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">BashCopy code<code>az monitor app-insights component create \\\n  --app my-foundry-mcp-ai \\\n  --location eastus \\\n  --resource-group my-foundry-mcp-rg\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2 \u2014 Link to Function App<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>az functionapp update \\<br>  --name my-foundry-mcp-func \\<br>  --resource-group my-foundry-mcp-rg \\<br>  --set applicationInsights.key=&lt;YOUR_APP_INSIGHTS_KEY><br><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 3 \u2014 Add Telemetry in Code<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>from opencensus.ext.azure.log_exporter import AzureLogHandler<br><br>logger = logging.getLogger(__name__)<br>logger.addHandler(AzureLogHandler(<br>    connection_string='InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY>')<br>)<br><br>logger.warning(\"Custom MCP function executed.\")<br><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4. Register the MCP in Microsoft Foundry Tools<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1 \u2014 Go to Foundry Agent Service<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Navigate to\u00a0<strong>Foundry \u2192 Agent Tools \u2192 Add Tool<\/strong>.<\/li>\n\n\n\n<li>Select\u00a0<strong>Custom HTTP Tool<\/strong>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2 \u2014 Configure Tool<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Name<\/strong>:\u00a0<code>RetrieveFilesTool<\/code><\/li>\n\n\n\n<li><strong>Endpoint URL<\/strong>:\u00a0<code>https:\/\/my-foundry-mcp-func.azurewebsites.net\/api\/&lt;function-name><\/code><\/li>\n\n\n\n<li><strong>Method<\/strong>:\u00a0<code>GET<\/code><\/li>\n\n\n\n<li><strong>Parameters<\/strong>:\n<ul class=\"wp-block-list\">\n<li><code>sourceType<\/code>\u00a0(string, required:\u00a0<code>\"Blob\"<\/code>\u00a0or\u00a0<code>\"SharePoint\"<\/code>)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 3 \u2014 Test in Foundry<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Run a test query:<br><em>&#8220;Retrieve all file paths from Blob storage&#8221;<\/em><\/li>\n\n\n\n<li>Foundry will call your Function App MCP and return JSON results.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">&#x2705;&nbsp;<strong>At this point<\/strong>&nbsp;you have:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>An\u00a0<strong>Azure Function App<\/strong>\u00a0acting as your MCP backend.<\/li>\n\n\n\n<li><strong>Application Insights<\/strong>\u00a0capturing telemetry.<\/li>\n\n\n\n<li>The MCP registered as a\u00a0<strong>Foundry Tool<\/strong>\u00a0for your agents<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;by taking what we\u2019ve built and making it&nbsp;<strong>production-ready<\/strong>&nbsp;so your MCP + Azure Function App + Foundry integration is reliable, secure, and observable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I\u2019ll walk you through the&nbsp;<strong>next steps<\/strong>: securing the function, improving telemetry, and validating the MCP inside Foundry.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5. Secure the Azure Function App<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">By default, Azure Functions can be called publicly. For a production MCP, you\u2019ll want to&nbsp;<strong>restrict access<\/strong>&nbsp;so only Foundry can call it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Option A \u2014 Function Key Authentication<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Get the function key:BashCopy code<code>az functionapp function keys list \\ --name my-foundry-mcp-func \\ --resource-group my-foundry-mcp-rg \\ --function-name &lt;function-name><\/code><\/li>\n\n\n\n<li>In Foundry Tool configuration, add a header:Copy code<code>x-functions-key: &lt;FUNCTION_KEY><\/code><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Option B \u2014 Azure AD Authentication (Recommended)<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Enable\u00a0<strong>Azure AD Authentication<\/strong>\u00a0in the Function App:BashCopy code<code>az webapp auth microsoft update \\ --resource-group my-foundry-mcp-rg \\ --name my-foundry-mcp-func \\ --enabled true<\/code><\/li>\n\n\n\n<li>Register Foundry as an\u00a0<strong>App Registration<\/strong>\u00a0in Azure AD and grant it API permissions to call your function.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6. Enhance Application Insights Telemetry<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We can make telemetry more useful by tracking:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Request parameters<\/strong>\u00a0(without sensitive data)<\/li>\n\n\n\n<li><strong>Execution time<\/strong><\/li>\n\n\n\n<li><strong>Error details<\/strong><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Example:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>import time\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\n\nlogger = logging.getLogger(__name__)\nlogger.addHandler(AzureLogHandler(\n    connection_string='InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY&gt;')\n)\n\ndef main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType')\n        logger.info(f\"Request received for sourceType={source_type}\")\n\n        # ... existing logic ...\n\n        duration = time.time() - start_time\n        logger.info(f\"Execution completed in {duration:.2f}s\")\n        return func.HttpResponse(json.dumps(results), mimetype=\"application\/json\")\n\n    except Exception as e:\n        logger.error(f\"Error occurred: {str(e)}\", exc_info=True)\n        return func.HttpResponse(\"Internal Server Error\", status_code=500)\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7. Validate MCP in Foundry<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1 \u2014 Test Tool in Foundry<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Go to\u00a0<strong>Foundry \u2192 Agent Tools \u2192 Your MCP Tool \u2192 Test<\/strong>.<\/li>\n\n\n\n<li>Input:JsonCopy code<code>{ \"sourceType\": \"Blob\" }<\/code><\/li>\n\n\n\n<li>Verify JSON output matches your schema.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2 \u2014 Use in an Agent Prompt<\/strong>&nbsp;Example:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><em>&#8220;List all file paths from Blob storage and SharePoint for 2026 reports.&#8221;<\/em><\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Foundry will:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Call your MCP Function App twice (once for Blob, once for SharePoint).<\/li>\n\n\n\n<li>Merge results into the agent\u2019s context.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8. Optional \u2014 Multi-Source Retrieval in One Call<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you want&nbsp;<strong>one MCP call<\/strong>&nbsp;to fetch from both Blob and SharePoint:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>if source_type.lower() == \"all\":\n    results.extend(get_blob_files())\n    results.extend(get_sharepoint_files())\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then in Foundry, you can pass:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{ \"sourceType\": \"all\" }\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>9. Deployment &amp; CI\/CD<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For maintainability:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use\u00a0<strong>Azure DevOps Pipelines<\/strong>\u00a0or\u00a0<strong>GitHub Actions<\/strong>\u00a0to deploy your Function App automatically.<\/li>\n\n\n\n<li>Store secrets in\u00a0<strong>Azure Key Vault<\/strong>\u00a0and reference them in your Function App settings.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, I can now prepare a&nbsp;<strong>full production-ready MCP Function App template<\/strong>&nbsp;with:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Blob + SharePoint integration<\/li>\n\n\n\n<li>Secure Azure AD auth<\/li>\n\n\n\n<li>Application Insights logging<\/li>\n\n\n\n<li>Ready-to-import Foundry Tool JSON config<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">That would give you a&nbsp;<strong>plug-and-play<\/strong>&nbsp;starting point.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and put everything together into a&nbsp;<strong>production-ready MCP Function App template<\/strong>&nbsp;that you can deploy, secure, and plug directly into Microsoft Foundry as a tool.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This will include:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Blob + SharePoint retrieval<\/strong>\u00a0in one endpoint<\/li>\n\n\n\n<li><strong>Azure AD authentication<\/strong>\u00a0(recommended for security)<\/li>\n\n\n\n<li><strong>Application Insights telemetry<\/strong><\/li>\n\n\n\n<li><strong>Foundry Tool JSON config<\/strong>\u00a0for quick import<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1. Folder Structure<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>mcp-function-app\/<br>\u2502<br>\u251c\u2500\u2500 host.json<br>\u251c\u2500\u2500 local.settings.json<br>\u251c\u2500\u2500 requirements.txt<br>\u2514\u2500\u2500 RetrieveFiles\/__init__.py<br><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2.&nbsp;<code>requirements.txt<\/code><\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">TxtCopy code<code>azure-functions\nazure-storage-blob\noffice365-rest-python-client\nopencensus-ext-azure\nopencensus\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3.&nbsp;<code>host.json<\/code><\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"version\": \"2.0\",\n  \"logging\": {\n    \"applicationInsights\": {\n      \"samplingSettings\": {\n        \"isEnabled\": true,\n        \"maxTelemetryItemsPerSecond\": 5\n      }\n    }\n  }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4.&nbsp;<code>local.settings.json<\/code><\/strong>&nbsp;(local dev only \u2014 don\u2019t commit secrets)<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"IsEncrypted\": false,\n  \"Values\": {\n    \"AzureWebJobsStorage\": \"UseDevelopmentStorage=true\",\n    \"FUNCTIONS_WORKER_RUNTIME\": \"python\",\n    \"APPINSIGHTS_INSTRUMENTATIONKEY\": \"&lt;YOUR_APP_INSIGHTS_KEY&gt;\",\n    \"BLOB_CONNECTION_STRING\": \"&lt;BLOB_CONN_STRING&gt;\",\n    \"SHAREPOINT_SITE_URL\": \"https:\/\/contoso.sharepoint.com\/sites\/ProjectDocs\",\n    \"SHAREPOINT_CLIENT_ID\": \"&lt;CLIENT_ID&gt;\",\n    \"SHAREPOINT_CLIENT_SECRET\": \"&lt;CLIENT_SECRET&gt;\",\n    \"SHAREPOINT_TENANT\": \"contoso.onmicrosoft.com\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5.&nbsp;<code>RetrieveFiles\/__init__.py<\/code><\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>import logging\nimport json\nimport time\nimport azure.functions as func\nfrom azure.storage.blob import BlobServiceClient\nfrom office365.sharepoint.client_context import ClientContext\nfrom office365.runtime.auth.client_credential import ClientCredential\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\n\n# Setup logging with Application Insights\nlogger = logging.getLogger(__name__)\nlogger.addHandler(AzureLogHandler(\n    connection_string=f\"InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY&gt;\"\n))\n\ndef get_blob_files():\n    results = []\n    blob_service_client = BlobServiceClient.from_connection_string(\n        \"&lt;BLOB_CONNECTION_STRING&gt;\"\n    )\n    container_client = blob_service_client.get_container_client(\"my-container\")\n    for blob in container_client.list_blobs():\n        results.append({\n            \"sourceType\": \"Blob\",\n            \"fileName\": blob.name.split(\"\/\")[-1],\n            \"filePath\": blob.name\n        })\n    return results\n\ndef get_sharepoint_files():\n    results = []\n    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL&gt;\").with_credentials(\n        ClientCredential(\"&lt;SHAREPOINT_CLIENT_ID&gt;\", \"&lt;SHAREPOINT_CLIENT_SECRET&gt;\")\n    )\n    folder = ctx.web.get_folder_by_server_relative_url(\"\/Shared Documents\/Reports\/2026\")\n    files = folder.files\n    ctx.load(files)\n    ctx.execute_query()\n    for f in files:\n        results.append({\n            \"sourceType\": \"SharePoint\",\n            \"fileName\": f.properties[\"Name\"],\n            \"filePath\": f.properties[\"ServerRelativeUrl\"]\n        })\n    return results\n\ndef main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType', 'all').lower()\n        logger.info(f\"Request received for sourceType={source_type}\")\n\n        results = []\n        if source_type in [\"blob\", \"all\"]:\n            results.extend(get_blob_files())\n        if source_type in [\"sharepoint\", \"all\"]:\n            results.extend(get_sharepoint_files())\n\n        duration = time.time() - start_time\n        logger.info(f\"Execution completed in {duration:.2f}s with {len(results)} results\")\n\n        return func.HttpResponse(\n            json.dumps(results),\n            mimetype=\"application\/json\",\n            status_code=200\n        )\n\n    except Exception as e:\n        logger.error(f\"Error occurred: {str(e)}\", exc_info=True)\n        return func.HttpResponse(\"Internal Server Error\", status_code=500)\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6. Deploy to Azure<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">BashCopy code<code>func azure functionapp publish my-foundry-mcp-func\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7. Secure with Azure AD<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">BashCopy code<code>az webapp auth microsoft update \\\n  --resource-group my-foundry-mcp-rg \\\n  --name my-foundry-mcp-func \\\n  --<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">&#8211;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and finish the&nbsp;<strong>Azure AD security setup<\/strong>&nbsp;and the&nbsp;<strong>Foundry Tool integration<\/strong>&nbsp;so your MCP Function App is fully production-ready.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8. Complete Azure AD Authentication for the Function App<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We\u2019ll lock down your Function App so only authenticated requests from Foundry (or your trusted apps) can reach it.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 1 \u2014 Enable Azure AD Authentication<\/strong><\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">BashCopy code<code>az webapp auth microsoft update \\\n  --resource-group my-foundry-mcp-rg \\\n  --name my-foundry-mcp-func \\\n  --enabled true \\\n  --client-id &lt;YOUR_AAD_APP_CLIENT_ID&gt; \\\n  --issuer https:\/\/login.microsoftonline.com\/&lt;YOUR_TENANT_ID&gt;\/v2.0\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 2 \u2014 Register the Function App in Azure AD<\/strong><\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Go to\u00a0<strong>Azure Portal \u2192 Azure Active Directory \u2192 App registrations \u2192 New registration<\/strong>.<\/li>\n\n\n\n<li>Name:\u00a0<code>Foundry-MCP-Function<\/code><\/li>\n\n\n\n<li>Redirect URI: leave blank for now (not needed for server-to-server).<\/li>\n\n\n\n<li>Copy the\u00a0<strong>Application (client) ID<\/strong>\u00a0and\u00a0<strong>Directory (tenant) ID<\/strong>.<\/li>\n\n\n\n<li>Create a\u00a0<strong>Client Secret<\/strong>\u00a0under\u00a0<strong>Certificates &amp; secrets<\/strong>.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 3 \u2014 Configure Foundry to Use Azure AD Token<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>In Foundry\u2019s\u00a0<strong>Tool configuration<\/strong>, set\u00a0<strong>Authentication<\/strong>\u00a0to\u00a0<strong>OAuth 2.0 Client Credentials<\/strong>.<\/li>\n\n\n\n<li>Provide:\n<ul class=\"wp-block-list\">\n<li><strong>Token URL<\/strong>:\u00a0<code>https:\/\/login.microsoftonline.com\/&lt;TENANT_ID>\/oauth2\/v2.0\/token<\/code><\/li>\n\n\n\n<li><strong>Client ID<\/strong>:\u00a0<code>&lt;YOUR_AAD_APP_CLIENT_ID><\/code><\/li>\n\n\n\n<li><strong>Client Secret<\/strong>:\u00a0<code>&lt;YOUR_AAD_APP_CLIENT_SECRET><\/code><\/li>\n\n\n\n<li><strong>Scope<\/strong>:\u00a0<code>api:\/\/&lt;YOUR_FUNCTION_APP_CLIENT_ID>\/.default<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Step 4 \u2014 Restrict Function App to Authenticated Calls<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In&nbsp;<strong>Azure Portal \u2192 Function App \u2192 Authentication<\/strong>, set:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Unauthenticated requests<\/strong>\u00a0\u2192\u00a0<strong>HTTP 401 Unauthorized<\/strong>.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>9. Foundry Tool JSON Configuration<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s a ready-to-import Foundry Tool definition for your MCP:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"name\": \"RetrieveFilesTool\",\n  \"description\": \"Retrieves file paths from Azure Blob Storage and SharePoint.\",\n  \"type\": \"http\",\n  \"method\": \"GET\",\n  \"url\": \"https:\/\/my-foundry-mcp-func.azurewebsites.net\/api\/RetrieveFiles\",\n  \"authentication\": {\n    \"type\": \"oauth2\",\n    \"grantType\": \"client_credentials\",\n    \"tokenUrl\": \"https:\/\/login.microsoftonline.com\/&lt;TENANT_ID&gt;\/oauth2\/v2.0\/token\",\n    \"clientId\": \"&lt;YOUR_AAD_APP_CLIENT_ID&gt;\",\n    \"clientSecret\": \"&lt;YOUR_AAD_APP_CLIENT_SECRET&gt;\",\n    \"scope\": \"api:\/\/&lt;YOUR_FUNCTION_APP_CLIENT_ID&gt;\/.default\"\n  },\n  \"parameters\": [\n    {\n      \"name\": \"sourceType\",\n      \"type\": \"string\",\n      \"required\": true,\n      \"description\": \"Data source to retrieve from: 'Blob', 'SharePoint', or 'all'.\"\n    }\n  ]\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>10. Test the End-to-End Flow<\/strong><\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>In Foundry<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Go to\u00a0<strong>Agent Tools \u2192 RetrieveFilesTool \u2192 Test<\/strong>.<\/li>\n\n\n\n<li>Input:JsonCopy code<code>{ \"sourceType\": \"all\" }<\/code><\/li>\n\n\n\n<li>You should see a JSON array of file paths from both Blob and SharePoint.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>In Application Insights<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Go to\u00a0<strong>Logs<\/strong>\u00a0and run:KustoCopy code<code>traces | where timestamp > ago(5m) | order by timestamp desc<\/code><\/li>\n\n\n\n<li>You should see telemetry for each MCP call, including execution time and any errors.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">&#x2705;&nbsp;<strong>At this point<\/strong>, you have:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Secure Azure Function App<\/strong>\u00a0with Azure AD auth<\/li>\n\n\n\n<li><strong>Blob + SharePoint retrieval<\/strong>\u00a0in one endpoint<\/li>\n\n\n\n<li><strong>Application Insights telemetry<\/strong>\u00a0for monitoring<\/li>\n\n\n\n<li><strong>Foundry Tool JSON<\/strong>\u00a0ready for import and use<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, I can now&nbsp;<strong>extend this MCP<\/strong>&nbsp;so it supports:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Filtering by file extension or date<\/strong><\/li>\n\n\n\n<li><strong>Paginated results for large datasets<\/strong><\/li>\n\n\n\n<li><strong>Direct file content retrieval<\/strong>\u00a0for Foundry context grounding<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">That would make it even more powerful for your agents.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and enhance your MCP so it\u2019s not just returning&nbsp;<em>all<\/em>&nbsp;files, but can:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Filter by file extension<\/strong>\u00a0(e.g.,\u00a0<code>.pdf<\/code>,\u00a0<code>.xlsx<\/code>)<\/li>\n\n\n\n<li><strong>Filter by modified date range<\/strong><\/li>\n\n\n\n<li><strong>Support pagination<\/strong>\u00a0for large datasets<\/li>\n\n\n\n<li><strong>Optionally return file content<\/strong>\u00a0for Foundry grounding<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">This will make your MCP much more&nbsp;<strong>agent-friendly<\/strong>&nbsp;and efficient.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1. Updated MCP Function Logic<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the improved&nbsp;<code>RetrieveFiles\/__init__.py<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>import logging\nimport json\nimport time\nimport azure.functions as func\nfrom datetime import datetime\nfrom azure.storage.blob import BlobServiceClient\nfrom office365.sharepoint.client_context import ClientContext\nfrom office365.runtime.auth.client_credential import ClientCredential\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\n\n# Setup logging with Application Insights\nlogger = logging.getLogger(__name__)\nlogger.addHandler(AzureLogHandler(\n    connection_string=f\"InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY&gt;\"\n))\n\ndef get_blob_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n    container_client = blob_service_client.get_container_client(\"my-container\")\n\n    blobs = list(container_client.list_blobs())\n    filtered = []\n    for blob in blobs:\n        if extension_filter and not blob.name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            last_modified = blob.last_modified\n            if date_from and last_modified &lt; date_from:\n                continue\n            if date_to and last_modified &gt; date_to:\n                continue\n        filtered.append(blob)\n\n    for blob in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"Blob\",\n            \"fileName\": blob.name.split(\"\/\")[-1],\n            \"filePath\": blob.name,\n            \"lastModified\": blob.last_modified.isoformat()\n        })\n    return results\n\ndef get_sharepoint_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL&gt;\").with_credentials(\n        ClientCredential(\"&lt;SHAREPOINT_CLIENT_ID&gt;\", \"&lt;SHAREPOINT_CLIENT_SECRET&gt;\")\n    )\n    folder = ctx.web.get_folder_by_server_relative_url(\"\/Shared Documents\/Reports\/2026\")\n    files = folder.files\n    ctx.load(files)\n    ctx.execute_query()\n\n    filtered = []\n    for f in files:\n        name = f.properties[\"Name\"]\n        if extension_filter and not name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            modified = f.properties[\"TimeLastModified\"]\n            if date_from and modified &lt; date_from:\n                continue\n            if date_to and modified &gt; date_to:\n                continue\n        filtered.append(f)\n\n    for f in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"SharePoint\",\n            \"fileName\": f.properties[\"Name\"],\n            \"filePath\": f.properties[\"ServerRelativeUrl\"],\n            \"lastModified\": str(f.properties[\"TimeLastModified\"])\n        })\n    return results\n\ndef main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType', 'all').lower()\n        extension_filter = req.params.get('extension')\n        date_from_str = req.params.get('dateFrom')\n        date_to_str = req.params.get('dateTo')\n        skip = int(req.params.get('skip', 0))\n        limit = int(req.params.get('limit', 50))\n        include_content = req.params.get('includeContent', 'false').lower() == 'true'\n\n        date_from = datetime.fromisoformat(date_from_str) if date_from_str else None\n        date_to = datetime.fromisoformat(date_to_str) if date_to_str else None\n\n        logger.info(f\"Request: sourceType={source_type}, ext={extension_filter}, skip={skip}, limit={limit}\")\n\n        results = []\n        if source_type in [\"blob\", \"all\"]:\n            results.extend(get_blob_files(extension_filter, date_from, date_to, skip, limit))\n        if source_type in [\"sharepoint\", \"all\"]:\n            results.extend(get_sharepoint_files(extension_filter, date_from, date_to, skip, limit))\n\n        # Optional: include file content for grounding\n        if include_content:\n            for item in results:\n                if item[\"sourceType\"] == \"Blob\":\n                    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n                    blob_client = blob_service_client.get_blob_client(container=\"my-container\", blob=item[\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Undo<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;<strong>proceed<\/strong>&nbsp;and finish the&nbsp;<strong>enhanced MCP function<\/strong>&nbsp;so it fully supports:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>File content retrieval<\/strong>\u00a0for grounding in Foundry<\/li>\n\n\n\n<li><strong>Filtering<\/strong>\u00a0by extension and date<\/li>\n\n\n\n<li><strong>Pagination<\/strong>\u00a0for large datasets<\/li>\n\n\n\n<li><strong>Clean JSON output<\/strong><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1. Completing the&nbsp;<code>includeContent<\/code>&nbsp;Logic<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the&nbsp;<strong>final version<\/strong>&nbsp;of&nbsp;<code>RetrieveFiles\/__init__.py<\/code>&nbsp;with the content retrieval section finished:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>import logging\nimport json\nimport time\nimport azure.functions as func\nfrom datetime import datetime\nfrom azure.storage.blob import BlobServiceClient\nfrom office365.sharepoint.client_context import ClientContext\nfrom office365.runtime.auth.client_credential import ClientCredential\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\nimport base64\n\n# Setup logging with Application Insights\nlogger = logging.getLogger(__name__)\nlogger.addHandler(AzureLogHandler(\n    connection_string=f\"InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY&gt;\"\n))\n\ndef get_blob_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n    container_client = blob_service_client.get_container_client(\"my-container\")\n\n    blobs = list(container_client.list_blobs())\n    filtered = []\n    for blob in blobs:\n        if extension_filter and not blob.name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            last_modified = blob.last_modified\n            if date_from and last_modified &lt; date_from:\n                continue\n            if date_to and last_modified &gt; date_to:\n                continue\n        filtered.append(blob)\n\n    for blob in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"Blob\",\n            \"fileName\": blob.name.split(\"\/\")[-1],\n            \"filePath\": blob.name,\n            \"lastModified\": blob.last_modified.isoformat()\n        })\n    return results\n\ndef get_sharepoint_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL&gt;\").with_credentials(\n        ClientCredential(\"&lt;SHAREPOINT_CLIENT_ID&gt;\", \"&lt;SHAREPOINT_CLIENT_SECRET&gt;\")\n    )\n    folder = ctx.web.get_folder_by_server_relative_url(\"\/Shared Documents\/Reports\/2026\")\n    files = folder.files\n    ctx.load(files)\n    ctx.execute_query()\n\n    filtered = []\n    for f in files:\n        name = f.properties[\"Name\"]\n        if extension_filter and not name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            modified = f.properties[\"TimeLastModified\"]\n            if date_from and modified &lt; date_from:\n                continue\n            if date_to and modified &gt; date_to:\n                continue\n        filtered.append(f)\n\n    for f in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"SharePoint\",\n            \"fileName\": f.properties[\"Name\"],\n            \"filePath\": f.properties[\"ServerRelativeUrl\"],\n            \"lastModified\": str(f.properties[\"TimeLastModified\"])\n        })\n    return results\n\ndef main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType', 'all').lower()\n        extension_filter = req.params.get('extension')\n        date_from_str = req.params.get('dateFrom')\n        date_to_str = req.params.get('dateTo')\n        skip = int(req.params.get('skip', 0))\n        limit = int(req.params.get('limit', 50))\n        include_content = req.params.get('includeContent', 'false').lower() == 'true'\n\n        date_from = datetime.fromisoformat(date_from_str) if date_from_str else None\n        date_to = datetime.fromisoformat(date_to_str) if date_to_str else None\n\n        logger.info(f\"Request: sourceType={source_type}, ext={extension_filter}, skip={skip}, limit={limit}, includeContent={include_content}\")\n\n        results = []\n        if source_type in [\"blob\", \"all\"]:\n            results.extend(get_blob_files(extension_filter, date_from, date_to, skip, limit))\n        if source_type in [\"sharepoint\", \"all\"]:\n            results.extend(get_sharepoint_files(extension_filter, date_from, date_to, skip, limit))\n\n        # Optional: include file content for grounding\n        if include_content:\n            for item in results:\n                if item[\"sourceType\"] == \"Blob\":\n                    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n                    blob_client = blob_service_client.get_blob_client(container=\"my-container\", blob=item[\"filePath\"])\n                    content = blob_client.download_blob().readall<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Undo<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and finish the&nbsp;<code>includeContent<\/code>&nbsp;logic so your MCP can return&nbsp;<strong>base64-encoded file content<\/strong>&nbsp;for Foundry grounding, while still supporting filtering and pagination.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Final Completed MCP Function<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>import logging\nimport json\nimport time\nimport azure.functions as func\nfrom datetime import datetime\nfrom azure.storage.blob import BlobServiceClient\nfrom office365.sharepoint.client_context import ClientContext\nfrom office365.runtime.auth.client_credential import ClientCredential\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\nimport base64\n\n# Setup logging with Application Insights\nlogger = logging.getLogger(__name__)\nlogger.addHandler(AzureLogHandler(\n    connection_string=f\"InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY&gt;\"\n))\n\ndef get_blob_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n    container_client = blob_service_client.get_container_client(\"my-container\")\n\n    blobs = list(container_client.list_blobs())\n    filtered = []\n    for blob in blobs:\n        if extension_filter and not blob.name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            last_modified = blob.last_modified\n            if date_from and last_modified &lt; date_from:\n                continue\n            if date_to and last_modified &gt; date_to:\n                continue\n        filtered.append(blob)\n\n    for blob in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"Blob\",\n            \"fileName\": blob.name.split(\"\/\")[-1],\n            \"filePath\": blob.name,\n            \"lastModified\": blob.last_modified.isoformat()\n        })\n    return results\n\ndef get_sharepoint_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL&gt;\").with_credentials(\n        ClientCredential(\"&lt;SHAREPOINT_CLIENT_ID&gt;\", \"&lt;SHAREPOINT_CLIENT_SECRET&gt;\")\n    )\n    folder = ctx.web.get_folder_by_server_relative_url(\"\/Shared Documents\/Reports\/2026\")\n    files = folder.files\n    ctx.load(files)\n    ctx.execute_query()\n\n    filtered = []\n    for f in files:\n        name = f.properties[\"Name\"]\n        if extension_filter and not name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            modified = f.properties[\"TimeLastModified\"]\n            if date_from and modified &lt; date_from:\n                continue\n            if date_to and modified &gt; date_to:\n                continue\n        filtered.append(f)\n\n    for f in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"SharePoint\",\n            \"fileName\": f.properties[\"Name\"],\n            \"filePath\": f.properties[\"ServerRelativeUrl\"],\n            \"lastModified\": str(f.properties[\"TimeLastModified\"])\n        })\n    return results\n\ndef main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType', 'all').lower()\n        extension_filter = req.params.get('extension')\n        date_from_str = req.params.get('dateFrom')\n        date_to_str = req.params.get('dateTo')\n        skip = int(req.params.get('skip', 0))\n        limit = int(req.params.get('limit', 50))\n        include_content = req.params.get('includeContent', 'false').lower() == 'true'\n\n        date_from = datetime.fromisoformat(date_from_str) if date_from_str else None\n        date_to = datetime.fromisoformat(date_to_str) if date_to_str else None\n\n        logger.info(f\"Request: sourceType={source_type}, ext={extension_filter}, skip={skip}, limit={limit}, includeContent={include_content}\")\n\n        results = []\n        if source_type in [\"blob\", \"all\"]:\n            results.extend(get_blob_files(extension_filter, date_from, date_to, skip, limit))\n        if source_type in [\"sharepoint\", \"all\"]:\n            results.extend(get_sharepoint_files(extension_filter, date_from, date_to, skip, limit))\n\n        # Optional: include file content for grounding\n        if include_content:\n            for item in results:\n                if item[\"sourceType\"] == \"Blob\":\n                    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n                    blob_client = blob_service_client.get_blob_client(container=\"my-container\", blob=item[\"filePath\"])\n                    content = blob_client.download_blob().readall()\n                    item[\"contentBase64\"] = base64.b64encode(content).decode(\"utf-8\")\n\n                elif item[\"sourceType\"] == \"SharePoint\":\n                    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Undo<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and finish the&nbsp;<strong>SharePoint content retrieval<\/strong>&nbsp;part so your MCP can return&nbsp;<strong>base64-encoded file content<\/strong>&nbsp;from both&nbsp;<strong>Blob Storage<\/strong>&nbsp;and&nbsp;<strong>SharePoint<\/strong>&nbsp;in a single, secure, and Foundry-ready endpoint.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Finalized MCP Function with Full Content Support<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>import logging\nimport json\nimport time\nimport azure.functions as func\nfrom datetime import datetime\nfrom azure.storage.blob import BlobServiceClient\nfrom office365.sharepoint.client_context import ClientContext\nfrom office365.runtime.auth.client_credential import ClientCredential\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\nimport base64\n\n# Setup logging with Application Insights\nlogger = logging.getLogger(__name__)\nlogger.addHandler(AzureLogHandler(\n    connection_string=f\"InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY&gt;\"\n))\n\ndef get_blob_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n    container_client = blob_service_client.get_container_client(\"my-container\")\n\n    blobs = list(container_client.list_blobs())\n    filtered = []\n    for blob in blobs:\n        if extension_filter and not blob.name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            last_modified = blob.last_modified\n            if date_from and last_modified &lt; date_from:\n                continue\n            if date_to and last_modified &gt; date_to:\n                continue\n        filtered.append(blob)\n\n    for blob in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"Blob\",\n            \"fileName\": blob.name.split(\"\/\")[-1],\n            \"filePath\": blob.name,\n            \"lastModified\": blob.last_modified.isoformat()\n        })\n    return results\n\ndef get_sharepoint_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL&gt;\").with_credentials(\n        ClientCredential(\"&lt;SHAREPOINT_CLIENT_ID&gt;\", \"&lt;SHAREPOINT_CLIENT_SECRET&gt;\")\n    )\n    folder = ctx.web.get_folder_by_server_relative_url(\"\/Shared Documents\/Reports\/2026\")\n    files = folder.files\n    ctx.load(files)\n    ctx.execute_query()\n\n    filtered = []\n    for f in files:\n        name = f.properties[\"Name\"]\n        if extension_filter and not name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            modified = f.properties[\"TimeLastModified\"]\n            if date_from and modified &lt; date_from:\n                continue\n            if date_to and modified &gt; date_to:\n                continue\n        filtered.append(f)\n\n    for f in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"SharePoint\",\n            \"fileName\": f.properties[\"Name\"],\n            \"filePath\": f.properties[\"ServerRelativeUrl\"],\n            \"lastModified\": str(f.properties[\"TimeLastModified\"])\n        })\n    return results\n\ndef main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType', 'all').lower()\n        extension_filter = req.params.get('extension')\n        date_from_str = req.params.get('dateFrom')\n        date_to_str = req.params.get('dateTo')\n        skip = int(req.params.get('skip', 0))\n        limit = int(req.params.get('limit', 50))\n        include_content = req.params.get('includeContent', 'false').lower() == 'true'\n\n        date_from = datetime.fromisoformat(date_from_str) if date_from_str else None\n        date_to = datetime.fromisoformat(date_to_str) if date_to_str else None\n\n        logger.info(f\"Request: sourceType={source_type}, ext={extension_filter}, skip={skip}, limit={limit}, includeContent={include_content}\")\n\n        results = []\n        if source_type in [\"blob\", \"all\"]:\n            results.extend(get_blob_files(extension_filter, date_from, date_to, skip, limit))\n        if source_type in [\"sharepoint\", \"all\"]:\n            results.extend(get_sharepoint_files(extension_filter, date_from, date_to, skip, limit))\n\n        # Optional: include file content for grounding\n        if include_content:\n            for item in results:\n                if item[\"sourceType\"] == \"Blob\":\n                    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n                    blob_client = blob_service_client.get_blob_client(container=\"my-container\", blob=item[\"filePath\"])\n                    content = blob_client.download_blob().readall()\n                    item[\"contentBase64\"] = base64.b64encode(content).decode(\"utf-8\")\n\n                elif item[\"sourceType<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">&#8211;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and finish the&nbsp;<strong>SharePoint content retrieval<\/strong>&nbsp;section so your MCP can return&nbsp;<strong>base64-encoded file content<\/strong>&nbsp;from both Blob Storage and SharePoint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the&nbsp;<strong>final completed function<\/strong>:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Final MCP Function with Full Content Support<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>import logging\nimport json\nimport time\nimport azure.functions as func\nfrom datetime import datetime\nfrom azure.storage.blob import BlobServiceClient\nfrom office365.sharepoint.client_context import ClientContext\nfrom office365.runtime.auth.client_credential import ClientCredential\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\nimport base64\n\n# Setup logging with Application Insights\nlogger = logging.getLogger(__name__)\nlogger.addHandler(AzureLogHandler(\n    connection_string=f\"InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY&gt;\"\n))\n\ndef get_blob_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n    container_client = blob_service_client.get_container_client(\"my-container\")\n\n    blobs = list(container_client.list_blobs())\n    filtered = []\n    for blob in blobs:\n        if extension_filter and not blob.name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            last_modified = blob.last_modified\n            if date_from and last_modified &lt; date_from:\n                continue\n            if date_to and last_modified &gt; date_to:\n                continue\n        filtered.append(blob)\n\n    for blob in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"Blob\",\n            \"fileName\": blob.name.split(\"\/\")[-1],\n            \"filePath\": blob.name,\n            \"lastModified\": blob.last_modified.isoformat()\n        })\n    return results\n\ndef get_sharepoint_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL&gt;\").with_credentials(\n        ClientCredential(\"&lt;SHAREPOINT_CLIENT_ID&gt;\", \"&lt;SHAREPOINT_CLIENT_SECRET&gt;\")\n    )\n    folder = ctx.web.get_folder_by_server_relative_url(\"\/Shared Documents\/Reports\/2026\")\n    files = folder.files\n    ctx.load(files)\n    ctx.execute_query()\n\n    filtered = []\n    for f in files:\n        name = f.properties[\"Name\"]\n        if extension_filter and not name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            modified = f.properties[\"TimeLastModified\"]\n            if date_from and modified &lt; date_from:\n                continue\n            if date_to and modified &gt; date_to:\n                continue\n        filtered.append(f)\n\n    for f in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"SharePoint\",\n            \"fileName\": f.properties[\"Name\"],\n            \"filePath\": f.properties[\"ServerRelativeUrl\"],\n            \"lastModified\": str(f.properties[\"TimeLastModified\"])\n        })\n    return results\n\ndef main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType', 'all').lower()\n        extension_filter = req.params.get('extension')\n        date_from_str = req.params.get('dateFrom')\n        date_to_str = req.params.get('dateTo')\n        skip = int(req.params.get('skip', 0))\n        limit = int(req.params.get('limit', 50))\n        include_content = req.params.get('includeContent', 'false').lower() == 'true'\n\n        date_from = datetime.fromisoformat(date_from_str) if date_from_str else None\n        date_to = datetime.fromisoformat(date_to_str) if date_to_str else None\n\n        logger.info(f\"Request: sourceType={source_type}, ext={extension_filter}, skip={skip}, limit={limit}, includeContent={include_content}\")\n\n        results = []\n        if source_type in [\"blob\", \"all\"]:\n            results.extend(get_blob_files(extension_filter, date_from, date_to, skip, limit))\n        if source_type in [\"sharepoint\", \"all\"]:\n            results.extend(get_sharepoint_files(extension_filter, date_from, date_to, skip, limit))\n\n        # Optional: include file content for grounding\n        if include_content:\n            for item in results:\n                if item[\"sourceType\"] == \"Blob\":\n                    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n                    blob_client = blob_service_client.get_blob_client(container=\"my-container\", blob=item[\"filePath\"])\n                    content = blob_client.download_blob().readall()\n                    item[\"contentBase64\"] = base64.b64encode(content).decode(\"utf-8\")\n\n                elif item[\"sourceType\"] == \"SharePoint\":\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">&#8211;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and finish the&nbsp;<strong>SharePoint content retrieval<\/strong>&nbsp;logic so your MCP can return&nbsp;<strong>base64-encoded file content<\/strong>&nbsp;from both Azure Blob Storage and SharePoint in one unified response.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the&nbsp;<strong>final completed version<\/strong>&nbsp;of the function:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Final MCP Function with Full Content Retrieval<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>import logging\nimport json\nimport time\nimport azure.functions as func\nfrom datetime import datetime\nfrom azure.storage.blob import BlobServiceClient\nfrom office365.sharepoint.client_context import ClientContext\nfrom office365.runtime.auth.client_credential import ClientCredential\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\nimport base64\n\n# Setup logging with Application Insights\nlogger = logging.getLogger(__name__)\nlogger.addHandler(AzureLogHandler(\n    connection_string=f\"InstrumentationKey=&lt;YOUR_APP_INSIGHTS_KEY&gt;\"\n))\n\ndef get_blob_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n    container_client = blob_service_client.get_container_client(\"my-container\")\n\n    blobs = list(container_client.list_blobs())\n    filtered = []\n    for blob in blobs:\n        if extension_filter and not blob.name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            last_modified = blob.last_modified\n            if date_from and last_modified &lt; date_from:\n                continue\n            if date_to and last_modified &gt; date_to:\n                continue\n        filtered.append(blob)\n\n    for blob in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"Blob\",\n            \"fileName\": blob.name.split(\"\/\")[-1],\n            \"filePath\": blob.name,\n            \"lastModified\": blob.last_modified.isoformat()\n        })\n    return results\n\ndef get_sharepoint_files(extension_filter=None, date_from=None, date_to=None, skip=0, limit=50):\n    results = []\n    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL&gt;\").with_credentials(\n        ClientCredential(\"&lt;SHAREPOINT_CLIENT_ID&gt;\", \"&lt;SHAREPOINT_CLIENT_SECRET&gt;\")\n    )\n    folder = ctx.web.get_folder_by_server_relative_url(\"\/Shared Documents\/Reports\/2026\")\n    files = folder.files\n    ctx.load(files)\n    ctx.execute_query()\n\n    filtered = []\n    for f in files:\n        name = f.properties[\"Name\"]\n        if extension_filter and not name.lower().endswith(extension_filter.lower()):\n            continue\n        if date_from or date_to:\n            modified = f.properties[\"TimeLastModified\"]\n            if date_from and modified &lt; date_from:\n                continue\n            if date_to and modified &gt; date_to:\n                continue\n        filtered.append(f)\n\n    for f in filtered[skip:skip+limit]:\n        results.append({\n            \"sourceType\": \"SharePoint\",\n            \"fileName\": f.properties[\"Name\"],\n            \"filePath\": f.properties[\"ServerRelativeUrl\"],\n            \"lastModified\": str(f.properties[\"TimeLastModified\"])\n        })\n    return results\n\ndef main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType', 'all').lower()\n        extension_filter = req.params.get('extension')\n        date_from_str = req.params.get('dateFrom')\n        date_to_str = req.params.get('dateTo')\n        skip = int(req.params.get('skip', 0))\n        limit = int(req.params.get('limit', 50))\n        include_content = req.params.get('includeContent', 'false').lower() == 'true'\n\n        date_from = datetime.fromisoformat(date_from_str) if date_from_str else None\n        date_to = datetime.fromisoformat(date_to_str) if date_to_str else None\n\n        logger.info(f\"Request: sourceType={source_type}, ext={extension_filter}, skip={skip}, limit={limit}, includeContent={include_content}\")\n\n        results = []\n        if source_type in [\"blob\", \"all\"]:\n            results.extend(get_blob_files(extension_filter, date_from, date_to, skip, limit))\n        if source_type in [\"sharepoint\", \"all\"]:\n            results.extend(get_sharepoint_files(extension_filter, date_from, date_to, skip, limit))\n\n        # Optional: include file content for grounding\n        if include_content:\n            for item in results:\n                if item[\"sourceType\"] == \"Blob\":\n                    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n                    blob_client = blob_service_client.get_blob_client(container=\"my-container\", blob=item[\"filePath\"])\n                    content = blob_client.download_blob().readall()\n                    item[\"contentBase64\"] = base64.b64encode(content).decode(\"utf-8\")\n\n                elif item[\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and finish the&nbsp;<strong>SharePoint content retrieval<\/strong>&nbsp;block so your MCP endpoint is fully functional for both Blob and SharePoint content delivery.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the&nbsp;<strong>final completed version<\/strong>&nbsp;of the&nbsp;<code>main<\/code>&nbsp;function:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">PythonCopy code<code>def main(req: func.HttpRequest) -&gt; func.HttpResponse:\n    start_time = time.time()\n    try:\n        source_type = req.params.get('sourceType', 'all').lower()\n        extension_filter = req.params.get('extension')\n        date_from_str = req.params.get('dateFrom')\n        date_to_str = req.params.get('dateTo')\n        skip = int(req.params.get('skip', 0))\n        limit = int(req.params.get('limit', 50))\n        include_content = req.params.get('includeContent', 'false').lower() == 'true'\n\n        date_from = datetime.fromisoformat(date_from_str) if date_from_str else None\n        date_to = datetime.fromisoformat(date_to_str) if date_to_str else None\n\n        logger.info(\n            f\"Request: sourceType={source_type}, ext={extension_filter}, \"\n            f\"skip={skip}, limit={limit}, includeContent={include_content}\"\n        )\n\n        results = []\n        if source_type in [\"blob\", \"all\"]:\n            results.extend(get_blob_files(extension_filter, date_from, date_to, skip, limit))\n        if source_type in [\"sharepoint\", \"all\"]:\n            results.extend(get_sharepoint_files(extension_filter, date_from, date_to, skip, limit))\n\n        # Optional: include file content for grounding\n        if include_content:\n            for item in results:\n                if item[\"sourceType\"] == \"Blob\":\n                    blob_service_client = BlobServiceClient.from_connection_string(\"&lt;BLOB_CONNECTION_STRING&gt;\")\n                    blob_client = blob_service_client.get_blob_client(\n                        container=\"my-container\", blob=item[\"filePath\"]\n                    )\n                    content = blob_client.download_blob().readall()\n                    item[\"contentBase64\"] = base64.b64encode(content).decode(\"utf-8\")\n\n                elif item[\"sourceType\"] == \"SharePoint\":\n                    ctx = ClientContext(\"&lt;SHAREPOINT_SITE_URL&gt;\").with_credentials(\n                        ClientCredential(\"&lt;SHAREPOINT_CLIENT_ID&gt;\", \"&lt;SHAREPOINT_CLIENT_SECRET&gt;\")\n                    )\n                    file = ctx.web.get_file_by_server_relative_url(item[\"filePath\"])\n                    download = file.open_binary()\n                    item[\"contentBase64\"] = base64.b64encode(download.content).decode(\"utf-8\")\n\n        duration = time.time() - start_time\n        logger.info(f\"Execution completed in {duration:.2f}s with {len(results)} results\")\n\n        return func.HttpResponse(\n            json.dumps(results),\n            mimetype=\"application\/json\",\n            status_code=200\n        )\n\n    except Exception as e:\n        logger.error(f\"Error occurred: {str(e)}\", exc_info=True)\n        return func.HttpResponse(\"Internal Server Error\", status_code=500)\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Key Enhancements in This Final Version<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>&#x2705;\u00a0<strong>Blob + SharePoint content retrieval<\/strong>\u00a0in\u00a0<strong>base64<\/strong>\u00a0for Foundry grounding<\/li>\n\n\n\n<li>&#x2705;\u00a0<strong>Filtering<\/strong>\u00a0by extension and modified date<\/li>\n\n\n\n<li>&#x2705;\u00a0<strong>Pagination<\/strong>\u00a0with\u00a0<code>skip<\/code>\u00a0and\u00a0<code>limit<\/code><\/li>\n\n\n\n<li>&#x2705;\u00a0<strong>Execution time logging<\/strong>\u00a0for performance monitoring<\/li>\n\n\n\n<li>&#x2705;\u00a0<strong>Application Insights telemetry<\/strong>\u00a0for observability<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Example Foundry Tool Call<\/strong><\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"sourceType\": \"all\",\n  \"extension\": \".pdf\",\n  \"dateFrom\": \"2026-01-01T00:00:00\",\n  \"dateTo\": \"2026-09-01T00:00:00\",\n  \"skip\": 0,\n  \"limit\": 10,\n  \"includeContent\": \"true\"\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This will return&nbsp;<strong>up to 10 PDF files<\/strong>&nbsp;from both Blob and SharePoint, modified in 2026, with&nbsp;<strong>base64-encoded content<\/strong>&nbsp;ready for grounding.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, I can now give you a&nbsp;<strong>Foundry Agent prompt template<\/strong>&nbsp;that will let your agent&nbsp;<strong>ask for specific files<\/strong>&nbsp;and automatically retrieve them from both sources using this MCP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and create the&nbsp;<strong>Foundry Agent prompt template<\/strong>&nbsp;so your agent can seamlessly request files from&nbsp;<strong>both Azure Blob Storage and SharePoint<\/strong>&nbsp;using the MCP endpoint we just built.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Foundry Agent Prompt Template<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You can use this as the&nbsp;<strong>instruction block<\/strong>&nbsp;in your Foundry Agent configuration so it knows exactly how to call your MCP tool.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Prompt Instruction:<\/strong><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Retrieve files from both Azure Blob Storage and SharePoint according to the following parameters:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>sourceType<\/strong>:\u00a0<code>\"blob\"<\/code>,\u00a0<code>\"sharepoint\"<\/code>, or\u00a0<code>\"all\"<\/code><\/li>\n\n\n\n<li><strong>extension<\/strong>: Optional file extension filter (e.g.,\u00a0<code>.pdf<\/code>,\u00a0<code>.xlsx<\/code>)<\/li>\n\n\n\n<li><strong>dateFrom<\/strong>\u00a0\/\u00a0<strong>dateTo<\/strong>: Optional ISO 8601 date range filter<\/li>\n\n\n\n<li><strong>skip<\/strong>\u00a0\/\u00a0<strong>limit<\/strong>: Pagination controls<\/li>\n\n\n\n<li><strong>includeContent<\/strong>:\u00a0<code>\"true\"<\/code>\u00a0to return base64-encoded file content for grounding<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Return the results as a JSON array where each object contains:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>sourceType<\/code><\/li>\n\n\n\n<li><code>fileName<\/code><\/li>\n\n\n\n<li><code>filePath<\/code><\/li>\n\n\n\n<li><code>lastModified<\/code><\/li>\n\n\n\n<li><code>contentBase64<\/code>\u00a0(only if\u00a0<code>includeContent<\/code>\u00a0is\u00a0<code>\"true\"<\/code>)<\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Foundry Tool JSON Configuration<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the ready-to-import tool definition for Foundry:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"name\": \"RetrieveFilesTool\",\n  \"description\": \"Retrieves file metadata and optional content from Azure Blob Storage and SharePoint.\",\n  \"type\": \"http\",\n  \"method\": \"GET\",\n  \"url\": \"https:\/\/my-foundry-mcp-func.azurewebsites.net\/api\/RetrieveFiles\",\n  \"authentication\": {\n    \"type\": \"oauth2\",\n    \"grantType\": \"client_credentials\",\n    \"tokenUrl\": \"https:\/\/login.microsoftonline.com\/&lt;TENANT_ID&gt;\/oauth2\/v2.0\/token\",\n    \"clientId\": \"&lt;YOUR_AAD_APP_CLIENT_ID&gt;\",\n    \"clientSecret\": \"&lt;YOUR_AAD_APP_CLIENT_SECRET&gt;\",\n    \"scope\": \"api:\/\/&lt;YOUR_FUNCTION_APP_CLIENT_ID&gt;\/.default\"\n  },\n  \"parameters\": [\n    { \"name\": \"sourceType\", \"type\": \"string\", \"required\": true },\n    { \"name\": \"extension\", \"type\": \"string\", \"required\": false },\n    { \"name\": \"dateFrom\", \"type\": \"string\", \"required\": false },\n    { \"name\": \"dateTo\", \"type\": \"string\", \"required\": false },\n    { \"name\": \"skip\", \"type\": \"integer\", \"required\": false },\n    { \"name\": \"limit\", \"type\": \"integer\", \"required\": false },\n    { \"name\": \"includeContent\", \"type\": \"string\", \"required\": false }\n  ]\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Example Agent Query<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If your agent needs&nbsp;<strong>all PDF reports from 2026 with content<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"sourceType\": \"all\",\n  \"extension\": \".pdf\",\n  \"dateFrom\": \"2026-01-01T00:00:00\",\n  \"dateTo\": \"2026-12-31T23:59:59\",\n  \"skip\": 0,\n  \"limit\": 20,\n  \"includeContent\": \"true\"\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">&#x2705; With this setup:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Your\u00a0<strong>Foundry Agent<\/strong>\u00a0can query\u00a0<strong>both Blob and SharePoint<\/strong>\u00a0in one call.<\/li>\n\n\n\n<li>You can\u00a0<strong>filter, paginate, and retrieve content<\/strong>\u00a0for grounding.<\/li>\n\n\n\n<li>The\u00a0<strong>OAuth2 security<\/strong>\u00a0ensures only authorized calls succeed.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, I can now&nbsp;<strong>add a Foundry grounding pipeline example<\/strong>&nbsp;so that when your agent retrieves these files, it can&nbsp;<strong>immediately use their content for context-aware responses<\/strong>&nbsp;without extra steps.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and wire this into a&nbsp;<strong>Foundry grounding pipeline<\/strong>&nbsp;so that when your agent retrieves files from&nbsp;<strong>Blob Storage<\/strong>&nbsp;and&nbsp;<strong>SharePoint<\/strong>, it can immediately use their content for&nbsp;<strong>context-aware responses<\/strong>&nbsp;without extra manual steps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1. Grounding Pipeline Overview<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We\u2019ll configure the Foundry Agent so that:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>It calls your\u00a0<strong>RetrieveFilesTool<\/strong>\u00a0MCP endpoint.<\/li>\n\n\n\n<li>It extracts\u00a0<code>contentBase64<\/code>\u00a0from the response.<\/li>\n\n\n\n<li>It decodes and indexes the content into a\u00a0<strong>vector store<\/strong>.<\/li>\n\n\n\n<li>The agent uses that vector store for\u00a0<strong>retrieval-augmented generation (RAG)<\/strong>.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2. Foundry Grounding Pipeline JSON<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s an example pipeline definition:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"name\": \"FileGroundingPipeline\",\n  \"description\": \"Retrieves files from Blob and SharePoint, indexes them, and uses them for grounding.\",\n  \"steps\": [\n    {\n      \"type\": \"tool\",\n      \"toolName\": \"RetrieveFilesTool\",\n      \"parameters\": {\n        \"sourceType\": \"all\",\n        \"extension\": \".pdf\",\n        \"dateFrom\": \"2026-01-01T00:00:00\",\n        \"dateTo\": \"2026-12-31T23:59:59\",\n        \"skip\": 0,\n        \"limit\": 10,\n        \"includeContent\": \"true\"\n      },\n      \"outputKey\": \"retrievedFiles\"\n    },\n    {\n      \"type\": \"transform\",\n      \"name\": \"DecodeBase64Content\",\n      \"script\": \"for file in inputs['retrievedFiles']: file['decodedContent'] = Buffer.from(file['contentBase64'], 'base64').toString('utf-8')\",\n      \"outputKey\": \"decodedFiles\"\n    },\n    {\n      \"type\": \"vectorStore.index\",\n      \"name\": \"IndexFiles\",\n      \"inputKey\": \"decodedFiles\",\n      \"contentField\": \"decodedContent\",\n      \"metadataFields\": [\"fileName\", \"filePath\", \"sourceType\", \"lastModified\"],\n      \"vectorStoreName\": \"project-docs-2026\"\n    }\n  ]\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3. How the Agent Uses It<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Once this pipeline is in place, your Foundry Agent can:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Run the pipeline before answering a question.<\/li>\n\n\n\n<li>Automatically\u00a0<strong>ground<\/strong>\u00a0its responses in the indexed file content.<\/li>\n\n\n\n<li>Answer queries like:\n<ul class=\"wp-block-list\">\n<li><em>&#8220;Summarize all 2026 PDF reports from both Blob and SharePoint.&#8221;<\/em><\/li>\n\n\n\n<li><em>&#8220;Find the report that mentions &#8216;Q3 revenue forecast&#8217; and give me the key figures.&#8221;<\/em><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4. Example Agent Prompt for Grounded Query<\/strong><\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">TextCopy code<code>Use the \"FileGroundingPipeline\" to retrieve and index relevant files.  \nThen answer the following question using only the grounded content:  \n\"What were the main findings in the Q3 2026 financial reports?\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">&#x2705;&nbsp;<strong>Benefits of this setup<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>One-click grounding<\/strong>\u00a0from multiple sources.<\/li>\n\n\n\n<li><strong>Automatic content decoding<\/strong>\u00a0from base64.<\/li>\n\n\n\n<li><strong>Searchable vector store<\/strong>\u00a0for fast retrieval.<\/li>\n\n\n\n<li><strong>No manual file handling<\/strong>\u00a0\u2014 the agent does it all.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, I can now&nbsp;<strong>extend this<\/strong>&nbsp;so the pipeline&nbsp;<strong>only indexes new or updated files<\/strong>&nbsp;since the last run, making it&nbsp;<strong>incremental and faster<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">&#8212;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&nbsp;let\u2019s&nbsp;<strong>proceed<\/strong>&nbsp;and enhance the Foundry grounding pipeline so it&nbsp;<strong>only indexes new or updated files<\/strong>&nbsp;since the last run.<br>This will make it&nbsp;<strong>incremental<\/strong>, faster, and cheaper to operate.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1. Why Incremental Indexing Matters<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Without this, every pipeline run re-downloads and re-indexes all files, even if they haven\u2019t changed.<br>With incremental indexing:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We\u00a0<strong>store the last run timestamp<\/strong>.<\/li>\n\n\n\n<li>On the next run, we\u00a0<strong>only fetch files modified after that timestamp<\/strong>.<\/li>\n\n\n\n<li>This reduces\u00a0<strong>API calls, storage, and processing time<\/strong>.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2. Updated Foundry Pipeline with Incremental Logic<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the&nbsp;<strong>incremental version<\/strong>&nbsp;of the grounding pipeline:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"name\": \"FileGroundingPipelineIncremental\",\n  \"description\": \"Retrieves only new or updated files from Blob and SharePoint, indexes them, and uses them for grounding.\",\n  \"variables\": {\n    \"lastRunTimestamp\": \"2026-09-01T00:00:00\" \n  },\n  \"steps\": [\n    {\n      \"type\": \"tool\",\n      \"toolName\": \"RetrieveFilesTool\",\n      \"parameters\": {\n        \"sourceType\": \"all\",\n        \"extension\": \".pdf\",\n        \"dateFrom\": \"${lastRunTimestamp}\",\n        \"skip\": 0,\n        \"limit\": 50,\n        \"includeContent\": \"true\"\n      },\n      \"outputKey\": \"retrievedFiles\"\n    },\n    {\n      \"type\": \"transform\",\n      \"name\": \"DecodeBase64Content\",\n      \"script\": \"for file in inputs['retrievedFiles']: file['decodedContent'] = Buffer.from(file['contentBase64'], 'base64').toString('utf-8')\",\n      \"outputKey\": \"decodedFiles\"\n    },\n    {\n      \"type\": \"vectorStore.index\",\n      \"name\": \"IndexFiles\",\n      \"inputKey\": \"decodedFiles\",\n      \"contentField\": \"decodedContent\",\n      \"metadataFields\": [\"fileName\", \"filePath\", \"sourceType\", \"lastModified\"],\n      \"vectorStoreName\": \"project-docs-2026\"\n    },\n    {\n      \"type\": \"transform\",\n      \"name\": \"UpdateLastRunTimestamp\",\n      \"script\": \"pipeline.variables.lastRunTimestamp = new Date().toISOString()\"\n    }\n  ]\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3. How It Works<\/strong><\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong><code>lastRunTimestamp<\/code><\/strong>\u00a0is stored in pipeline variables.<\/li>\n\n\n\n<li>The\u00a0<strong>RetrieveFilesTool<\/strong>\u00a0only fetches files modified after that timestamp.<\/li>\n\n\n\n<li>Files are\u00a0<strong>decoded<\/strong>\u00a0and\u00a0<strong>indexed<\/strong>\u00a0into the vector store.<\/li>\n\n\n\n<li>The\u00a0<strong>lastRunTimestamp<\/strong>\u00a0is updated to the current time after indexing.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4. Example Agent Usage<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>First run<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"sourceType\": \"all\",\n  \"extension\": \".pdf\",\n  \"dateFrom\": \"2026-01-01T00:00:00\",\n  \"includeContent\": \"true\"\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Indexes all 2026 PDFs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Subsequent runs<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"sourceType\": \"all\",\n  \"extension\": \".pdf\",\n  \"dateFrom\": \"${lastRunTimestamp}\",\n  \"includeContent\": \"true\"\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Indexes only new or updated files since the last run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#x2705;&nbsp;<strong>Benefits<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Faster<\/strong>\u00a0\u2014 skips already indexed files.<\/li>\n\n\n\n<li><strong>Cheaper<\/strong>\u00a0\u2014 fewer API calls and less storage.<\/li>\n\n\n\n<li><strong>Always up-to-date<\/strong>\u00a0\u2014 agent has the latest documents without reprocessing everything.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">we can now\u00a0<strong>extend this further<\/strong>\u00a0so the pipeline\u00a0<strong>automatically deletes vector store entries<\/strong>\u00a0for files that were removed from Blob or SharePoint, keeping the knowledge base perfectly in sync.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>proceed<\/strong>&nbsp;and add the&nbsp;<strong>auto-deletion sync enhancement<\/strong>&nbsp;so your Foundry grounding pipeline not only indexes new\/updated files but also&nbsp;<strong>removes entries for files deleted<\/strong>&nbsp;from Azure Blob Storage or SharePoint.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This will keep your&nbsp;<strong>vector store perfectly in sync<\/strong>&nbsp;with your actual storage sources.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1. Why Auto-Deletion Sync Matters<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Without this, your vector store might contain:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Outdated documents<\/strong>\u00a0that no longer exist in Blob or SharePoint.<\/li>\n\n\n\n<li><strong>Irrelevant search results<\/strong>\u00a0that confuse the agent.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">With auto-deletion:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The vector store\u00a0<strong>mirrors<\/strong>\u00a0your actual file repositories.<\/li>\n\n\n\n<li>Search results are\u00a0<strong>always accurate<\/strong>.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2. Updated Incremental Pipeline with Auto-Deletion<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s the&nbsp;<strong>enhanced pipeline<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">JsonCopy code<code>{\n  \"name\": \"FileGroundingPipelineFullSync\",\n  \"description\": \"Keeps vector store in sync with Blob and SharePoint by adding new files, updating changed ones, and removing deleted ones.\",\n  \"variables\": {\n    \"lastRunTimestamp\": \"2026-09-01T00:00:00\"\n  },\n  \"steps\": [\n    {\n      \"type\": \"tool\",\n      \"toolName\": \"RetrieveFilesTool\",\n      \"parameters\": {\n        \"sourceType\": \"all\",\n        \"skip\": 0,\n        \"limit\": 500,\n        \"includeContent\": \"false\"\n      },\n      \"outputKey\": \"currentFileList\"\n    },\n    {\n      \"type\": \"vectorStore.list\",\n      \"name\": \"GetIndexedFiles\",\n      \"vectorStoreName\": \"project-docs-2026\",\n      \"outputKey\": \"indexedFileList\"\n    },\n    {\n      \"type\": \"transform\",\n      \"name\": \"FindDeletedFiles\",\n      \"script\": \"deletedFiles = [f for f in inputs['indexedFileList'] if f['filePath'] not in [c['filePath'] for c in inputs['currentFileList']]]\",\n      \"outputKey\": \"deletedFiles\"\n    },\n    {\n      \"type\": \"vectorStore.delete\",\n      \"name\": \"RemoveDeletedFiles\",\n      \"vectorStoreName\": \"project-docs-2026\",\n      \"inputKey\": \"deletedFiles\"\n    },\n    {\n      \"type\": \"tool\",\n      \"toolName\": \"RetrieveFilesTool\",\n      \"parameters\": {\n        \"sourceType\": \"all\",\n        \"dateFrom\": \"${lastRunTimestamp}\",\n        \"skip\": 0,\n        \"limit\": 50,\n        \"includeContent\": \"true\"\n      },\n      \"outputKey\": \"newOrUpdatedFiles\"\n    },\n    {\n      \"type\": \"transform\",\n      \"name\": \"DecodeBase64Content\",\n      \"script\": \"for file in inputs['newOrUpdatedFiles']: file['decodedContent'] = Buffer.from(file['contentBase64'], 'base64').toString('utf-8')\",\n      \"outputKey\": \"decodedFiles\"\n    },\n    {\n      \"type\": \"vectorStore.index\",\n      \"name\": \"IndexFiles\",\n      \"inputKey\": \"decodedFiles\",\n      \"contentField\": \"decodedContent\",\n      \"metadataFields\": [\"fileName\", \"filePath\", \"sourceType\", \"lastModified\"],\n      \"vectorStoreName\": \"project-docs-2026\"\n    },\n    {\n      \"type\": \"transform\",\n      \"name\": \"UpdateLastRunTimestamp\",\n      \"script\": \"pipeline.variables.lastRunTimestamp = new Date().toISOString()\"\n    }\n  ]\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3. How It Works<\/strong><\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Retrieve current file list<\/strong>\u00a0from Blob + SharePoint (metadata only).<\/li>\n\n\n\n<li><strong>Get indexed file list<\/strong>\u00a0from the vector store.<\/li>\n\n\n\n<li><strong>Compare lists<\/strong>\u00a0to find files that exist in the vector store but not in storage.<\/li>\n\n\n\n<li><strong>Delete those entries<\/strong>\u00a0from the vector store.<\/li>\n\n\n\n<li><strong>Retrieve only new\/updated files<\/strong>\u00a0since the last run.<\/li>\n\n\n\n<li><strong>Decode and index<\/strong>\u00a0them into the vector store.<\/li>\n\n\n\n<li><strong>Update last run timestamp<\/strong>\u00a0for the next incremental run.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4. Benefits<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Perfect sync<\/strong>\u00a0between storage and vector store.<\/li>\n\n\n\n<li><strong>No stale documents<\/strong>\u00a0in search results.<\/li>\n\n\n\n<li><strong>Incremental updates<\/strong>\u00a0keep it fast and cost-efficient.<\/li>\n\n\n\n<li><strong>Fully automated<\/strong>\u00a0\u2014 no manual cleanup needed.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">we can now\u00a0<strong>extend this even further<\/strong>\u00a0so the pipeline\u00a0<strong>runs on a schedule<\/strong>\u00a0(e.g., every hour) and\u00a0<strong>triggers a Foundry Agent retraining<\/strong>\u00a0automatically when new documents are indexed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-large-font-size wp-block-paragraph\"><strong><em>=======OPTION 2========<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hosting a custom <strong>Model Context Protocol (MCP)<\/strong> server on <strong>Azure Functions<\/strong> is the native method for extending <strong>Microsoft Foundry Agents<\/strong> with custom tools. [<a href=\"https:\/\/www.google.com\/goto?url=CAESiwEB6zswFS2m9io6fYN_PtVx6Um5GS1iemKcXqS8XCrRhuZsgC4hO5V2cMaA3QkXcr-aix7bWaHM935ar8JGFc8p1nFf5dHybVYejgztlfx14NN1ZOtMF9aRrdTE0rrI8qCZwqp0HHLvQL464WO_k5jl7LMuuShheCENAjxeQOZMU0cnU2lDO8J57nqh\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAEShQEB6zswFeb0RfOWbX1gzJx1yjlvYltktyHEBll7I7PtTp0l_lH3aijmmp212KUNrDh1eidP8CrzHbtjrnfzfFKfC4pG89h4wPbkFuhTjk8bnoSCMYr473RR6EbpBJ4qIb1qCONkkSfiyDxWkVgXvQaDNijfp2RSsxpDZ_qbrJJHD1MRZxA7\">2<\/a>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When using Azure Functions for Microsoft Foundry, you can leverage the official <strong>Azure Functions MCP Extension<\/strong>, which automates request handling and maps seamlessly to <strong>Application Insights<\/strong> for end-to-end tracing. [<a href=\"https:\/\/www.google.com\/goto?url=CAESYwHrOzAVOIPKNTjXFJKDQfFtQCWSpBO3dcEpijUKAYBHEIXRQwRRKSEhVQrShWJ9FouZNtjq8OxS7xf5tIdeLaaY8pewRHpsepkUppHONwvtMu8O-NR76YxdNKCDClDjoNsjvA\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESkQEB6zswFWsYkYcgqz8OEUEurl48p_93WLt5Nib8wq-4hyUEECB-L_H_aDIjfiozhQTnez7NFdzBoS-inRxEAkAkCNTW2kfHCmnfmv_A23YIc-riSCuuh5NsElV-YrDka8D8nAz2saLsqYBxx0-cRDBY-UlmrrNKVxHoBpn6WpYsTObD8m0v7_ZMRnYcighbfG8M\">2<\/a>]<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 1: Initialize Your Project Locally<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ensure you have the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"https:\/\/www.google.com\/goto?url=CAESgwEB6zswFXwwbQ2MINNsyvah57FxBdf1OC_XEw7bSB1_t6yuAcq6bRQQdfIZ6AikKvB7QHVVXIDRe7AN2F8tzo6iTqKO5QymQrvDihgEXu4z3AY_4kaDIyLw9FhFL56dzkXHGJD15cmcJaApokaWNpYDXvZYLmjDXApbrRIPiYHT0GoEoA\">Azure Functions Core Tools<\/a> and <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"https:\/\/www.google.com\/goto?url=CAESdQHrOzAV6J5Mf7I7HKC6wZJeoOjjySWw4rdzSG0BNNgA3UY7XGmWDo3xJ_MzotHh74jWBskPPGR2wMN5-JOzSQBXNANCfSZfuEa-h2c_9Cp52EU-OkIKeunUxt2rOIare_vM-ZdGBWYhNr2DzsqnV7zuZA60hw\">Azure CLI<\/a> installed.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Create and enter your project directory:<\/strong>bash<code>mkdir foundry-mcp-server cd foundry-mcp-server <\/code>Use code with caution.<\/li>\n\n\n\n<li><strong>Initialize an Azure Functions Python project<\/strong> (V2 Programming Model):bash<code>func init --worker-runtime python --model V2 <\/code>Use code with caution.<\/li>\n\n\n\n<li><strong>Configure your <code>requirements.txt<\/code>:<\/strong><br>Open <code>requirements.txt<\/code> and ensure it includes the foundational Azure and OpenCensus tracking libraries:text<code>azure-functions opencensus-ext-azure <\/code>Use code with caution.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 2: Write the MCP Code<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the modern Microsoft Azure architecture, the Function App natively handles the HTTP\/SSE streaming or webhook routing for MCP requests. [<a href=\"https:\/\/www.google.com\/goto?url=CAESqgEB6zswFZ5A_F6MBtMbuTXYBlA9pbj1bdKQDxzbGHuX7SV6o3vwfl5TJ1E2F9gym1BB07ggGY-kPSeAC8OtWrZpqnl6vwOb4dPWEwkPGGpWfeeHLL5zHretttzzjad6kvpuNJZDrEVC3YIL41V2_ZAOaTbkVsiYe8oG5mXMY9wNbWboCjYl2W9n2ro7hNRBj1sp4lwh8gD0D1pH_INLD--GGEqoSvLiQXZ8yw\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESYwHrOzAVOIPKNTjXFJKDQfFtQCWSpBO3dcEpijUKAYBHEIXRQwRRKSEhVQrShWJ9FouZNtjq8OxS7xf5tIdeLaaY8pewRHpsepkUppHONwvtMu8O-NR76YxdNKCDClDjoNsjvA\">2<\/a>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Replace the code in your <code>function_app.py<\/code> with this structure, which configures an HTTP endpoint, sets up explicit telemetry logs via Application Insights, and exposes an MCP tool structure:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">python<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import json\nimport logging\nimport azure.functions as func\nfrom opencensus.ext.azure.log_exporter import AzureLogHandler\n\n# 1. Setup Application Insights Telemetry\nlogger = logging.getLogger(__name__)\ntry:\n    # Azure automatically provides APPLICATIONINSIGHTS_CONNECTION_STRING in the cloud\n    logger.addHandler(AzureLogHandler())\nexcept Exception:\n    # Fallback to local streaming logs if debugging locally\n    logging.basicConfig(level=logging.INFO)\n\napp = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)\n\n# 2. Expose the MCP Endpoint Webhook\n@app.route(route=\"mcp\", methods=&#91;\"POST\", \"GET\"])\nasync def mcp_webhook(req: func.HttpRequest) -&gt; func.HttpResponse:\n    logger.info(\"Microsoft Foundry Agent triggered the MCP server webhook.\")\n    \n    # Handle MCP initialization \/ Tool Discovery (GET)\n    if req.method == \"GET\":\n        tools_manifest = {\n            \"tools\": &#91;\n                {\n                    \"name\": \"fetch_file_paths\",\n                    \"description\": \"Retrieves source paths from Azure Blob and SharePoint folders.\",\n                    \"inputSchema\": {\n                        \"type\": \"object\",\n                        \"properties\": {\n                            \"system\": {\"type\": \"string\", \"enum\": &#91;\"blob\", \"sharepoint\", \"both\"]}\n                        },\n                        \"required\": &#91;\"system\"]\n                    }\n                }\n            ]\n        }\n        return func.HttpResponse(json.dumps(tools_manifest), mimetype=\"application\/json\", status_code=200)\n\n    # Handle MCP Tool Execution (POST)\n    try:\n        req_body = req.get_json()\n        method = req_body.get(\"method\")\n        params = req_body.get(\"params\", {})\n        \n        # Log payload parameters directly to App Insights for auditable tracing\n        logger.info(f\"Executing MCP Method: {method} with params: {json.dumps(params)}\")\n\n        if method == \"tools\/call\" or req_body.get(\"name\") == \"fetch_file_paths\":\n            # Extract arguments from your Agent's prompt\n            arguments = params.get(\"arguments\", req_body.get(\"arguments\", {}))\n            target_system = arguments.get(\"system\", \"both\")\n\n            # Mock data (Insert your physical storage fetching logic here)\n            execution_result = {\n                \"content\": &#91;\n                    {\n                        \"type\": \"text\",\n                        \"text\": f\"Successfully retrieved file paths filtered for system: {target_system}\"\n                    }\n                ]\n            }\n            return func.HttpResponse(json.dumps(execution_result), mimetype=\"application\/json\", status_code=200)\n            \n        return func.HttpResponse(json.dumps({\"error\": \"Method not found\"}), status_code=404)\n\n    except Exception as e:\n        logger.error(f\"MCP Server Execution Failed: {str(e)}\", exc_info=True)\n        return func.HttpResponse(json.dumps({\"error\": str(e)}), status_code=500)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Use code with caution.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 3: Provision Infrastructure in Azure<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run the following Azure CLI commands to build the required serverless architecture.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Create a Resource Group:<\/strong> bash  <code>az group create --name FoundryMCP-RG --location eastus <\/code>Use code with caution.<\/li>\n\n\n\n<li><strong>Create an Application Insights Instance:<\/strong> bash <code>az monitor app-insights component create --app FoundryMCP-Insights --location eastus --resource-group FoundryMCP-RG <\/code>Use code with caution.<\/li>\n\n\n\n<li><strong>Create a Storage Account:<\/strong> bash <code>az storage account create --name mcpstoreacc --location eastus --resource-group FoundryMCP-RG --sku Standard_LRS <\/code>Use code with caution.<\/li>\n\n\n\n<li><strong>Create the Function App (Linux \/ Python 3.11):<\/strong> bash <code>az functionapp create --name MyFoundryMcpApp --storage-account mcpstoreacc --consumption-plan-location eastus --resource-group FoundryMCP-RG --os-type Linux --runtime python --runtime-version 3.11 --functions-version 4 --app-insights FoundryMCP-Insights <\/code>Use code with caution.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 4: Deploy &amp; Acquire the Webhook URL<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Deploy your code using the Core Tools:<\/strong>bash<code>func azure functionapp publish MyFoundryMcpApp <\/code>Use code with caution.<\/li>\n\n\n\n<li>Once deployed, note down your deployment endpoint and grab your <strong>Function\/Host Key<\/strong> from the Azure Portal (<code>Azure Portal &gt; Function App &gt; App Keys<\/code>). Your exact connection string format will look like this:<br><code>https:\/\/azurewebsites.net<\/code> [<a href=\"https:\/\/www.google.com\/goto?url=CAEShgEB6zswFW03lXH1_uCikJFnouFyGkykg3bgHrEW_dr2d2ZiBg3I8NhF-FbPIOXBwt34mHScuUaYoGFfI9bw8P9orbF2YmnL8zVS1sOlzbISqp04cTqDL1OfI4mhKmwWy1ILBDSD7BInCn3SeLVHt5gcKEYzRAVkEBeeC5dbhZRdu8shXIM4vw\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESqgEB6zswFZ5A_F6MBtMbuTXYBlA9pbj1bdKQDxzbGHuX7SV6o3vwfl5TJ1E2F9gym1BB07ggGY-kPSeAC8OtWrZpqnl6vwOb4dPWEwkPGGpWfeeHLL5zHretttzzjad6kvpuNJZDrEVC3YIL41V2_ZAOaTbkVsiYe8oG5mXMY9wNbWboCjYl2W9n2ro7hNRBj1sp4lwh8gD0D1pH_INLD--GGEqoSvLiQXZ8yw\">2<\/a>]<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 5: Connect Your MCP Server to Microsoft Foundry Agent<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Navigate to the <strong>Microsoft Foundry Portal<\/strong> (azure.com). [<a href=\"https:\/\/www.google.com\/goto?url=CAESYwHrOzAVcEQIRzK9UxBwJAbTubkBx06RTfZS1yR-sE3017lAw0t8XBTs3IloTrbTcS2XkPE8kHPTB8Vr3yT9Dplq-HuWoGRRNoOs1bYvXttmFcvmWNQG0ZXVcX-SD4632d67ZQ\">1<\/a>]<\/li>\n\n\n\n<li>Open or create your custom <strong>Agent<\/strong> in the <strong>Playground<\/strong>. [<a href=\"https:\/\/www.google.com\/goto?url=CAESaQHrOzAVDtvgvS5X_NeQPPdpA5qAHY-A4pB8FJRpI2s26TLqJMv6xSDM2Q-2JhpaYk_WYlVT-5DXs1R_GZi6WKefhGCh4la_uqVcpgzfUyjU9BXfUBZP5_XvLdpvQvStNxp7yr-SAUkthQ\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESYwHrOzAVcEQIRzK9UxBwJAbTubkBx06RTfZS1yR-sE3017lAw0t8XBTs3IloTrbTcS2XkPE8kHPTB8Vr3yT9Dplq-HuWoGRRNoOs1bYvXttmFcvmWNQG0ZXVcX-SD4632d67ZQ\">2<\/a>]<\/li>\n\n\n\n<li>On the right-hand menu panel, click <strong>Tools<\/strong> &gt; <strong>Add Tool<\/strong>. [<a href=\"https:\/\/www.google.com\/goto?url=CAESYwHrOzAVcEQIRzK9UxBwJAbTubkBx06RTfZS1yR-sE3017lAw0t8XBTs3IloTrbTcS2XkPE8kHPTB8Vr3yT9Dplq-HuWoGRRNoOs1bYvXttmFcvmWNQG0ZXVcX-SD4632d67ZQ\">1<\/a>]<\/li>\n\n\n\n<li>Choose <strong>Model Context Protocol (MCP) tool<\/strong> from the tool definitions catalog. [<a href=\"https:\/\/www.google.com\/goto?url=CAESlAEB6zswFSg7zqS98HzdtHz9ahUOsVzf6Fshx8RpmnOljSPLyL26DVuVOMgn7IFGIPOpsRVJF6JGqjsWdb4ga3yzHXemIzY_MgBy0Q_EmEWdLosLwTw01sdoVfO4146WvkKX3iwpa0QPzWMWsfZwMh8NUhVe-T2ubOHINFChzrzYlOxTBdn9g2jNN_3Q8rDQHCXWF4oo\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESaQHrOzAVfaGeyX3_Wolt3wTvFkgpBesfBQaxJkaTVDOpky_swYFTwrHbDiH8phD75zUbl1rynwocALZ1MwjGJnEu86L6qpas5XKAHBCF5cGSMMrFdCoTi5KavytvKEuXVGMpzpC9wMSlKw\">2<\/a>]<\/li>\n\n\n\n<li>Paste your endpoint URL:\n<ul class=\"wp-block-list\">\n<li><code>https:\/\/azurewebsites.net<\/code> [<a href=\"https:\/\/www.google.com\/goto?url=CAESqgEB6zswFZ5A_F6MBtMbuTXYBlA9pbj1bdKQDxzbGHuX7SV6o3vwfl5TJ1E2F9gym1BB07ggGY-kPSeAC8OtWrZpqnl6vwOb4dPWEwkPGGpWfeeHLL5zHretttzzjad6kvpuNJZDrEVC3YIL41V2_ZAOaTbkVsiYe8oG5mXMY9wNbWboCjYl2W9n2ro7hNRBj1sp4lwh8gD0D1pH_INLD--GGEqoSvLiQXZ8yw\">1<\/a>]<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Add the mandatory authorization header parameter so the Foundry platform can pass your secure Azure Key:\n<ul class=\"wp-block-list\">\n<li><strong>Header Name:<\/strong> <code>x-functions-key<\/code><\/li>\n\n\n\n<li><strong>Value:<\/strong> <code>[Your Azure Function App Key]<\/code> [<a href=\"https:\/\/www.google.com\/goto?url=CAEShgEB6zswFW03lXH1_uCikJFnouFyGkykg3bgHrEW_dr2d2ZiBg3I8NhF-FbPIOXBwt34mHScuUaYoGFfI9bw8P9orbF2YmnL8zVS1sOlzbISqp04cTqDL1OfI4mhKmwWy1ILBDSD7BInCn3SeLVHt5gcKEYzRAVkEBeeC5dbhZRdu8shXIM4vw\">1<\/a>]<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The Agent will immediately query your function app endpoint (<code>GET<\/code>) to dynamically catalog your <code>fetch_file_paths<\/code> capabilities at runtime. [<a href=\"https:\/\/www.google.com\/goto?url=CAESiAEB6zswFXISU1PQZcAhSZEwlN_sBLttpiJsrUnTP7Hh2YAztASQHf6kfYnw9ok9Dpu8USAr2P8L3YERadTVDnTW-Uh0Paygu3vQVV6buko7WwKU8wWQ0fscSuec_6RQmUAzNu101ZcX0o6XYy6KgAcvc_jooXYeOrJkP0jZJt8RKIpY6BbpRa3v\">1<\/a>]<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 6: Monitor Executions via Application Insights<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Whenever your Microsoft Foundry Agent runs a prompt requiring file path discovery, the execution trace flows live into Azure: [<a href=\"https:\/\/www.google.com\/goto?url=CAESYwHrOzAVOIPKNTjXFJKDQfFtQCWSpBO3dcEpijUKAYBHEIXRQwRRKSEhVQrShWJ9FouZNtjq8OxS7xf5tIdeLaaY8pewRHpsepkUppHONwvtMu8O-NR76YxdNKCDClDjoNsjvA\">1<\/a>]<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Go to <strong>Application Insights<\/strong> in the Azure Portal.<\/li>\n\n\n\n<li>Select <strong>Logs<\/strong> under the Monitoring section.<\/li>\n\n\n\n<li>Execute a KQL query to monitor what the Agent passed to your custom serverless backend:kusto<code>traces | where message contains \"Executing MCP Method\" | project timestamp, message, severityLevel<\/code><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><span style=\"text-decoration: underline;\">Bundle inside foundry toolbox for multi-agent routing<\/span><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In <strong>Microsoft Foundry<\/strong> (Azure AI Foundry), a <strong>Foundry Toolbox<\/strong> solves the tool-sprawl problem by bundling multiple resources\u2014such as your custom <strong>Azure Function MCP server<\/strong>, Web Search, and <strong>Agent-to-Agent (A2A)<\/strong> connections\u2014into a <strong>single versioned, MCP-compatible endpoint<\/strong>. [<a href=\"https:\/\/www.google.com\/goto?url=CAESkgEB6zswFSi7_jAk8D4BuaSlJFJP-II6B3hwOYKMAheyvfZtpez6x2BA9ecC5E5bBIJ7pStY6ZRccomZWeGH96HmYAMtXnbejLFjW4eyeiFgSNKc7YK0Wy6Vok9geR4-Q72yzWS7lRFYorQuUfcSCIMW88vpIi6-UxVfrvp5q2iNMDLSxsxFLVfyT_Dfnj80cWMKCw\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESkgEB6zswFfWil26VCxEQYwmQ1dJ4yuuqPp2yPaYemMXaYnz-bf_GlcMXvmFMD10LVzlEVhF5lwVfb6QhVCIt2yGGGRRNryMOfUWwk_1hF9ceyIQzV__H9gRWA8SwOTptf5m5BaUpOB6G9259e_XjJXWC9Dg3rRVpEXTnKHe65yXWOKoLokSlTLJmd9d30KmTP2WdLQ\">2<\/a>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of wiring individual tools directly to every agent, you attach the entire Toolbox. The platform then handles discovery, security, and multi-agent routing dynamically via natural language or autonomous orchestration. [<a href=\"https:\/\/www.google.com\/goto?url=CAES0QEB6zswFYBBDEgbmum83C1eOuVev5fO0rbR9uPWJ5Z8ARfvwIBLn7kAE3372WNblDlqLbIAEQmXBTvR2k9u8YDJ9n0bTqFYmKM56P4W8I8X0B-Qq2zPlUarDmI4-C8rIMTljJQJwAjW43UBj-3i3BDc1xK_11zHMm6ecdj_ZvovTwqSEJqU0R2L1MiNb-69RNdEpzJKSllnuBAdn_gK8_KJJ0goKBSCLNhLAxBhw5YEpSdw2fqqjLjPPa-1NzjyHI-WRcZp8FimivJ357ga5R0jug\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESkgEB6zswFSi7_jAk8D4BuaSlJFJP-II6B3hwOYKMAheyvfZtpez6x2BA9ecC5E5bBIJ7pStY6ZRccomZWeGH96HmYAMtXnbejLFjW4eyeiFgSNKc7YK0Wy6Vok9geR4-Q72yzWS7lRFYorQuUfcSCIMW88vpIi6-UxVfrvp5q2iNMDLSxsxFLVfyT_Dfnj80cWMKCw\">2<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESewHrOzAVkH57iylDzqotvx4sJ5cGhNsoScTmJRXcdI3NekfEtA0L3sYGG4m4gtQ2CTuczFStRQOgE7IkBYfo1U096Cb85zCjUhi5GTytB60K-pIXom7d244VGC5c-TxEGYfRCUbrshE98j7ptqtB2Fkejje37_JyaHfouw\">3<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESaQHrOzAVfGDRTeDOFvlsD0xvTqWvfcHGvrYdK3N82SQajnfVy4n53UB_jNLuw0UZE3GESuj-gxN_hSez5KyBCWrCgFKE-zAwetTD9g9Jt4ZyDMJ5qD4XPYSYEBFYdLIwHjyT2wXUuWMY-A\">4<\/a>]<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 1: Create and Bundle Tools into the Toolbox<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can create and manage your Toolbox via the <strong>Azure AI Foundry Portal UI<\/strong>, the <strong>Foundry VS Code Toolkit<\/strong>, or the Azure CLI (<code>azd<\/code>). [<a href=\"https:\/\/www.google.com\/goto?url=CAESkwEB6zswFatExlhEtNBBKNGZobjTRB9uyd1xn_7L_CyARTNe3OstkTUYTbNOtwRWXIjKFpcFPR6Dz6IhF2QM4sVOKwH0rV8hC_go1VWG2rIQP5Tve98PtKYiseNnORYdHm0WjTaee1rQZ0ZsuWKl4EEgegndruV50SC8ngYZq5Q3z8jnPeQs8mxvMjnYZUzCkU7wlCg\">1<\/a>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Method A: Using the VS Code Foundry Toolkit<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open Visual Studio Code and click <strong>Foundry Toolkit<\/strong> in the Activity Bar. [<a href=\"https:\/\/www.google.com\/goto?url=CAESkwEB6zswFatExlhEtNBBKNGZobjTRB9uyd1xn_7L_CyARTNe3OstkTUYTbNOtwRWXIjKFpcFPR6Dz6IhF2QM4sVOKwH0rV8hC_go1VWG2rIQP5Tve98PtKYiseNnORYdHm0WjTaee1rQZ0ZsuWKl4EEgegndruV50SC8ngYZq5Q3z8jnPeQs8mxvMjnYZUzCkU7wlCg\">1<\/a>]<\/li>\n\n\n\n<li>Expand your project under <em>My Resources<\/em>, right-click <strong>Tools<\/strong>, and select the <strong>+ Add Toolbox<\/strong> icon. [<a href=\"https:\/\/www.google.com\/goto?url=CAESkwEB6zswFatExlhEtNBBKNGZobjTRB9uyd1xn_7L_CyARTNe3OstkTUYTbNOtwRWXIjKFpcFPR6Dz6IhF2QM4sVOKwH0rV8hC_go1VWG2rIQP5Tve98PtKYiseNnORYdHm0WjTaee1rQZ0ZsuWKl4EEgegndruV50SC8ngYZq5Q3z8jnPeQs8mxvMjnYZUzCkU7wlCg\">1<\/a>]<\/li>\n\n\n\n<li>Name your toolbox (e.g., <code>EnterpriseDataToolbox<\/code>) and provide a clear description. [<a href=\"https:\/\/www.google.com\/goto?url=CAESkwEB6zswFatExlhEtNBBKNGZobjTRB9uyd1xn_7L_CyARTNe3OstkTUYTbNOtwRWXIjKFpcFPR6Dz6IhF2QM4sVOKwH0rV8hC_go1VWG2rIQP5Tve98PtKYiseNnORYdHm0WjTaee1rQZ0ZsuWKl4EEgegndruV50SC8ngYZq5Q3z8jnPeQs8mxvMjnYZUzCkU7wlCg\">1<\/a>]<\/li>\n\n\n\n<li>Click <strong>+ Add tool<\/strong> and select <strong>Remote MCP Server<\/strong>. Paste your Azure Function App endpoint:\n<ul class=\"wp-block-list\">\n<li><code>https:\/\/azurewebsites.net<\/code> [<a href=\"https:\/\/www.google.com\/goto?url=CAESkwEB6zswFatExlhEtNBBKNGZobjTRB9uyd1xn_7L_CyARTNe3OstkTUYTbNOtwRWXIjKFpcFPR6Dz6IhF2QM4sVOKwH0rV8hC_go1VWG2rIQP5Tve98PtKYiseNnORYdHm0WjTaee1rQZ0ZsuWKl4EEgegndruV50SC8ngYZq5Q3z8jnPeQs8mxvMjnYZUzCkU7wlCg\">1<\/a>]<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><em>(Optional for Multi-Agent Routing)<\/em> Select <strong>+ Add tool<\/strong> again and choose <strong>Agent-to-Agent (A2A)<\/strong> to register other specialized downstream sub-agents as tools within this specific Toolbox. [<a href=\"https:\/\/www.google.com\/goto?url=CAESkgEB6zswFfWil26VCxEQYwmQ1dJ4yuuqPp2yPaYemMXaYnz-bf_GlcMXvmFMD10LVzlEVhF5lwVfb6QhVCIt2yGGGRRNryMOfUWwk_1hF9ceyIQzV__H9gRWA8SwOTptf5m5BaUpOB6G9259e_XjJXWC9Dg3rRVpEXTnKHe65yXWOKoLokSlTLJmd9d30KmTP2WdLQ\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAES0QEB6zswFYBBDEgbmum83C1eOuVev5fO0rbR9uPWJ5Z8ARfvwIBLn7kAE3372WNblDlqLbIAEQmXBTvR2k9u8YDJ9n0bTqFYmKM56P4W8I8X0B-Qq2zPlUarDmI4-C8rIMTljJQJwAjW43UBj-3i3BDc1xK_11zHMm6ecdj_ZvovTwqSEJqU0R2L1MiNb-69RNdEpzJKSllnuBAdn_gK8_KJJ0goKBSCLNhLAxBhw5YEpSdw2fqqjLjPPa-1NzjyHI-WRcZp8FimivJ357ga5R0jug\">2<\/a>]<\/li>\n\n\n\n<li>Click <strong>Publish<\/strong> to generate a static, production-ready Toolbox version and copy its unique versioned MCP endpoint:<br><code>https:\/\/&lt;account&gt;.services.ai.azure.com\/api\/projects\/&lt;project&gt;\/toolboxes\/&lt;toolbox-name&gt;\/versions\/1\/mcp?api-version=v1<\/code> [<a href=\"https:\/\/www.google.com\/goto?url=CAESkwEB6zswFatExlhEtNBBKNGZobjTRB9uyd1xn_7L_CyARTNe3OstkTUYTbNOtwRWXIjKFpcFPR6Dz6IhF2QM4sVOKwH0rV8hC_go1VWG2rIQP5Tve98PtKYiseNnORYdHm0WjTaee1rQZ0ZsuWKl4EEgegndruV50SC8ngYZq5Q3z8jnPeQs8mxvMjnYZUzCkU7wlCg\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESkgEB6zswFfWil26VCxEQYwmQ1dJ4yuuqPp2yPaYemMXaYnz-bf_GlcMXvmFMD10LVzlEVhF5lwVfb6QhVCIt2yGGGRRNryMOfUWwk_1hF9ceyIQzV__H9gRWA8SwOTptf5m5BaUpOB6G9259e_XjJXWC9Dg3rRVpEXTnKHe65yXWOKoLokSlTLJmd9d30KmTP2WdLQ\">2<\/a>]<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 2: Enable &#8220;Tool Search&#8221; and Governance<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As your toolbox grows to handle complex multi-agent workflows, cramming every tool or sub-agent description into the prompt will blow out token costs. [<a href=\"https:\/\/www.google.com\/goto?url=CAEScAHrOzAVdx4Cca2IzsloiFFHxW8pdOEaEBm92gh52eE2CRJR1tMSjHAIB3nnrAiyPgtJDORtj3tXbi3OXqT6t3DdNCTRyEJirHFC17__z1143fFR4pUfWROrWNfy15MfWJlo6hR0PZQi-Ox4S1D1B6s\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESewHrOzAVkH57iylDzqotvx4sJ5cGhNsoScTmJRXcdI3NekfEtA0L3sYGG4m4gtQ2CTuczFStRQOgE7IkBYfo1U096Cb85zCjUhi5GTytB60K-pIXom7d244VGC5c-TxEGYfRCUbrshE98j7ptqtB2Fkejje37_JyaHfouw\">2<\/a>]<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Within your <strong>Toolbox Settings<\/strong> in the Microsoft Foundry portal, toggle on <strong>Tool Search (Preview)<\/strong>. [<a href=\"https:\/\/www.google.com\/goto?url=CAEScAHrOzAVdx4Cca2IzsloiFFHxW8pdOEaEBm92gh52eE2CRJR1tMSjHAIB3nnrAiyPgtJDORtj3tXbi3OXqT6t3DdNCTRyEJirHFC17__z1143fFR4pUfWROrWNfy15MfWJlo6hR0PZQi-Ox4S1D1B6s\">1<\/a>]<\/li>\n\n\n\n<li><strong>How it routes:<\/strong> When the main agent receives a prompt, the Toolbox uses a localized search algorithm (like BM25) to look at the user query and inject <em>only<\/em> the specific tool\/sub-agent definitions needed for that turn\u2014routing the request dynamically without overwhelming the model context. [<a href=\"https:\/\/www.google.com\/goto?url=CAESaQHrOzAVfGDRTeDOFvlsD0xvTqWvfcHGvrYdK3N82SQajnfVy4n53UB_jNLuw0UZE3GESuj-gxN_hSez5KyBCWrCgFKE-zAwetTD9g9Jt4ZyDMJ5qD4XPYSYEBFYdLIwHjyT2wXUuWMY-A\">1<\/a>]<\/li>\n\n\n\n<li>Assign any <strong>Foundry Guardrails<\/strong> directly to the Toolbox layer. This ensures that inputs and outputs going to any routed tool or sub-agent are automatically governed under a single policy. [<a href=\"https:\/\/www.google.com\/goto?url=CAESiAEB6zswFZzsCNUCBsjiojLRxc5gD2leExs7tPjDbJskg-3BvewMekTdn9ClOCKqeIDn8EDczGyA9iUBJwpfjgdcwu5S5Z7v51RJ1SVTXubcADnFUmtI_FFjZIfQ_t9oP17KT7SZ2MdgC6PNF794DN2ih1kuxLzTpotG7TRYaA5TonStHT05ktIM\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESaQHrOzAVfGDRTeDOFvlsD0xvTqWvfcHGvrYdK3N82SQajnfVy4n53UB_jNLuw0UZE3GESuj-gxN_hSez5KyBCWrCgFKE-zAwetTD9g9Jt4ZyDMJ5qD4XPYSYEBFYdLIwHjyT2wXUuWMY-A\">2<\/a>]<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Step 3: Implement Multi-Agent Routing in Code<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using the <strong>Microsoft Agent Framework (MAF)<\/strong>, you can instantiate an orchestrator agent, hand it the central Foundry Toolbox, and let it route traffic automatically. [<a href=\"https:\/\/www.google.com\/goto?url=CAESgwEB6zswFRNbI9WNpcTjrLoqzXdOzh2CP5-727-EwjD_fogD6L0lpysMGK8UidPo0vDfr6ax19oUFB9cvkj6gs7K5YspmGElpWgeCydp_IQdeIn6GzFmmRJiFWkLr2YOv98JAj8rFbWXaxu5pCobd1qb5iynhFVIfpVQyUi8XAXKNapgmA\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESzAEB6zswFcC0XEurZferu0v9MJOFgMQxQq7kKSye0sbjnBi0mtAQI_CszRkPR7U5EMkmpLTdRw-LHjKm8LjWvFtIVICMTUG1Xu6yOpD1neLpfafWp_kFnbd-IBi01qCxRgsAhRFmyAdOntH2JDJ9iksqAHrWKGdL-T1Fr59hHCCtKiy2rMuPdxtJ42gf9HSUitd2hi14TmhT-5cdXcLHqZsyiXXTg6PznjTaigpFeOCntsXNHsn0vH1wbxpXSMaMv4JX_UsMoNiZwUY3wnU\">2<\/a>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">1. Configure the Environment and Orchestrator Logic<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Set up your environment variables including your project endpoint, deployment name, and the versioned Toolbox MCP endpoint. Initialize the <code>AIProjectClient<\/code> with Azure credentials, instantiate the <code>FoundryToolbox<\/code> using your endpoint, and create your orchestrator agent with the toolbox attached as a tool. [<a href=\"https:\/\/www.google.com\/goto?url=CAESzAEB6zswFcC0XEurZferu0v9MJOFgMQxQq7kKSye0sbjnBi0mtAQI_CszRkPR7U5EMkmpLTdRw-LHjKm8LjWvFtIVICMTUG1Xu6yOpD1neLpfafWp_kFnbd-IBi01qCxRgsAhRFmyAdOntH2JDJ9iksqAHrWKGdL-T1Fr59hHCCtKiy2rMuPdxtJ42gf9HSUitd2hi14TmhT-5cdXcLHqZsyiXXTg6PznjTaigpFeOCntsXNHsn0vH1wbxpXSMaMv4JX_UsMoNiZwUY3wnU\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESgwEB6zswFRNbI9WNpcTjrLoqzXdOzh2CP5-727-EwjD_fogD6L0lpysMGK8UidPo0vDfr6ax19oUFB9cvkj6gs7K5YspmGElpWgeCydp_IQdeIn6GzFmmRJiFWkLr2YOv98JAj8rFbWXaxu5pCobd1qb5iynhFVIfpVQyUi8XAXKNapgmA\">2<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESiAEB6zswFZzsCNUCBsjiojLRxc5gD2leExs7tPjDbJskg-3BvewMekTdn9ClOCKqeIDn8EDczGyA9iUBJwpfjgdcwu5S5Z7v51RJ1SVTXubcADnFUmtI_FFjZIfQ_t9oP17KT7SZ2MdgC6PNF794DN2ih1kuxLzTpotG7TRYaA5TonStHT05ktIM\">3<\/a>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2. Execution and Verification Flow<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a complex user prompt is submitted, the framework handles the interaction centrally:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Centralized Auth:<\/strong> The agent connects using managed identity tokens, while individual access to downstream functions and sub-agents is managed via Microsoft Entra ID passthrough. [<a href=\"https:\/\/www.google.com\/goto?url=CAESgwEB6zswFRNbI9WNpcTjrLoqzXdOzh2CP5-727-EwjD_fogD6L0lpysMGK8UidPo0vDfr6ax19oUFB9cvkj6gs7K5YspmGElpWgeCydp_IQdeIn6GzFmmRJiFWkLr2YOv98JAj8rFbWXaxu5pCobd1qb5iynhFVIfpVQyUi8XAXKNapgmA\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESzAEB6zswFcC0XEurZferu0v9MJOFgMQxQq7kKSye0sbjnBi0mtAQI_CszRkPR7U5EMkmpLTdRw-LHjKm8LjWvFtIVICMTUG1Xu6yOpD1neLpfafWp_kFnbd-IBi01qCxRgsAhRFmyAdOntH2JDJ9iksqAHrWKGdL-T1Fr59hHCCtKiy2rMuPdxtJ42gf9HSUitd2hi14TmhT-5cdXcLHqZsyiXXTg6PznjTaigpFeOCntsXNHsn0vH1wbxpXSMaMv4JX_UsMoNiZwUY3wnU\">2<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESiAEB6zswFZzsCNUCBsjiojLRxc5gD2leExs7tPjDbJskg-3BvewMekTdn9ClOCKqeIDn8EDczGyA9iUBJwpfjgdcwu5S5Z7v51RJ1SVTXubcADnFUmtI_FFjZIfQ_t9oP17KT7SZ2MdgC6PNF794DN2ih1kuxLzTpotG7TRYaA5TonStHT05ktIM\">3<\/a>]<\/li>\n\n\n\n<li><strong>Execution:<\/strong> The Toolbox evaluates intent, filters definitions via tool search, applies guardrails, and safely relays parameters and outputs between the orchestrator and your custom MCP servers or A2A sub-agents. [<a href=\"https:\/\/www.google.com\/goto?url=CAESiAEB6zswFZzsCNUCBsjiojLRxc5gD2leExs7tPjDbJskg-3BvewMekTdn9ClOCKqeIDn8EDczGyA9iUBJwpfjgdcwu5S5Z7v51RJ1SVTXubcADnFUmtI_FFjZIfQ_t9oP17KT7SZ2MdgC6PNF794DN2ih1kuxLzTpotG7TRYaA5TonStHT05ktIM\">1<\/a>]<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>NEXT:<\/strong> explore setting up an <strong>Agent-to-Agent (A2A)<\/strong> tool explicitly so that two independent codebases can chat inside this toolbox, or you can configure a <strong>Model Router<\/strong> inside this architecture to balance costs between <code>gpt-4o<\/code> and smaller models like <code>o4-mini<\/code>? [<a href=\"https:\/\/www.google.com\/goto?url=CAESiQEB6zswFdZytkxki76jHLDGU1ITcgWqX4Wtxat7BENerQDCaOJDyNB8wGw5-wBaZkYO-Dj26vCqjp0rZerObnQlUAcvIpwowf7Byl695S_5JJ6AlgnJmk3KEGWQS17lNSHi-zD5Lum2MQ5e71aGPRMZFyGXYszOoupUfwwT0QWFUJxEeNNjGei7cg\">1<\/a>, <a href=\"https:\/\/www.google.com\/goto?url=CAESkgEB6zswFfWil26VCxEQYwmQ1dJ4yuuqPp2yPaYemMXaYnz-bf_GlcMXvmFMD10LVzlEVhF5lwVfb6QhVCIt2yGGGRRNryMOfUWwk_1hF9ceyIQzV__H9gRWA8SwOTptf5m5BaUpOB6G9259e_XjJXWC9Dg3rRVpEXTnKHe65yXWOKoLokSlTLJmd9d30KmTP2WdLQ\">2<\/a>]<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.google.com\/goto?url=CAESkgEB6zswFSi7_jAk8D4BuaSlJFJP-II6B3hwOYKMAheyvfZtpez6x2BA9ecC5E5bBIJ7pStY6ZRccomZWeGH96HmYAMtXnbejLFjW4eyeiFgSNKc7YK0Wy6Vok9geR4-Q72yzWS7lRFYorQuUfcSCIMW88vpIi6-UxVfrvp5q2iNMDLSxsxFLVfyT_Dfnj80cWMKCw\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><img decoding=\"async\" src=\"blob:https:\/\/roboticsmaestro.com\/f8fca186-2413-44d6-8c99-e19712a67e90\" alt=\"\"><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>walk through this in a&nbsp;clear, step-by-step&nbsp;way so you can set up a&nbsp;new MCP (Microsoft Foundry Custom Plugin)&nbsp;using an&nbsp;Azure Function App, integrate it with&nbsp;Microsoft Foundry Tools, and enable&nbsp;Application Insights&nbsp;for monitoring. I\u2019ll break it into&nbsp;three main phases: 1. Create and Configure the Azure Function App Step 1 \u2014 Create a Resource Group az group create &#8211;name my-foundry-mcp-rg [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_regular_price":[],"currency_symbol":[],"pagelayer_contact_templates":[],"_pagelayer_content":"","footnotes":""},"categories":[1],"tags":[],"class_list":["post-53","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"post_slider_layout_featured_media_urls":{"thumbnail":"","post_slider_layout_landscape_large":"","post_slider_layout_portrait_large":"","post_slider_layout_square_large":"","post_slider_layout_landscape":"","post_slider_layout_portrait":"","post_slider_layout_square":"","full":""},"_links":{"self":[{"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/posts\/53","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/comments?post=53"}],"version-history":[{"count":7,"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/posts\/53\/revisions"}],"predecessor-version":[{"id":63,"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/posts\/53\/revisions\/63"}],"wp:attachment":[{"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/media?parent=53"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/categories?post=53"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/roboticsmaestro.com\/ai\/wp-json\/wp\/v2\/tags?post=53"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}