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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
FROM mcr.microsoft.com/vscode/devcontainers/python:dev-3.12-bookworm
FROM mcr.microsoft.com/vscode/devcontainers/python:dev-3.13-bookworm
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/

# Copy custom first notice message.
COPY first-run-notice.txt /tmp/staging/
Expand Down
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,14 @@

AZURE_AI_PROJECT_ENDPOINT="https://<your-ai-services>.services.ai.azure.com/api/projects/<your-project-name>"
AZURE_AI_MODEL_DEPLOYMENT_NAME="chat"

# Optional GitHub configuration for the daily digest sample
GITHUB_REPOSITORY="Azure/azure-functions-host"
# GITHUB_TOKEN can be set to increase public API rate limits. OAuth is not required.
GITHUB_TOKEN=""

# Optional Copilot SDK MCP loopback. Local Functions does not need a key.
# For Azure, set this to https://<app-name>.azurewebsites.net/runtime/webhooks/mcp
# and set MCP_EXTENSION_KEY to the mcp_extension system key.
COPILOT_MCP_SERVER_URL="http://localhost:7071/runtime/webhooks/mcp"
MCP_EXTENSION_KEY=""
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,10 @@ cython_debug/
Thumbs.db

# Azure Functions
local.settings.json
__blobstorage__/
__queuestorage__/
__azurite_db*__.json
.python_packages/

# Bicep build output
infra/main.json
81 changes: 81 additions & 0 deletions MCP-extension-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# MCP extension notes

This sample exposes the live repo data fetcher as an Azure Functions MCP extension tool and can consume that same Functions-hosted MCP service from the Copilot SDK.

The full Azure Functions tutorial belongs in Microsoft Learn. Start with [Tutorial: Host an MCP server on Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-mcp-tutorial), then use these sample-specific notes to see how the pieces map into this repo.

## Expose a Functions MCP tool

The repo digest data fetcher is exposed from [`function_app.py`](function_app.py) with the Python v2 Functions programming model:

```python
@app.mcp_tool()
@app.mcp_tool_property(
arg_name="repository",
description="Public GitHub repository in owner/name format.",
is_required=False,
)
def get_repo_digest_context(repository: str = DEFAULT_REPOSITORY) -> str:
return json.dumps(_repo_digest_context(repository), indent=2)
```

The Functions MCP extension exposes the server at:

```text
http://localhost:7071/runtime/webhooks/mcp
```

For a deployed Function App, use:

```text
https://<app-name>.azurewebsites.net/runtime/webhooks/mcp
```

Remote endpoints require the `mcp_extension` system key unless `host.json` is configured for anonymous webhook authorization. Get the deployed key with:

```bash
az functionapp keys list \
--resource-group <resource-group> \
--name <function-app-name> \
--query systemKeys.mcp_extension \
--output tsv
```

## Consume the Functions MCP service from Copilot SDK

Pass the Functions MCP endpoint as a remote HTTP MCP server when creating a Copilot SDK session:

```python
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
mcp_servers={
"repo-digest-functions": {
"type": "http",
"url": "https://<app-name>.azurewebsites.net/runtime/webhooks/mcp",
"headers": {"x-functions-key": "<mcp_extension system key>"},
"tools": ["get_repo_digest_context"],
}
},
)
```

For local loopback in this sample, set:

```bash
export COPILOT_MCP_SERVER_URL="http://localhost:7071/runtime/webhooks/mcp"
```

For a deployed Function App endpoint, also set:

```bash
export MCP_EXTENSION_KEY="<mcp_extension system key>"
```

When `COPILOT_MCP_SERVER_URL` is set, `function_app.py` passes the MCP server into `CopilotClient.create_session` and asks the model to call `get_repo_digest_context` for live GitHub data. If `COPILOT_MCP_SERVER_URL` is not set, the sample falls back to fetching public GitHub REST data directly before sending the prompt to the model.

## References

