Adding da-rapairs-oauth-python sample#144
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new da-repairs-oauth-python sample that demonstrates a Microsoft 365 Copilot declarative agent calling an OAuth-secured Python Azure Functions API via an OpenAPI-backed plugin.
Changes:
- Introduces a Python Azure Functions API endpoint (
/api/repairs) backed by a JSON dataset. - Adds Teams/M365 Agents Toolkit provisioning/deployment workflows (including Entra app + OAuth registration) and Azure infrastructure (Bicep).
- Adds the agent manifest, plugin manifest (
ai-plugin.json), and OpenAPI specification for the repairs API.
Reviewed changes
Copilot reviewed 21 out of 24 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/da-repairs-oauth-python/requirements.txt | Python dependencies for the Azure Function (azure-functions, PyJWT). |
| samples/da-repairs-oauth-python/repairsData.json | Sample repairs dataset served by the API. |
| samples/da-repairs-oauth-python/README.md | Sample documentation and usage guidance. |
| samples/da-repairs-oauth-python/m365agents.yml | Provision/deploy/publish workflow for Azure + OAuth registration. |
| samples/da-repairs-oauth-python/m365agents.local.yml | Local provisioning/deploy workflow for running the function locally. |
| samples/da-repairs-oauth-python/infra/azure.parameters.json | ARM/Bicep parameterization for provisioned Azure resources. |
| samples/da-repairs-oauth-python/infra/azure.bicep | Azure infrastructure for Function App + Easy Auth configuration. |
| samples/da-repairs-oauth-python/host.json | Azure Functions host configuration. |
| samples/da-repairs-oauth-python/function_app.py | Python Azure Function implementation for the repairs endpoint + scope check. |
| samples/da-repairs-oauth-python/env/.env.local | Local environment variable template for Teams/M365 Agents Toolkit. |
| samples/da-repairs-oauth-python/env/.env.dev | Dev environment variable template for Teams/M365 Agents Toolkit. |
| samples/da-repairs-oauth-python/assets/sample.json | Sample catalog metadata entry. |
| samples/da-repairs-oauth-python/appPackage/repairDeclarativeAgent.json | Declarative agent definition that references the plugin action. |
| samples/da-repairs-oauth-python/appPackage/manifest.json | Teams/M365 app manifest wiring in the declarative agent. |
| samples/da-repairs-oauth-python/appPackage/instruction.txt | Agent instruction prompt text. |
| samples/da-repairs-oauth-python/appPackage/declarativeAgent.json | Additional declarative agent JSON artifact included in the package. |
| samples/da-repairs-oauth-python/appPackage/apiSpecificationFile/repair.yml | OpenAPI definition for the repairs API + OAuth scheme/scopes. |
| samples/da-repairs-oauth-python/appPackage/ai-plugin.json | Plugin manifest connecting Copilot to the OpenAPI runtime via OAuth vault reference. |
| samples/da-repairs-oauth-python/aad.manifest.json | Entra application manifest defining exposed scope and reply URL. |
| samples/da-repairs-oauth-python/.gitignore | Ignore rules for local/dev artifacts in this sample. |
| samples/da-repairs-oauth-python/.funcignore | Exclusions for Azure Functions zip deploy packaging. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def has_required_scopes(req: func.HttpRequest, required_scopes: str | list[str]) -> bool: | ||
| """Validate that the request has the required OAuth scopes.""" | ||
| if isinstance(required_scopes, str): | ||
| required_scopes = [required_scopes] | ||
|
|
||
| auth_header = req.headers.get("Authorization") | ||
| if not auth_header: | ||
| return False | ||
|
|
||
| parts = auth_header.split(" ") | ||
| if len(parts) != 2 or parts[0] != "Bearer": | ||
| return False | ||
|
|
||
| try: | ||
| decoded_token = jwt.decode(parts[1], options={"verify_signature": False}) | ||
| token_scopes = decoded_token.get("scp", "").split(" ") | ||
| return all(scope in token_scopes for scope in required_scopes) | ||
| except Exception: | ||
| return False |
| def has_required_scopes(req: func.HttpRequest, required_scopes: str | list[str]) -> bool: | ||
| """Validate that the request has the required OAuth scopes.""" | ||
| if isinstance(required_scopes, str): | ||
| required_scopes = [required_scopes] | ||
|
|
||
| auth_header = req.headers.get("Authorization") | ||
| if not auth_header: | ||
| return False | ||
|
|
||
| parts = auth_header.split(" ") | ||
| if len(parts) != 2 or parts[0] != "Bearer": | ||
| return False | ||
|
|
||
| try: | ||
| decoded_token = jwt.decode(parts[1], options={"verify_signature": False}) | ||
| token_scopes = decoded_token.get("scp", "").split(" ") | ||
| return all(scope in token_scopes for scope in required_scopes) | ||
| except Exception: | ||
| return False | ||
|
|
||
|
|
||
| @app.route(route="repairs", methods=["GET"]) | ||
| def repairs(req: func.HttpRequest) -> func.HttpResponse: | ||
| """HTTP trigger function that returns repair information.""" | ||
| logging.info("HTTP trigger function processed a request.") | ||
|
|
||
| if not has_required_scopes(req, "repairs_read"): |
| def has_required_scopes(req: func.HttpRequest, required_scopes: str | list[str]) -> bool: | ||
| """Validate that the request has the required OAuth scopes.""" | ||
| if isinstance(required_scopes, str): | ||
| required_scopes = [required_scopes] | ||
|
|
|
|
||
| * Microsoft 365 tenant with Microsoft 365 Copilot | ||
| * [Visual Studio Code](https://code.visualstudio.com/) with the [Teams Toolkit](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension) extension | ||
| * [Python](https://www.python.org/) 3.9 or later |
| { | ||
| "$schema": "https://developer.microsoft.com/json-schemas/copilot/declarative-agent/v1.6/schema.json", | ||
| "version": "v1.6", | ||
| "name": "da-repairs-auth-python${{APP_NAME_SUFFIX}}", |
There was a problem hiding this comment.
No updated needed the manifest reference the declarativeAgent.json file
| format: date-time | ||
| description: The date and time when the repair is scheduled or completed |
| { | ||
| "id": "4", | ||
| "title": "Battery replacement", | ||
| "description": "Remove the old battery and install a new one to ensure that the vehicle start reliably and the electrical systems function properly.", |
| - uses: script | ||
| name: install dependencies | ||
| with: | ||
| run: pip install -r requirements.txt No newline at end of file |
garrytrinder
left a comment
There was a problem hiding this comment.
Thanks for the contribution @AjayJ12-MSFT can you address the comments then we can merge
| appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip | ||
| writeToEnvironmentFile: | ||
| publishedAppId: TEAMS_APP_PUBLISHED_APP_ID | ||
| projectId: 59c52cd9-3bdd-4bc7-afd2-a02343a1c390 |
There was a problem hiding this comment.
Let's remove the projectId as it is created on first provision.
No description provided.