Skip to content

Commit 804244b

Browse files
paulyukPaul YuknewiczCopilot
authored
Add daily GitHub repo digest Functions sample (#2)
* Add daily GitHub repo digest sample Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Update devcontainer to Python 3.13 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Use idiomatic uv workflow Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Ignore generated Bicep output Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Refine uv README guidance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix Azure Functions MCP docs link Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add Functions MCP loopback tutorial Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Split MCP tutorial notes from main README Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Rename MCP extension notes Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Simplify main README MCP guidance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Use Azure Functions host as default digest repo Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Bump template metadata version Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address Python digest review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Paul Yuknewicz <paulyuk@Pauls-MacBook-Pro-2.local> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent f6e1b90 commit 804244b

15 files changed

Lines changed: 961 additions & 18498 deletions

‎.devcontainer/Dockerfile‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
FROM mcr.microsoft.com/vscode/devcontainers/python:dev-3.12-bookworm
1+
FROM mcr.microsoft.com/vscode/devcontainers/python:dev-3.13-bookworm
2+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
23

34
# Copy custom first notice message.
45
COPY first-run-notice.txt /tmp/staging/

‎.env.example‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,14 @@
33

44
AZURE_AI_PROJECT_ENDPOINT="https://<your-ai-services>.services.ai.azure.com/api/projects/<your-project-name>"
55
AZURE_AI_MODEL_DEPLOYMENT_NAME="chat"
6+
7+
# Optional GitHub configuration for the daily digest sample
8+
GITHUB_REPOSITORY="Azure/azure-functions-host"
9+
# GITHUB_TOKEN can be set to increase public API rate limits. OAuth is not required.
10+
GITHUB_TOKEN=""
11+
12+
# Optional Copilot SDK MCP loopback. Local Functions does not need a key.
13+
# For Azure, set this to https://<app-name>.azurewebsites.net/runtime/webhooks/mcp
14+
# and set MCP_EXTENSION_KEY to the mcp_extension system key.
15+
COPILOT_MCP_SERVER_URL="http://localhost:7071/runtime/webhooks/mcp"
16+
MCP_EXTENSION_KEY=""

‎.gitignore‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ cython_debug/
145145
Thumbs.db
146146

147147
# Azure Functions
148-
local.settings.json
149148
__blobstorage__/
150149
__queuestorage__/
151150
__azurite_db*__.json
152151
.python_packages/
152+
153+
# Bicep build output
154+
infra/main.json

‎MCP-extension-notes.md‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# MCP extension notes
2+
3+
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.
4+
5+
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.
6+
7+
## Expose a Functions MCP tool
8+
9+
The repo digest data fetcher is exposed from [`function_app.py`](function_app.py) with the Python v2 Functions programming model:
10+
11+
```python
12+
@app.mcp_tool()
13+
@app.mcp_tool_property(
14+
arg_name="repository",
15+
description="Public GitHub repository in owner/name format.",
16+
is_required=False,
17+
)
18+
def get_repo_digest_context(repository: str = DEFAULT_REPOSITORY) -> str:
19+
return json.dumps(_repo_digest_context(repository), indent=2)
20+
```
21+
22+
The Functions MCP extension exposes the server at:
23+
24+
```text
25+
http://localhost:7071/runtime/webhooks/mcp
26+
```
27+
28+
For a deployed Function App, use:
29+
30+
```text
31+
https://<app-name>.azurewebsites.net/runtime/webhooks/mcp
32+
```
33+
34+
Remote endpoints require the `mcp_extension` system key unless `host.json` is configured for anonymous webhook authorization. Get the deployed key with:
35+
36+
```bash
37+
az functionapp keys list \
38+
--resource-group <resource-group> \
39+
--name <function-app-name> \
40+
--query systemKeys.mcp_extension \
41+
--output tsv
42+
```
43+
44+
## Consume the Functions MCP service from Copilot SDK
45+
46+
Pass the Functions MCP endpoint as a remote HTTP MCP server when creating a Copilot SDK session:
47+
48+
```python
49+
session = await client.create_session(
50+
on_permission_request=PermissionHandler.approve_all,
51+
mcp_servers={
52+
"repo-digest-functions": {
53+
"type": "http",
54+
"url": "https://<app-name>.azurewebsites.net/runtime/webhooks/mcp",
55+
"headers": {"x-functions-key": "<mcp_extension system key>"},
56+
"tools": ["get_repo_digest_context"],
57+
}
58+
},
59+
)
60+
```
61+
62+
For local loopback in this sample, set:
63+
64+
```bash
65+
export COPILOT_MCP_SERVER_URL="http://localhost:7071/runtime/webhooks/mcp"
66+
```
67+
68+
For a deployed Function App endpoint, also set:
69+
70+
```bash
71+
export MCP_EXTENSION_KEY="<mcp_extension system key>"
72+
```
73+
74+
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.
75+
76+
## References
77+
78+
- [Tutorial: Host an MCP server on Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-mcp-tutorial)
79+
- [Model context protocol bindings for Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-mcp)
80+
- [MCP tool trigger for Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-mcp-tool-trigger)
81+
- [Using MCP servers with the GitHub Copilot SDK](https://github.com/github/copilot-sdk/blob/main/docs/features/mcp.md)

‎README.md‎

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
# Simple Agent QuickStart (Python Copilot SDK)
1+
# Daily Repo Digest QuickStart (Python Copilot SDK on Azure Functions)
22

3-
A simple AI agent built with the GitHub Copilot SDK, running as an Azure Function.
3+
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`.
44

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

9-
- [Python 3.11+](https://docs.astral.sh/uv/getting-started/installation/) (via [uv](https://docs.astral.sh/uv/))
9+
- Python 3.13+ via [uv](https://docs.astral.sh/uv/getting-started/installation/)
1010
- [Azure Functions Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools)
11+
- [Azurite](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite) for local Azure Functions storage
1112
- [Azure Developer CLI (azd)](https://aka.ms/azd-install) (only needed for deploying Microsoft Foundry resources)
1213
- Access to an AI model via one of:
13-
- **GitHub Copilot subscription** — models are available automatically
14-
- **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))
14+
- **GitHub Copilot subscription** - models are available automatically
15+
- **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))
1516

1617
## Quickstart
1718

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

2425
```bash
25-
uv venv
26-
source .venv/bin/activate # macOS/Linux
27-
# .venv\Scripts\activate # Windows
28-
uv pip install -r requirements.txt
26+
uv sync
2927
```
3028

31-
3. Run the function locally:
29+
3. Start Azurite in a separate terminal:
3230

3331
```bash
34-
func start
32+
azurite --skipApiVersionCheck --silent --location ./.azurite
3533
```
3634

37-
4. Test the agent (in a new terminal):
35+
4. Run the function locally:
36+
37+
```bash
38+
uv run func start
39+
```
40+
41+
5. Test the digest agent in another terminal:
3842

3943
```bash
4044
# Interactive chat client
4145
uv run chat.py
4246

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

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

5964
## Source Code
6065

61-
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.
66+
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:
67+
68+
- An HTTP endpoint at `/api/ask` for chat or API requests.
69+
- A timer-triggered function named `daily_repo_digest` that runs the digest at 9 AM Pacific.
6270

6371
[`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.
6472

73+
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.
74+
75+
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`.
76+
77+
## Daily Schedule
78+
79+
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.
80+
6581
## Deploy Microsoft Foundry Resources
6682

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

94110
**Getting these values:**
95-
- If you ran `azd up`, the endpoint is already in your environment — run `azd env get-values | grep AZURE_OPENAI_ENDPOINT`
96-
- For the API key, go to [Azure Portal](https://portal.azure.com) → your AI Services resource → **Keys and Endpoint** → select the **Azure OpenAI** tab
111+
- If you ran `azd up`, the endpoint is already in your environment. Run `azd env get-values | grep AZURE_OPENAI_ENDPOINT`
112+
- 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
97113
- Or find both in the [Microsoft Foundry portal](https://ai.azure.com) under your project settings
98114

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

117+
## Next steps
118+
119+
- 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).
120+
- 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).
121+
101122
## Learn More
102123

103124
- [GitHub Copilot SDK](https://github.com/github/copilot-sdk)

‎azure.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
name: simple-agent-functions-python
44
metadata:
5-
template: simple-agent-functions-python@1.0.0
5+
template: simple-agent-functions-python@1.1.0
66
services:
77
api:
88
project: ./

‎chat.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
"""Console chat client for the Simple Agent function app."""
1+
"""Console chat client for the Repo Digest function app."""
22
import os
33
import urllib.request
44

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

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

1213
while True:
1314
message = input("You: ").strip()

0 commit comments

Comments
 (0)