- [Tutorial: Host an MCP server on Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-mcp-tutorial)
- [Model context protocol bindings for Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-mcp)
- [MCP tool trigger for Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-mcp-tool-trigger)
- [Using MCP servers with the GitHub Copilot SDK](https://github.com/github/copilot-sdk/blob/main/docs/features/mcp.md)
53 changes: 37 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# Simple Agent QuickStart (Python Copilot SDK)
# Daily Repo Digest QuickStart (Python Copilot SDK on Azure Functions)

A simple AI agent built with the GitHub Copilot SDK, running as an Azure Function.
A simple AI agent built with the GitHub Copilot SDK, running as an Azure Function. It creates live daily GitHub repository digests for recent pull requests, issues, and workflow failures. The default repository is `Azure/azure-functions-host`.

> Looking for [C#](https://github.com/Azure-Samples/simple-agent-functions-dotnet) or [TypeScript](https://github.com/Azure-Samples/simple-agent-functions-typescript)?

## Prerequisites

- [Python 3.11+](https://docs.astral.sh/uv/getting-started/installation/) (via [uv](https://docs.astral.sh/uv/))
- Python 3.13+ via [uv](https://docs.astral.sh/uv/getting-started/installation/)
- [Azure Functions Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools)
- [Azurite](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite) for local Azure Functions storage
- [Azure Developer CLI (azd)](https://aka.ms/azd-install) (only needed for deploying Microsoft Foundry resources)
- Access to an AI model via one of:
- **GitHub Copilot subscription** models are available automatically
- **Bring Your Own Key (BYOK)** use an API key from [Microsoft Foundry](https://ai.azure.com) (see [BYOK docs](https://github.com/github/copilot-sdk/blob/main/docs/auth/byok.md))
- **GitHub Copilot subscription** - models are available automatically
- **Bring Your Own Key (BYOK)** - use an API key from [Microsoft Foundry](https://ai.azure.com) (see [BYOK docs](https://github.com/github/copilot-sdk/blob/main/docs/auth/byok.md))

## Quickstart

Expand All @@ -22,26 +23,30 @@ A simple AI agent built with the GitHub Copilot SDK, running as an Azure Functio
2. Install dependencies:

```bash
uv venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows
uv pip install -r requirements.txt
uv sync
```

3. Run the function locally:
3. Start Azurite in a separate terminal:

```bash
func start
azurite --skipApiVersionCheck --silent --location ./.azurite
```

4. Test the agent (in a new terminal):
4. Run the function locally:

```bash
uv run func start
```

5. Test the digest agent in another terminal:

```bash
# Interactive chat client
uv run chat.py

# Or use curl directly
curl -X POST http://localhost:7071/api/ask -d "what are the laws"
curl -X POST http://localhost:7071/api/ask \
-d "Create a concise daily repo digest for Azure/azure-functions-host."
```

To chat with a deployed instance, grab the URL and function key from your `azd` environment:
Expand All @@ -58,10 +63,21 @@ A simple AI agent built with the GitHub Copilot SDK, running as an Azure Functio

## Source Code

The agent logic is in [`function_app.py`](function_app.py). It creates a `CopilotClient`, configures a session with a system message (Asimov's Three Laws of Robotics), and exposes an HTTP endpoint (`/api/ask`) that accepts a prompt and returns the agent's response.
The agent logic is in [`function_app.py`](function_app.py). It creates a `CopilotClient`, fetches live public GitHub data through REST APIs, and asks the model to produce a concise daily digest. The sample keeps the Azure Functions hosting model from this repo and exposes:

- An HTTP endpoint at `/api/ask` for chat or API requests.
- A timer-triggered function named `daily_repo_digest` that runs the digest at 9 AM Pacific.

[`chat.py`](chat.py) is a lightweight console client that POSTs messages to the function in a loop, giving you an interactive chat experience. It defaults to `http://localhost:7071` but can be pointed at a deployed instance via the `AGENT_URL` environment variable.

Ask for a digest with an optional public repo such as `Azure/azure-functions-host`. If you omit the repo, the agent uses `Azure/azure-functions-host` by default. Set `GITHUB_REPOSITORY` to change the default repository. Set `GITHUB_TOKEN` only if you want higher public GitHub API rate limits.

Local development uses [`pyproject.toml`](pyproject.toml) with `uv sync` and `uv run`. [`requirements.txt`](requirements.txt) is kept for Azure Functions packaging and should stay aligned with the dependencies in `pyproject.toml`.

## Daily Schedule

The timer trigger uses the Azure Functions NCRONTAB schedule `0 0 16,17 * * *`. Azure Functions timer schedules run in UTC for this Linux Functions sample, so the function wakes at both possible 9 AM Pacific UTC offsets and only creates a digest when the current `America/Los_Angeles` hour is 9. This keeps the sample aligned with Pacific daylight and standard time without adding a separate scheduler service.

## Deploy Microsoft Foundry Resources

If you prefer to use your own models via BYOK and don't already have a Microsoft Foundry project with a model deployed:
Expand Down Expand Up @@ -92,12 +108,17 @@ export AZURE_OPENAI_MODEL="gpt-5-mini" # optional, defaults to gpt-5-mini
```

**Getting these values:**
- If you ran `azd up`, the endpoint is already in your environment — run `azd env get-values | grep AZURE_OPENAI_ENDPOINT`
- For the API key, go to [Azure Portal](https://portal.azure.com)your AI Services resource**Keys and Endpoint** select the **Azure OpenAI** tab
- If you ran `azd up`, the endpoint is already in your environment. Run `azd env get-values | grep AZURE_OPENAI_ENDPOINT`
- For the API key, go to [Azure Portal](https://portal.azure.com), your AI Services resource, **Keys and Endpoint**, then select the **Azure OpenAI** tab
- Or find both in the [Microsoft Foundry portal](https://ai.azure.com) under your project settings

See the [BYOK docs](https://github.com/github/copilot-sdk/blob/main/docs/auth/byok.md) for details.

## Next steps

- Add tools and data sources with Azure Functions custom bindings, Python helpers, or MCP integration: [MCP extension notes for this sample](MCP-extension-notes.md), [Tutorial: Host an MCP server on Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-mcp-tutorial), and [connect MCP server endpoints to Foundry agents](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/model-context-protocol).
- Build durable, long-running Functions workflows: [Durable Functions overview](https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview) and [Azure Functions timer triggers](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer).

## Learn More

- [GitHub Copilot SDK](https://github.com/github/copilot-sdk)
Expand Down
2 changes: 1 addition & 1 deletion azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

name: simple-agent-functions-python
metadata:
template: simple-agent-functions-python@1.0.0
template: simple-agent-functions-python@1.1.0
services:
api:
project: ./
Expand Down
7 changes: 4 additions & 3 deletions chat.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""Console chat client for the Simple Agent function app."""
"""Console chat client for the Repo Digest function app."""
import os
import urllib.request

BASE_URL = os.environ.get("AGENT_URL", "http://localhost:7071").rstrip("/")
FUNCTION_KEY = os.environ.get("FUNCTION_KEY", "")

print(f"=== Simple Agent Chat ===")
print(f"=== Repo Digest Agent Chat ===")
print(f"Endpoint: {BASE_URL}/api/ask")
print(f"Type 'exit' or 'quit' to end.\n")
print("Ask for a daily digest, or include a public repo like Azure/azure-functions-host.")
print("Type 'exit' or 'quit' to end.\n")

while True:
message = input("You: ").strip()
Expand Down
Loading