{"id":30866668,"url":"https://github.com/Azure-Samples/remote-mcp-functions-python","last_synced_at":"2025-09-07T22:06:21.075Z","repository":{"id":286028675,"uuid":"960128516","full_name":"Azure-Samples/remote-mcp-functions-python","owner":"Azure-Samples","description":null,"archived":false,"fork":false,"pushed_at":"2025-07-14T05:26:18.000Z","size":107,"stargazers_count":78,"open_issues_count":3,"forks_count":35,"subscribers_count":17,"default_branch":"main","last_synced_at":"2025-07-14T07:38:35.123Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Bicep","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Azure-Samples.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-03T22:53:05.000Z","updated_at":"2025-07-14T05:26:22.000Z","dependencies_parsed_at":"2025-05-03T02:42:03.041Z","dependency_job_id":"1df86a71-33bc-443f-9bb7-dc945c334c68","html_url":"https://github.com/Azure-Samples/remote-mcp-functions-python","commit_stats":null,"previous_names":["azure-samples/remote-mcp-functions-python"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Azure-Samples/remote-mcp-functions-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azure-Samples%2Fremote-mcp-functions-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azure-Samples%2Fremote-mcp-functions-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azure-Samples%2Fremote-mcp-functions-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azure-Samples%2Fremote-mcp-functions-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Azure-Samples","download_url":"https://codeload.github.com/Azure-Samples/remote-mcp-functions-python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Azure-Samples%2Fremote-mcp-functions-python/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274101776,"owners_count":25222448,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-07T02:00:09.463Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-07T22:03:14.989Z","updated_at":"2025-09-07T22:06:21.040Z","avatar_url":"https://github.com/Azure-Samples.png","language":"Bicep","readme":"\u003c!--\n---\nname: Remote MCP with Azure Functions (Python)\ndescription: Run a remote MCP server on Azure functions.  \npage_type: sample\nlanguages:\n- python\n- bicep\n- azdeveloper\nproducts:\n- azure-functions\n- azure\nurlFragment: remote-mcp-functions-python\n---\n--\u003e\n\n# Getting Started with Remote MCP Servers using Azure Functions (Python)\n\nThis is a quickstart template to easily build and deploy a custom remote MCP server to the cloud using Azure Functions with Python. You can clone/restore/run on your local machine with debugging, and `azd up` to have it in the cloud in a couple minutes. The MCP server is secured by design using keys and HTTPS, and allows more options for OAuth using built-in auth and/or [API Management](https://aka.ms/mcp-remote-apim-auth) as well as network isolation using VNET.\n\nIf you're looking for this sample in more languages check out the [.NET/C#](https://github.com/Azure-Samples/remote-mcp-functions-dotnet) and [Node.js/TypeScript](https://github.com/Azure-Samples/remote-mcp-functions-typescript) versions.\n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/Azure-Samples/remote-mcp-functions-python)\n\nBelow is the architecture diagram for the Remote MCP Server using Azure Functions:\n\n![Architecture Diagram](architecture-diagram.png)\n\n## Prerequisites\n\n+ [Python](https://www.python.org/downloads/) version 3.11 or higher\n+ [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local?pivots=programming-language-python#install-the-azure-functions-core-tools) \u003e= `4.0.7030`\n+ [Azure Developer CLI](https://aka.ms/azd)\n+ To use Visual Studio Code to run and debug locally:\n  + [Visual Studio Code](https://code.visualstudio.com/)\n  + [Azure Functions extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)\n\n## Prepare your local environment\n\nAn Azure Storage Emulator is needed for this particular sample because we will save and get snippets from blob storage.\n\n1. Start Azurite\n\n    ```shell\n    docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \\\n        mcr.microsoft.com/azure-storage/azurite\n    ```\n\n\u003e**Note** if you use Azurite coming from VS Code extension you need to run `Azurite: Start` now or you will see errors.\n\n## Run your MCP Server locally from the terminal\n\n1. Change to the src folder in a new terminal window:\n\n   ```shell\n   cd src\n   ```\n\n1. Install Python dependencies:\n\n   ```shell\n   pip install -r requirements.txt\n   ```\n\n\u003e**Note** it is a best practice to create a Virtual Environment before doing the `pip install` to avoid dependency issues/collisions, or if you are running in CodeSpaces.  See [Python Environments in VS Code](https://code.visualstudio.com/docs/python/environments#_creating-environments) for more information.\n\n1. Start the Functions host locally:\n\n   ```shell\n   func start\n   ```\n\n\u003e **Note** by default this will use the webhooks route: `/runtime/webhooks/mcp/sse`.  Later we will use this in Azure to set the key on client/host calls: `/runtime/webhooks/mcp/sse?code=\u003csystem_key\u003e`\n\n## Connect to the *local* MCP server from a client/host\n\n### VS Code - Copilot agent mode\n\n1. **Add MCP Server** from command palette and add URL to your running Function app's SSE endpoint:\n\n    ```shell\n    http://0.0.0.0:7071/runtime/webhooks/mcp/sse\n    ```\n\n1. **List MCP Servers** from command palette and start the server\n1. In Copilot chat agent mode enter a prompt to trigger the tool, e.g., select some code and enter this prompt\n\n    ```plaintext\n    Say Hello\n    ```\n\n    ```plaintext\n    Save this snippet as snippet1 \n    ```\n\n    ```plaintext\n    Retrieve snippet1 and apply to newFile.py\n    ```\n\n1. When prompted to run the tool, consent by clicking **Continue**\n\n1. When you're done, press Ctrl+C in the terminal window to stop the Functions host process.\n\n### MCP Inspector\n\n1. In a **new terminal window**, install and run MCP Inspector\n\n    ```shell\n    npx @modelcontextprotocol/inspector\n    ```\n\n2. CTRL click to load the MCP Inspector web app from the URL displayed by the app (e.g. http://0.0.0.0:5173/#resources)\n3. Set the transport type to `SSE`\n4. Set the URL to your running Function app's SSE endpoint and **Connect**:\n\n    ```shell\n    http://0.0.0.0:7071/runtime/webhooks/mcp/sse\n    ```\n\n\u003e**Note** this step will not work in CodeSpaces.  Please move on to Deploy to Remote MCP.  \n\n5. **List Tools**.  Click on a tool and **Run Tool**.\n\n## Verify local blob storage in Azurite\n\nAfter testing the snippet save functionality locally, you can verify that blobs are being stored correctly in your local Azurite storage emulator.\n\n### Using Azure Storage Explorer\n\n1. Open Azure Storage Explorer\n1. In the left panel, expand **Emulator \u0026 Attached** → **Storage Accounts** → **(Emulator - Default Ports) (Key)**\n1. Navigate to **Blob Containers** → **snippets**\n1. You should see any saved snippets as blob files in this container\n1. Double-click on any blob to view its contents and verify the snippet data was saved correctly\n\n### Using Azure CLI (Alternative)\n\nIf you prefer using the command line, you can also verify blobs using Azure CLI with the storage emulator:\n\n```shell\n# List blobs in the snippets container\naz storage blob list --container-name snippets --connection-string \"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;\"\n```\n\n```shell\n# Download a specific blob to view its contents\naz storage blob download --container-name snippets --name \u003cblob-name\u003e --file \u003clocal-file-path\u003e --connection-string \"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;\"\n```\n\nThis verification step ensures your MCP server is correctly interacting with the local storage emulator and that the blob storage functionality is working as expected before deploying to Azure.\n\n## Deploy to Azure for Remote MCP\n\nRun this [azd](https://aka.ms/azd) command to provision the function app, with any required Azure resources, and deploy your code:\n\n```shell\nazd up\n```\n\nYou can opt-in to a VNet being used in the sample. To do so, do this before `azd up`\n\n```bash\nazd env set VNET_ENABLED true\n```\n\nAdditionally, [API Management]() can be used for improved security and policies over your MCP Server, and [App Service built-in authentication](https://learn.microsoft.com/azure/app-service/overview-authentication-authorization) can be used to set up your favorite OAuth provider including Entra.  \n\n## Connect to your *remote* MCP server function app from a client\n\nYour client will need a key in order to invoke the new hosted SSE endpoint, which will be of the form `https://\u003cfuncappname\u003e.azurewebsites.net/runtime/webhooks/mcp/sse`. The hosted function requires a system key by default which can be obtained from the [portal](https://learn.microsoft.com/azure/azure-functions/function-keys-how-to?tabs=azure-portal) or the CLI (`az functionapp keys list --resource-group \u003cresource_group\u003e --name \u003cfunction_app_name\u003e`). Obtain the system key named `mcp_extension`.\n\n### Connect to remote MCP server in MCP Inspector\nFor MCP Inspector, you can include the key in the URL: \n```plaintext\nhttps://\u003cfuncappname\u003e.azurewebsites.net/runtime/webhooks/mcp/sse?code=\u003cyour-mcp-extension-system-key\u003e\n```\n\n### Connect to remote MCP server in VS Code - GitHub Copilot\nFor GitHub Copilot within VS Code, you should instead set the key as the `x-functions-key` header in `mcp.json`, and you would just use `https://\u003cfuncappname\u003e.azurewebsites.net/runtime/webhooks/mcp/sse` for the URL. The following example uses an input and will prompt you to provide the key when you start the server from VS Code.  Note [mcp.json](.vscode/mcp.json) has already been included in this repo and will be picked up by VS Code.  Click Start on the server to be prompted for values including `functionapp-name` (in your /.azure/*/.env file) and `functions-mcp-extension-system-key` which can be obtained from CLI command above or API Keys in the portal for the Function App.  \n\n```json\n{\n    \"inputs\": [\n        {\n            \"type\": \"promptString\",\n            \"id\": \"functions-mcp-extension-system-key\",\n            \"description\": \"Azure Functions MCP Extension System Key\",\n            \"password\": true\n        },\n        {\n            \"type\": \"promptString\",\n            \"id\": \"functionapp-name\",\n            \"description\": \"Azure Functions App Name\"\n        }\n    ],\n    \"servers\": {\n        \"remote-mcp-function\": {\n            \"type\": \"sse\",\n            \"url\": \"https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp/sse\",\n            \"headers\": {\n                \"x-functions-key\": \"${input:functions-mcp-extension-system-key}\"\n            }\n        },\n        \"local-mcp-function\": {\n            \"type\": \"sse\",\n            \"url\": \"http://0.0.0.0:7071/runtime/webhooks/mcp/sse\"\n        }\n    }\n}\n```\n\nFor MCP Inspector, you can include the key in the URL: `https://\u003cfuncappname\u003e.azurewebsites.net/runtime/webhooks/mcp/sse?code=\u003cyour-mcp-extension-system-key\u003e`.\n\nFor GitHub Copilot within VS Code, you should instead set the key as the `x-functions-key` header in `mcp.json`, and you would just use `https://\u003cfuncappname\u003e.azurewebsites.net/runtime/webhooks/mcp/sse` for the URL. The following example uses an input and will prompt you to provide the key when you start the server from VS Code:\n\n```json\n{\n    \"inputs\": [\n        {\n            \"type\": \"promptString\",\n            \"id\": \"functions-mcp-extension-system-key\",\n            \"description\": \"Azure Functions MCP Extension System Key\",\n            \"password\": true\n        }\n    ],\n    \"servers\": {\n        \"my-mcp-server\": {\n            \"type\": \"sse\",\n            \"url\": \"\u003cfuncappname\u003e.azurewebsites.net/runtime/webhooks/mcp/sse\",\n            \"headers\": {\n                \"x-functions-key\": \"${input:functions-mcp-extension-system-key}\"\n            }\n        }\n    }\n}\n```\n\n## Redeploy your code\n\nYou can run the `azd up` command as many times as you need to both provision your Azure resources and deploy code updates to your function app.\n\n\u003e[!NOTE]\n\u003eDeployed code files are always overwritten by the latest deployment package.\n\n## Clean up resources\n\nWhen you're done working with your function app and related resources, you can use this command to delete the function app and its related resources from Azure and avoid incurring any further costs:\n\n```shell\nazd down\n```\n\n## Helpful Azure Commands\n\nOnce your application is deployed, you can use these commands to manage and monitor your application:\n\n```bash\n# Get your function app name from the environment file\nFUNCTION_APP_NAME=$(cat .azure/$(cat .azure/config.json | jq -r '.defaultEnvironment')/env.json | jq -r '.FUNCTION_APP_NAME')\necho $FUNCTION_APP_NAME\n\n# Get resource group \nRESOURCE_GROUP=$(cat .azure/$(cat .azure/config.json | jq -r '.defaultEnvironment')/env.json | jq -r '.AZURE_RESOURCE_GROUP')\necho $RESOURCE_GROUP\n\n# View function app logs\naz webapp log tail --name $FUNCTION_APP_NAME --resource-group $RESOURCE_GROUP\n\n# Redeploy the application without provisioning new resources\nazd deploy\n```\n\n## Source Code\n\nThe function code for the `get_snippet` and `save_snippet` endpoints are defined in the Python files in the `src` directory. The MCP function annotations expose these functions as MCP Server tools.\n\nHere's the actual code from the function_app.py file:\n\n```python\n\n@app.generic_trigger(arg_name=\"context\", type=\"mcpToolTrigger\", toolName=\"hello\", \n                     description=\"Hello world.\", \n                     toolProperties=\"[]\")\ndef hello_mcp(context) -\u003e None:\n    \"\"\"\n    A simple function that returns a greeting message.\n\n    Args:\n        context: The trigger context (not used in this function).\n\n    Returns:\n        str: A greeting message.\n    \"\"\"\n    return \"Hello I am MCPTool!\"\n\n\n@app.generic_trigger(\n    arg_name=\"context\",\n    type=\"mcpToolTrigger\",\n    toolName=\"getsnippet\",\n    description=\"Retrieve a snippet by name.\",\n    toolProperties=tool_properties_get_snippets_json\n)\n@app.generic_input_binding(\n    arg_name=\"file\",\n    type=\"blob\",\n    connection=\"AzureWebJobsStorage\",\n    path=_BLOB_PATH\n)\ndef get_snippet(file: func.InputStream, context) -\u003e str:\n    \"\"\"\n    Retrieves a snippet by name from Azure Blob Storage.\n \n    Args:\n        file (func.InputStream): The input binding to read the snippet from Azure Blob Storage.\n        context: The trigger context containing the input arguments.\n \n    Returns:\n        str: The content of the snippet or an error message.\n    \"\"\"\n    snippet_content = file.read().decode(\"utf-8\")\n    logging.info(f\"Retrieved snippet: {snippet_content}\")\n    return snippet_content\n\n\n@app.generic_trigger(\n    arg_name=\"context\",\n    type=\"mcpToolTrigger\",\n    toolName=\"savesnippet\",\n    description=\"Save a snippet with a name.\",\n    toolProperties=tool_properties_save_snippets_json\n)                   \n@app.generic_output_binding(\n    arg_name=\"file\",\n    type=\"blob\",\n    connection=\"AzureWebJobsStorage\",\n    path=_BLOB_PATH\n)\ndef save_snippet(file: func.Out[str], context) -\u003e str:\n    content = json.loads(context)\n    snippet_name_from_args = content[\"arguments\"][_SNIPPET_NAME_PROPERTY_NAME]\n    snippet_content_from_args = content[\"arguments\"][_SNIPPET_PROPERTY_NAME]\n\n    if not snippet_name_from_args:\n        return \"No snippet name provided\"\n\n    if not snippet_content_from_args:\n        return \"No snippet content provided\"\n \n    file.set(snippet_content_from_args)\n    logging.info(f\"Saved snippet: {snippet_content_from_args}\")\n    return f\"Snippet '{snippet_content_from_args}' saved successfully\"\n```\n\nNote that the `host.json` file also includes a reference to the experimental bundle, which is required for apps using this feature:\n\n```json\n\"extensionBundle\": {\n  \"id\": \"Microsoft.Azure.Functions.ExtensionBundle.Experimental\",\n  \"version\": \"[4.*, 5.0.0)\"\n}\n```\n\n## Next Steps\n\n- Add [API Management](https://aka.ms/mcp-remote-apim-auth) to your MCP server (auth, gateway, policies, more!)\n- Add [built-in auth](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization) to your MCP server\n- Enable VNET using VNET_ENABLED=true flag\n- Learn more about [related MCP efforts from Microsoft](https://github.com/microsoft/mcp/tree/main/Resources)\n","funding_links":[],"categories":["🤖 AI/ML"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAzure-Samples%2Fremote-mcp-functions-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAzure-Samples%2Fremote-mcp-functions-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAzure-Samples%2Fremote-mcp-functions-python/lists"}