From 6062bcd26e7a2a8e0bbc8f504a80a9cdacc56e1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 04:06:18 +0000 Subject: [PATCH 1/5] Initial plan From ef1cfa2e5097421e0e0432ae6f8335163e1b312d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 04:13:53 +0000 Subject: [PATCH 2/5] Implement Python Azure Functions with MCP endpoints for GetTelemetry and SendAction Co-authored-by: KevinDMack <7958118+KevinDMack@users.noreply.github.com> --- .gitignore | 24 ++++- functions/.funcignore | 11 +++ functions/GetTelemetry/__init__.py | 74 ++++++++++++++ functions/GetTelemetry/function.json | 19 ++++ functions/README.md | 140 +++++++++++++++++++++++++++ functions/SendAction/__init__.py | 72 ++++++++++++++ functions/SendAction/function.json | 19 ++++ functions/host.json | 15 +++ functions/requirements.txt | 2 + infra/modules/functionApp.bicep | 6 +- infra/modules/serviceBus.bicep | 22 +++++ 11 files changed, 398 insertions(+), 6 deletions(-) create mode 100644 functions/.funcignore create mode 100644 functions/GetTelemetry/__init__.py create mode 100644 functions/GetTelemetry/function.json create mode 100644 functions/README.md create mode 100644 functions/SendAction/__init__.py create mode 100644 functions/SendAction/function.json create mode 100644 functions/host.json create mode 100644 functions/requirements.txt diff --git a/.gitignore b/.gitignore index 928cc92..6b25943 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Bicep compiled files -*.json +infra/**/*.json +!infra/main.bicepparam # Build artifacts dist/ @@ -9,6 +10,27 @@ out/ # Dependency directories node_modules/ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +.venv +pip-log.txt +pip-delete-this-directory.txt +.pytest_cache/ +*.egg-info/ + +# Azure Functions +bin/ +obj/ +appsettings.json +local.settings.json + # Environment files .env .env.local diff --git a/functions/.funcignore b/functions/.funcignore new file mode 100644 index 0000000..2873891 --- /dev/null +++ b/functions/.funcignore @@ -0,0 +1,11 @@ +.git* +.vscode +__azurite_db*__.json +__blobstorage__ +__queuestorage__ +local.settings.json +test +.python_packages +.venv +venv +env diff --git a/functions/GetTelemetry/__init__.py b/functions/GetTelemetry/__init__.py new file mode 100644 index 0000000..90ca04e --- /dev/null +++ b/functions/GetTelemetry/__init__.py @@ -0,0 +1,74 @@ +import json +import logging +import os +import azure.functions as func +from azure.servicebus import ServiceBusClient, ServiceBusMessage + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('GetTelemetry function processing a request.') + + try: + # Parse the request body + req_body = req.get_json() + + # Validate required fields + if not req_body: + return func.HttpResponse( + "Please pass a TelemetryRequest in the request body", + status_code=400 + ) + + sensor_key = req_body.get('SensorKey') + start_date = req_body.get('StartDate') + end_date = req_body.get('EndDate') + + # Validate that all required fields are present + if not sensor_key or not start_date or not end_date: + return func.HttpResponse( + "TelemetryRequest must include SensorKey, StartDate, and EndDate", + status_code=400 + ) + + # Create the telemetry request message + telemetry_request = { + 'SensorKey': sensor_key, + 'StartDate': start_date, + 'EndDate': end_date + } + + # Get Service Bus connection string from environment + connection_string = os.environ.get('ServiceBusConnectionString') + + if not connection_string: + logging.error('ServiceBusConnectionString not configured') + return func.HttpResponse( + "Service Bus connection not configured", + status_code=500 + ) + + # Send message to Service Bus topic + with ServiceBusClient.from_connection_string(connection_string) as client: + with client.get_topic_sender(topic_name="Telemetry") as sender: + message = ServiceBusMessage(json.dumps(telemetry_request)) + sender.send_messages(message) + logging.info(f'Sent telemetry request to Service Bus topic: {telemetry_request}') + + return func.HttpResponse( + json.dumps({"status": "success", "message": "Telemetry request sent"}), + mimetype="application/json", + status_code=200 + ) + + except ValueError as e: + logging.error(f'Invalid JSON in request: {str(e)}') + return func.HttpResponse( + "Invalid JSON in request body", + status_code=400 + ) + except Exception as e: + logging.error(f'Error processing request: {str(e)}') + return func.HttpResponse( + f"Error processing request: {str(e)}", + status_code=500 + ) diff --git a/functions/GetTelemetry/function.json b/functions/GetTelemetry/function.json new file mode 100644 index 0000000..9dc8d3f --- /dev/null +++ b/functions/GetTelemetry/function.json @@ -0,0 +1,19 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/functions/README.md b/functions/README.md new file mode 100644 index 0000000..a764ab3 --- /dev/null +++ b/functions/README.md @@ -0,0 +1,140 @@ +# Azure Functions - MCP Endpoints + +This directory contains Python-based Azure Functions that provide MCP (Model Context Protocol) endpoints for the Pi Chat application. + +## Functions + +### GetTelemetry + +HTTP POST endpoint that receives telemetry requests and forwards them to the Service Bus "Telemetry" topic. + +**Request Body (JSON):** +```json +{ + "SensorKey": "string", + "StartDate": "ISO 8601 datetime string", + "EndDate": "ISO 8601 datetime string" +} +``` + +**Response:** +```json +{ + "status": "success", + "message": "Telemetry request sent" +} +``` + +### SendAction + +HTTP POST endpoint that receives action requests and forwards them to the Service Bus "Action" topic. + +**Request Body (JSON):** +```json +{ + "ActionType": "string", + "ActionSpec": "string (raw JSON)" +} +``` + +**Response:** +```json +{ + "status": "success", + "message": "Action request sent" +} +``` + +## Local Development + +### Prerequisites + +- Python 3.9 or higher +- Azure Functions Core Tools +- Azure Service Bus namespace + +### Setup + +1. Install dependencies: +```bash +cd functions +pip install -r requirements.txt +``` + +2. Configure local settings: +Edit `local.settings.json` and add your Service Bus connection string: +```json +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "python", + "ServiceBusConnectionString": "Endpoint=sb://..." + } +} +``` + +3. Run locally: +```bash +func start +``` + +## Deployment + +Deploy to Azure using Azure Functions Core Tools: + +```bash +func azure functionapp publish +``` + +Or use the Azure CLI after infrastructure deployment: + +```bash +# Get function app name from infrastructure output +FUNC_NAME=$(az deployment group show \ + --resource-group rg-pichat-dev \ + --name main \ + --query properties.outputs.functionAppName.value -o tsv) + +# Deploy from functions directory +cd functions +func azure functionapp publish $FUNC_NAME +``` + +## Testing + +### Test GetTelemetry + +```bash +curl -X POST https://.azurewebsites.net/api/GetTelemetry \ + -H "Content-Type: application/json" \ + -H "x-functions-key: " \ + -d '{ + "SensorKey": "sensor-001", + "StartDate": "2025-01-01T00:00:00Z", + "EndDate": "2025-01-02T00:00:00Z" + }' +``` + +### Test SendAction + +```bash +curl -X POST https://.azurewebsites.net/api/SendAction \ + -H "Content-Type: application/json" \ + -H "x-functions-key: " \ + -d '{ + "ActionType": "turn_on", + "ActionSpec": "{\"device\": \"led\", \"pin\": 17}" + }' +``` + +## Architecture + +``` +HTTP Request → Azure Function → Service Bus Topic → Message Processing +``` + +- **GetTelemetry**: Sends messages to the "Telemetry" topic +- **SendAction**: Sends messages to the "Action" topic + +Both functions use the Service Bus connection string from the `ServiceBusConnectionString` environment variable, which is automatically configured during infrastructure deployment. diff --git a/functions/SendAction/__init__.py b/functions/SendAction/__init__.py new file mode 100644 index 0000000..3eea80d --- /dev/null +++ b/functions/SendAction/__init__.py @@ -0,0 +1,72 @@ +import json +import logging +import os +import azure.functions as func +from azure.servicebus import ServiceBusClient, ServiceBusMessage + + +def main(req: func.HttpRequest) -> func.HttpResponse: + logging.info('SendAction function processing a request.') + + try: + # Parse the request body + req_body = req.get_json() + + # Validate required fields + if not req_body: + return func.HttpResponse( + "Please pass an ActionRequest in the request body", + status_code=400 + ) + + action_type = req_body.get('ActionType') + action_spec = req_body.get('ActionSpec') + + # Validate that all required fields are present + if not action_type or not action_spec: + return func.HttpResponse( + "ActionRequest must include ActionType and ActionSpec", + status_code=400 + ) + + # Create the action request message + action_request = { + 'ActionType': action_type, + 'ActionSpec': action_spec + } + + # Get Service Bus connection string from environment + connection_string = os.environ.get('ServiceBusConnectionString') + + if not connection_string: + logging.error('ServiceBusConnectionString not configured') + return func.HttpResponse( + "Service Bus connection not configured", + status_code=500 + ) + + # Send message to Service Bus topic + with ServiceBusClient.from_connection_string(connection_string) as client: + with client.get_topic_sender(topic_name="Action") as sender: + message = ServiceBusMessage(json.dumps(action_request)) + sender.send_messages(message) + logging.info(f'Sent action request to Service Bus topic: {action_request}') + + return func.HttpResponse( + json.dumps({"status": "success", "message": "Action request sent"}), + mimetype="application/json", + status_code=200 + ) + + except ValueError as e: + logging.error(f'Invalid JSON in request: {str(e)}') + return func.HttpResponse( + "Invalid JSON in request body", + status_code=400 + ) + except Exception as e: + logging.error(f'Error processing request: {str(e)}') + return func.HttpResponse( + f"Error processing request: {str(e)}", + status_code=500 + ) diff --git a/functions/SendAction/function.json b/functions/SendAction/function.json new file mode 100644 index 0000000..9dc8d3f --- /dev/null +++ b/functions/SendAction/function.json @@ -0,0 +1,19 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "authLevel": "function", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "$return" + } + ] +} diff --git a/functions/host.json b/functions/host.json new file mode 100644 index 0000000..d1a0a92 --- /dev/null +++ b/functions/host.json @@ -0,0 +1,15 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "maxTelemetryItemsPerSecond": 20 + } + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + } +} diff --git a/functions/requirements.txt b/functions/requirements.txt new file mode 100644 index 0000000..e51d91f --- /dev/null +++ b/functions/requirements.txt @@ -0,0 +1,2 @@ +azure-functions +azure-servicebus>=7.11.0 diff --git a/infra/modules/functionApp.bicep b/infra/modules/functionApp.bicep index f8f6fda..529ed62 100644 --- a/infra/modules/functionApp.bicep +++ b/infra/modules/functionApp.bicep @@ -64,11 +64,7 @@ resource functionApp 'Microsoft.Web/sites@2022-09-01' = { } { name: 'FUNCTIONS_WORKER_RUNTIME' - value: 'node' - } - { - name: 'WEBSITE_NODE_DEFAULT_VERSION' - value: '~18' + value: 'python' } { name: 'ServiceBusConnectionString' diff --git a/infra/modules/serviceBus.bicep b/infra/modules/serviceBus.bicep index 7f3833f..2702d98 100644 --- a/infra/modules/serviceBus.bicep +++ b/infra/modules/serviceBus.bicep @@ -34,6 +34,28 @@ resource serviceBusQueue 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-prev } } +resource telemetryTopic 'Microsoft.ServiceBus/namespaces/topics@2022-10-01-preview' = { + parent: serviceBusNamespace + name: 'Telemetry' + properties: { + maxSizeInMegabytes: 1024 + requiresDuplicateDetection: false + defaultMessageTimeToLive: 'P14D' + enableBatchedOperations: true + } +} + +resource actionTopic 'Microsoft.ServiceBus/namespaces/topics@2022-10-01-preview' = { + parent: serviceBusNamespace + name: 'Action' + properties: { + maxSizeInMegabytes: 1024 + requiresDuplicateDetection: false + defaultMessageTimeToLive: 'P14D' + enableBatchedOperations: true + } +} + resource serviceBusAuthRule 'Microsoft.ServiceBus/namespaces/authorizationRules@2022-10-01-preview' = { parent: serviceBusNamespace name: 'RootManageSharedAccessKey' From cf6e90362fa8ab8118e92cd576af32d6a5e7c7af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 04:15:12 +0000 Subject: [PATCH 3/5] Update infrastructure documentation for Python Azure Functions Co-authored-by: KevinDMack <7958118+KevinDMack@users.noreply.github.com> --- infra/DEPLOYMENT.md | 15 ++++++++++++--- infra/README.md | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/infra/DEPLOYMENT.md b/infra/DEPLOYMENT.md index faeb3cc..a329ea6 100644 --- a/infra/DEPLOYMENT.md +++ b/infra/DEPLOYMENT.md @@ -56,9 +56,11 @@ az deployment group show \ | App Service | `pichat-dev-app` | Runs the chat application | | Storage Account | `pichatdevfuncsa` | Required for Function App | | App Service Plan (Consumption) | `pichat-dev-func-plan` | Hosts the function | -| Function App | `pichat-dev-func` | Processes HTTP requests | +| Function App | `pichat-dev-func` | Python-based MCP endpoints | | Service Bus Namespace | `pichat-dev` | Message broker | | Service Bus Queue | `requests` | Stores messages | +| Service Bus Topic | `Telemetry` | Telemetry data routing | +| Service Bus Topic | `Action` | Action command routing | | OpenAI Service | `pichat-dev-openai` | AI model service (gpt-4o-mini) | | AI Foundry Hub | `pichat-dev-ai-hub` | Machine learning workspace | | Storage Account (AI) | `pichatdevai` | Storage for AI Hub | @@ -92,10 +94,17 @@ FUNC_NAME=$(az deployment group show \ --name main \ --query properties.outputs.functionAppName.value -o tsv) -# Deploy your function code (example with zip) -func azure functionapp publish $FUNC_NAME +# Navigate to functions directory and deploy +cd ../functions +func azure functionapp publish $FUNC_NAME --python ``` +**Note:** The Function App includes two Python-based MCP endpoints: +- **GetTelemetry**: Receives telemetry requests and sends them to the "Telemetry" Service Bus topic +- **SendAction**: Receives action requests and sends them to the "Action" Service Bus topic + +See [functions/README.md](../functions/README.md) for detailed endpoint documentation and testing examples. + ### 3. Restart App Service ```bash # Get app service name diff --git a/infra/README.md b/infra/README.md index a5f5797..1047dd2 100644 --- a/infra/README.md +++ b/infra/README.md @@ -10,11 +10,13 @@ The infrastructure deploys the following Azure resources: 2. **Azure App Service** - Hosts the containerized chat application - Connects to ACR via Managed Identity for secure image pulling - No credentials needed for registry authentication -3. **Azure Function App** - Receives HTTP requests and forwards them to Service Bus - - Configured with Node.js runtime +3. **Azure Function App** - Python-based MCP endpoints for telemetry and action requests + - Configured with Python runtime - Consumption (serverless) plan for cost efficiency -4. **Azure Service Bus** - Message queue for async request processing + - Two HTTP endpoints: GetTelemetry and SendAction +4. **Azure Service Bus** - Message broker for async request processing - Standard tier with a queue named 'requests' + - Topics: 'Telemetry' for sensor data and 'Action' for device commands - Connected to the Function App for message handling 5. **Azure OpenAI Service** - Provides AI capabilities with GPT-4o-mini model - Deployed with gpt-4o-mini model for chat completions @@ -117,20 +119,27 @@ Example for dev environment: #### Function App - **Plan**: Consumption (Y1) -- **Runtime**: Node.js 18 +- **Runtime**: Python 3.x - **Functions Version**: 4 - **Includes**: Storage account for function state +- **Endpoints**: + - `GetTelemetry`: Receives telemetry requests and sends to "Telemetry" topic + - `SendAction`: Receives action requests and sends to "Action" topic - **Environment Variables**: - `ServiceBusConnectionString`: Connection to Service Bus - `ServiceBusQueueName`: Name of the queue to send messages to #### Service Bus - **Tier**: Standard -- **Queue Properties**: +- **Queue**: `requests` - Lock duration: 5 minutes - Max size: 1024 MB - Message TTL: 14 days - Max delivery count: 10 +- **Topics**: `Telemetry` and `Action` + - Max size: 1024 MB + - Message TTL: 14 days + - Batch operations: Enabled #### Azure OpenAI Service - **SKU**: S0 @@ -204,6 +213,22 @@ az group delete --name rg-pichat-dev --yes --no-wait ## Next Steps 1. Push a container image to the Container Registry -2. Deploy Function App code to handle HTTP requests +2. Deploy Python Function App code to handle MCP requests + - See [functions/README.md](../functions/README.md) for detailed documentation + - Deploy using: `func azure functionapp publish --python` 3. Configure the App Service to use the deployed container image 4. Set up CI/CD pipeline for automated deployments + +## Function Endpoints + +The deployed Function App provides two MCP endpoints: + +- **GetTelemetry** (`POST /api/GetTelemetry`) + - Accepts telemetry requests with SensorKey, StartDate, EndDate + - Sends messages to Service Bus "Telemetry" topic + +- **SendAction** (`POST /api/SendAction`) + - Accepts action requests with ActionType and ActionSpec + - Sends messages to Service Bus "Action" topic + +For detailed API documentation and testing examples, see [functions/README.md](../functions/README.md). From c74ef75b3d4cbe95b3eda38f4993a9d46a5d3a42 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 04:17:08 +0000 Subject: [PATCH 4/5] Add test scripts and update documentation for endpoint testing Co-authored-by: KevinDMack <7958118+KevinDMack@users.noreply.github.com> --- functions/README.md | 32 +++++++++-- functions/test_endpoints.py | 107 ++++++++++++++++++++++++++++++++++++ functions/test_endpoints.sh | 65 ++++++++++++++++++++++ 3 files changed, 198 insertions(+), 6 deletions(-) create mode 100755 functions/test_endpoints.py create mode 100755 functions/test_endpoints.sh diff --git a/functions/README.md b/functions/README.md index a764ab3..39aeae0 100644 --- a/functions/README.md +++ b/functions/README.md @@ -103,12 +103,33 @@ func azure functionapp publish $FUNC_NAME ## Testing -### Test GetTelemetry +### Automated Testing + +Use the provided test scripts to test both endpoints: + +**Bash script:** +```bash +./test_endpoints.sh https://pichat-dev-func.azurewebsites.net +``` + +**Python script:** +```bash +python test_endpoints.py https://pichat-dev-func.azurewebsites.net +``` + +To get your function key: +```bash +FUNC_NAME=pichat-dev-func +az functionapp keys list --name $FUNC_NAME --resource-group rg-pichat-dev --query functionKeys.default -o tsv +``` + +### Manual Testing with curl + +#### Test GetTelemetry ```bash -curl -X POST https://.azurewebsites.net/api/GetTelemetry \ +curl -X POST https://.azurewebsites.net/api/GetTelemetry?code= \ -H "Content-Type: application/json" \ - -H "x-functions-key: " \ -d '{ "SensorKey": "sensor-001", "StartDate": "2025-01-01T00:00:00Z", @@ -116,12 +137,11 @@ curl -X POST https://.azurewebsites.net/api/GetTelemetry \ }' ``` -### Test SendAction +#### Test SendAction ```bash -curl -X POST https://.azurewebsites.net/api/SendAction \ +curl -X POST https://.azurewebsites.net/api/SendAction?code= \ -H "Content-Type: application/json" \ - -H "x-functions-key: " \ -d '{ "ActionType": "turn_on", "ActionSpec": "{\"device\": \"led\", \"pin\": 17}" diff --git a/functions/test_endpoints.py b/functions/test_endpoints.py new file mode 100755 index 0000000..0f36465 --- /dev/null +++ b/functions/test_endpoints.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Test script for Azure Function MCP endpoints +Usage: python test_endpoints.py +Example: python test_endpoints.py https://pichat-dev-func.azurewebsites.net abcd1234== +""" + +import sys +import json +import requests +from datetime import datetime, timedelta + + +def test_get_telemetry(base_url: str, function_key: str) -> bool: + """Test the GetTelemetry endpoint""" + url = f"{base_url}/api/GetTelemetry?code={function_key}" + + # Create test payload + payload = { + "SensorKey": "sensor-001", + "StartDate": (datetime.now() - timedelta(days=1)).isoformat() + "Z", + "EndDate": datetime.now().isoformat() + "Z" + } + + print("Testing GetTelemetry endpoint...") + print(f" URL: {url}") + print(f" Payload: {json.dumps(payload, indent=2)}") + + try: + response = requests.post(url, json=payload, headers={"Content-Type": "application/json"}) + print(f" Status Code: {response.status_code}") + print(f" Response: {response.text}") + + if response.status_code == 200: + print("✓ GetTelemetry test PASSED") + return True + else: + print("✗ GetTelemetry test FAILED") + return False + except Exception as e: + print(f"✗ GetTelemetry test FAILED with exception: {str(e)}") + return False + + +def test_send_action(base_url: str, function_key: str) -> bool: + """Test the SendAction endpoint""" + url = f"{base_url}/api/SendAction?code={function_key}" + + # Create test payload + payload = { + "ActionType": "turn_on", + "ActionSpec": json.dumps({"device": "led", "pin": 17}) + } + + print("\nTesting SendAction endpoint...") + print(f" URL: {url}") + print(f" Payload: {json.dumps(payload, indent=2)}") + + try: + response = requests.post(url, json=payload, headers={"Content-Type": "application/json"}) + print(f" Status Code: {response.status_code}") + print(f" Response: {response.text}") + + if response.status_code == 200: + print("✓ SendAction test PASSED") + return True + else: + print("✗ SendAction test FAILED") + return False + except Exception as e: + print(f"✗ SendAction test FAILED with exception: {str(e)}") + return False + + +def main(): + if len(sys.argv) < 3: + print("Usage: python test_endpoints.py ") + print("Example: python test_endpoints.py https://pichat-dev-func.azurewebsites.net abcd1234==") + sys.exit(1) + + base_url = sys.argv[1].rstrip('/') + function_key = sys.argv[2] + + print("Testing MCP Endpoints...") + print("=" * 50) + print() + + # Run tests + test1_passed = test_get_telemetry(base_url, function_key) + test2_passed = test_send_action(base_url, function_key) + + # Summary + print("\n" + "=" * 50) + print("Test Summary:") + print(f" GetTelemetry: {'PASSED' if test1_passed else 'FAILED'}") + print(f" SendAction: {'PASSED' if test2_passed else 'FAILED'}") + + if test1_passed and test2_passed: + print("\n✓ All tests PASSED") + sys.exit(0) + else: + print("\n✗ Some tests FAILED") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/functions/test_endpoints.sh b/functions/test_endpoints.sh new file mode 100755 index 0000000..f10a929 --- /dev/null +++ b/functions/test_endpoints.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Test script for Azure Function MCP endpoints +# Usage: ./test_endpoints.sh + +if [ $# -lt 2 ]; then + echo "Usage: $0 " + echo "Example: $0 https://pichat-dev-func.azurewebsites.net abcd1234==" + exit 1 +fi + +FUNCTION_URL=$1 +FUNCTION_KEY=$2 + +echo "Testing MCP Endpoints..." +echo "========================" +echo "" + +# Test GetTelemetry endpoint +echo "1. Testing GetTelemetry endpoint..." +TELEMETRY_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ + "${FUNCTION_URL}/api/GetTelemetry?code=${FUNCTION_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "SensorKey": "sensor-001", + "StartDate": "2025-01-01T00:00:00Z", + "EndDate": "2025-01-02T00:00:00Z" + }') + +HTTP_CODE=$(echo "$TELEMETRY_RESPONSE" | tail -n1) +RESPONSE_BODY=$(echo "$TELEMETRY_RESPONSE" | sed '$d') + +if [ "$HTTP_CODE" == "200" ]; then + echo "✓ GetTelemetry test PASSED" + echo " Response: $RESPONSE_BODY" +else + echo "✗ GetTelemetry test FAILED (HTTP $HTTP_CODE)" + echo " Response: $RESPONSE_BODY" +fi + +echo "" + +# Test SendAction endpoint +echo "2. Testing SendAction endpoint..." +ACTION_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ + "${FUNCTION_URL}/api/SendAction?code=${FUNCTION_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "ActionType": "turn_on", + "ActionSpec": "{\"device\": \"led\", \"pin\": 17}" + }') + +HTTP_CODE=$(echo "$ACTION_RESPONSE" | tail -n1) +RESPONSE_BODY=$(echo "$ACTION_RESPONSE" | sed '$d') + +if [ "$HTTP_CODE" == "200" ]; then + echo "✓ SendAction test PASSED" + echo " Response: $RESPONSE_BODY" +else + echo "✗ SendAction test FAILED (HTTP $HTTP_CODE)" + echo " Response: $RESPONSE_BODY" +fi + +echo "" +echo "Testing complete!" From 0d1acce38b5384c3203cc16a75d9cdd46d088aa6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 04:18:31 +0000 Subject: [PATCH 5/5] Add comprehensive implementation summary document Co-authored-by: KevinDMack <7958118+KevinDMack@users.noreply.github.com> --- IMPLEMENTATION_SUMMARY.md | 166 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 IMPLEMENTATION_SUMMARY.md diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..5397997 --- /dev/null +++ b/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,166 @@ +# Implementation Summary: Azure Function MCP Endpoints + +## Overview +This implementation adds Python-based Azure Functions with MCP (Model Context Protocol) endpoints to the pi-chat project, enabling the application to send telemetry requests and action commands to Azure Service Bus topics. + +## Changes Made + +### 1. Infrastructure Updates + +#### Service Bus (infra/modules/serviceBus.bicep) +- **Added two Service Bus topics**: + - `Telemetry` - for routing sensor telemetry data + - `Action` - for routing device action commands +- Both topics configured with: + - Max size: 1024 MB + - Message TTL: 14 days + - Batch operations: Enabled + +#### Function App (infra/modules/functionApp.bicep) +- **Changed runtime from Node.js to Python** +- Removed `WEBSITE_NODE_DEFAULT_VERSION` setting +- Updated `FUNCTIONS_WORKER_RUNTIME` to `python` +- Maintained Service Bus connection configuration + +#### Git Configuration (.gitignore) +- Updated to exclude Python-specific artifacts (`__pycache__`, `*.pyc`, etc.) +- Added Azure Functions specific exclusions (`bin/`, `obj/`, `local.settings.json`) +- Changed from excluding all `*.json` files to only excluding compiled Bicep files + +### 2. Azure Functions Implementation + +#### Project Structure +``` +functions/ +├── .funcignore # Files to exclude from deployment +├── GetTelemetry/ +│ ├── __init__.py # GetTelemetry function implementation +│ └── function.json # Function binding configuration +├── SendAction/ +│ ├── __init__.py # SendAction function implementation +│ └── function.json # Function binding configuration +├── host.json # Function app configuration +├── local.settings.json # Local development settings +├── requirements.txt # Python dependencies +├── test_endpoints.py # Python test script +├── test_endpoints.sh # Bash test script +└── README.md # Function documentation +``` + +#### GetTelemetry Endpoint +- **HTTP Method**: POST +- **Route**: `/api/GetTelemetry` +- **Request Body**: + ```json + { + "SensorKey": "string", + "StartDate": "ISO 8601 datetime", + "EndDate": "ISO 8601 datetime" + } + ``` +- **Functionality**: Validates request and sends message to Service Bus "Telemetry" topic +- **Response**: JSON status message + +#### SendAction Endpoint +- **HTTP Method**: POST +- **Route**: `/api/SendAction` +- **Request Body**: + ```json + { + "ActionType": "string", + "ActionSpec": "string (raw JSON)" + } + ``` +- **Functionality**: Validates request and sends message to Service Bus "Action" topic +- **Response**: JSON status message + +### 3. Testing Infrastructure + +#### Test Scripts +- **test_endpoints.sh**: Bash script for automated endpoint testing +- **test_endpoints.py**: Python script for automated endpoint testing +- Both scripts test both endpoints and provide clear pass/fail results + +### 4. Documentation Updates + +#### functions/README.md (NEW) +- Comprehensive documentation for the Azure Functions +- Setup and deployment instructions +- API documentation with request/response examples +- Local development guide +- Testing instructions + +#### infra/DEPLOYMENT.md +- Updated to reference Python runtime +- Added information about Service Bus topics +- Updated deployment instructions for Python functions +- Added links to function documentation + +#### infra/README.md +- Updated architecture description to reflect Python runtime +- Added Service Bus topics to resource details +- Updated Function App description with endpoint information +- Added "Function Endpoints" section with API overview + +## Key Features + +1. **Python-based Azure Functions**: Modern, maintainable Python code +2. **Service Bus Topic Integration**: Messages sent to dedicated topics for routing +3. **Comprehensive Error Handling**: Validation and error responses +4. **Complete Documentation**: Setup, deployment, and testing guides +5. **Automated Testing**: Scripts for quick validation +6. **Infrastructure as Code**: All resources defined in Bicep + +## Deployment Steps + +1. Deploy infrastructure using Bicep templates: + ```bash + az deployment group create \ + --resource-group rg-pichat-dev \ + --template-file infra/main.bicep \ + --parameters infra/main.bicepparam + ``` + +2. Deploy Python functions: + ```bash + cd functions + func azure functionapp publish pichat-dev-func --python + ``` + +3. Test endpoints: + ```bash + ./test_endpoints.sh https://pichat-dev-func.azurewebsites.net + ``` + +## Technical Details + +### Dependencies +- **azure-functions**: Azure Functions runtime +- **azure-servicebus**: Service Bus SDK (>=7.11.0) + +### Security +- Function endpoints use function-level authentication +- Service Bus connection string stored securely in app settings +- HTTPS enforced +- TLS 1.2 minimum + +### Architecture Flow +``` +HTTP Request → Azure Function → Service Bus Topic → [Downstream Consumers] +``` + +## Validation + +All components have been validated: +- ✓ Bicep files compile without errors +- ✓ Python syntax is valid +- ✓ JSON configuration files are valid +- ✓ Git configuration properly excludes build artifacts + +## Next Steps + +1. Deploy infrastructure to Azure +2. Deploy function code +3. Test endpoints using provided scripts +4. Configure downstream Service Bus topic subscribers +5. Integrate with Raspberry Pi sensors and actuators