Generated banner for Declarative Agent Developer
Generated logo for Declarative Agent Developer

Declarative Agent Developer

MicrosoftOfficialPublished by Microsoft

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Low risk

From the author

Create, build, deploy, and localize declarative agents for M365 Copilot and Teams. USE THIS SKILL for ANY task involving a declarative agent — including localization, scaffolding, editing manifests, adding capabilities, and deploying. Localization requires tokenized manifests and language files that only this skill knows how to produce. Triggers: "create agent", "create a declarative agent", "new declarative agent", "scaffold an agent", "new agent project", "add a capability", "add a plugin", "configure my agent", "deploy my agent", "fix my agent manifest", "edit my agent", "localize my agent", "add localization", "translate my agent", "multi-language agent", "add an API plugin", "add an MCP plugin", "add OAuth to my plugin", "review instructions", "improve instructions", "fix my instructions"
— the description by Microsoft

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The declarative-agent-developer skill provides a structured workflow for managing the lifecycle of M365 Copilot and Teams declarative agents. It utilizes the M365 Agent Toolkit (ATK) CLI to handle project scaffolding, manifest management, API plugin integration, and deployment. The skill enforces strict workspace validation, requiring specific project structures before allowing modifications, and provides standardized protocols for error handling, localization, and authentication configuration.

What it does

  • Scaffolds new declarative agent projects using the ATK CLI.
  • Manages and validates declarativeAgent.json manifests.
  • Integrates API plugins and MCP servers into existing agent projects.
  • Configures OAuth authentication for secure plugin communication.
  • Handles localization of agent manifests and language files.
  • Deploys agents to local environments for testing and validation.

When to use it

  • When creating a new declarative agent project for M365 Copilot or Teams.
  • When adding new capabilities, API plugins, or MCP servers to an existing agent.
  • When updating agent instructions or conversation starters.
  • When localizing an agent for multiple languages.
  • When troubleshooting or fixing errors in an agent manifest.

Advantages

  • Ensures consistent project structure through mandatory workspace checks.
  • Automates complex tasks like API plugin registration and OAuth setup.
  • Provides a clear, step-by-step protocol for error detection and resolution.
  • Facilitates standardized deployment and testing workflows.
  • Maintains schema compatibility by checking version requirements before feature implementation.

Usage examples

“Create a new declarative agent project.”
The agent will initialize a new project structure, including the necessary manifest files, provided the workspace is empty and valid for agent development.
“Add an API plugin to my agent using this OpenAPI spec URL.”
The agent will use the ATK CLI to integrate the specified API, map the requested operations, and prompt for necessary metadata updates.
“Localize my agent into French.”
The agent will generate the required tokenized manifests and language files to support French localization.
“Deploy my agent to the local environment.”
The agent will run the provisioning command and provide the M365 test link for the deployed agent.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/microsoft/declarative-agent-developer/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip declarative-agent-developer.zip -d .agents/skills

Documentation by Microsoft

M365 Agent Developer

Telemetry Tagging

Before running any atk CLI commands, set the session environment variable so all CLI invocations are tagged as skill-initiated:

export ATK_CLI_SKILL=true

Run this once at the start of the session. All subsequent atk commands in the same terminal will inherit it.

⛔ Workspace Check — MANDATORY FIRST STEP

Before doing ANYTHING, check the workspace files to fingerprint the project:

  1. Run npx -y --package @microsoft/m365agentstoolkit-cli atk --version to confirm ATK CLI is installed. If not found → Stop. Tell the user to install ATK.

  2. Check for m365agents.yml or teamsApp.yml at the project root.

  3. Check for appPackage/declarativeAgent.json.

  4. Check for non-agent indicators (package.json with express/react/next, src/index.js, app.py, etc.)

Then follow the decision gate:

ConditionGateAction
Non-agent project files, no appPackage/RejectText-only response. No files, no commands.
No manifest, user wants to edit/deployRejectText-only response. Explain manifest is missing.
No manifest, user wants new projectScaffold→ Scaffolding Workflow
Manifest exists with errorsFixDetect → Inform → Ask (see below). Do NOT deploy.
Valid project, user reports behavior issuesReview→ Instruction Review — run the full 5-phase review workflow
Valid agent projectEdit→ Editing Workflow

Detailed gate rules, examples, and anti-patterns: Workspace Gates

🚫 HARD REJECTION RULES — No Exceptions

These rules override ALL other instructions. If any of these apply, you MUST stop immediately.

  1. NEVER create declarativeAgent.json yourself. If the manifest is missing and the user asked to edit/modify/deploy, respond with text only: explain the manifest is missing, suggest npx -y --package @microsoft/m365agentstoolkit-cli atk new or starting from scratch. Do NOT create the file, do NOT create appPackage/, do NOT "help" by scaffolding implicitly.

  2. NEVER create files in a non-agent project. If the workspace is an Express/React/Django/etc. app without appPackage/, your response must be text-only. Do NOT create any files, do NOT run any commands.

  3. NEVER deploy when errors exist. If the agent manifest has errors, STOP. Do NOT run npx -y --package @microsoft/m365agentstoolkit-cli atk provision — not "to test", not "to demonstrate the error", not "to see what happens". Report the errors and ask the user how to proceed.

🔍 Detect → Inform → Ask (Error-Handling Protocol)

When you encounter ANY problem (missing files, malformed JSON, validation errors, incompatible features), you MUST follow this sequence in order:

  1. Detect — Identify the specific problem. For JSON issues, attempt to parse the file and report syntax errors. For missing fields, check the manifest against the Schema.

  2. Inform — Tell the user BEFORE taking any action. Describe exactly what is wrong ("declarativeAgent.json has malformed JSON: missing comma on line 12, unclosed array on line 18").

  3. Ask — Wait for the user's response before making changes. Do NOT silently fix, auto-correct, or work around the problem.

This protocol applies to:

  • Missing declarativeAgent.json → Detect (file not found) → Inform ("no manifest found") → Ask ("would you like to create a new agent?")

  • Malformed JSON → Detect (parse errors) → Inform (list specific syntax issues) → Ask ("should I fix these syntax errors?")

  • Validation errors → Detect (parse and check manifest) → Inform (list all errors) → Ask ("how would you like to fix these?")

  • Version incompatibility → Detect (feature requires newer version) → Inform ("this feature requires v1.6, your agent is v1.4") → Ask ("should I upgrade?")


Phase Routing

ScenarioWorkflow Reference
Creating a NEW project from scratchScaffolding Workflow
Working with existing .json manifestsEditing Workflow
Adding an API pluginAPI Plugins
Adding an MCP serverMCP Plugin
Adding OAuth to an MCP or API pluginAuthentication
Reviewing or improving existing agent instructionsInstruction Review
User reports agent gives generic/wrong answersInstruction Review
Localizing an agent into multiple languagesLocalization
Adding a new language to an already-localized agentLocalization
Writing agent instructionsConversation Design

ATK CLI Setup

Before running any ATK commands, check if the ATK CLI is available by running npx -y --package @microsoft/m365agentstoolkit-cli atk --version. If not found, STOP and tell the user — do NOT attempt to install it yourself.

All commands use the npx -y --package @microsoft/m365agentstoolkit-cli atk prefix (e.g., npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local).


Critical Rules

1. Deploy After EVERY Edit

After ANY change to files in appPackage/, you MUST deploy and show the test link before responding:

npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local --interactive false

Then read M365_TITLE_ID from env/.env.local and ALWAYS present the review UX:

✅ Agent deployed successfully!

🚀 Test Your Agent in M365 Copilot:
🔗 https://m365.cloud.microsoft/chat/?titleId={M365_TITLE_ID}

⛔ Never respond without this link. If you deployed, the test link MUST appear in your response. This is not optional — it is how the user tests their agent.

  • If the manifest has errors → STOP. Fix errors. Do NOT deploy.

  • Exception: user explicitly asks you not to deploy

2. Never Invent Content or Create Missing Files
  • Do NOT invent placeholder names, descriptions, or instructions

  • Do NOT create declarativeAgent.json or appPackage/ if they don't exist — this is a REJECT scenario, not a "help by creating" scenario

  • If required fields are missing, report the gaps, and ASK the user

  • If JSON is malformed, follow Detect → Inform → Ask: parse the file first, tell the user what's broken, then ask before fixing. Use surgical edits (not rewrites)

  • ⛔ NEVER set placeholder values for environment variables that are populated by automation (e.g., <PREFIX>_MCP_AUTH_ID, TEAMS_APP_ID). Leave them empty (VAR_NAME=). Placeholders will be treated as real values and will NOT be overwritten by provisioning.

3. Schema Version Compatibility

Before adding ANY feature, read the version field in declarativeAgent.json and check the Schema feature matrix. If the feature isn't supported in that version, refuse and offer to upgrade.

Key version gates:

  • sensitivity_label, worker_agents, EmbeddedKnowledge → v1.6 only

  • Meetings → v1.5+

  • ScenarioModels, behavior_overrides, disclaimer → v1.4+

  • Dataverse, TeamsMessages, Email, People → v1.3+

4. Use npx -y --package @microsoft/m365agentstoolkit-cli atk add action for API Plugins — NEVER Create Plugin Files Manually

You are forbidden from manually creating ai-plugin.json, OpenAPI specs, adaptive cards, or editing the actions array. Use the CLI:

# ⛔ Always list ALL operations in a single call — NEVER run separate calls per operation
npx -y --package @microsoft/m365agentstoolkit-cli atk add action --api-plugin-type api-spec --openapi-spec-location URL --api-operation "GET /path,POST /path,PATCH /path/{id},DELETE /path/{id}" -i false

Run a single npx -y --package @microsoft/m365agentstoolkit-cli atk add action call per OpenAPI spec, listing all operations as a comma-separated list in --api-operation. Never run separate npx -y --package @microsoft/m365agentstoolkit-cli atk add action calls for different operations from the same spec — this creates multiple plugins instead of one. If npx -y --package @microsoft/m365agentstoolkit-cli atk add action fails, report the error; do NOT fall back to manual creation.

Exception: MCP servers are not supported by npx -y --package @microsoft/m365agentstoolkit-cli atk add action. Use the MCP Plugin workflow instead.

5. MCP Server Integration

When the user mentions an MCP server URL, follow the MCP Plugin workflow. You MUST discover tools via the MCP protocol handshake (initialize → notifications/initialized → tools/list) — NEVER fabricate tool names/descriptions. For authenticated MCP servers, follow the authentication guide to configure OAuth.

6. Always Update Instructions & Starters After Changes

Adding a capability or plugin without updating instructions is incomplete. After ANY change:

  1. Update instructions to describe the new/changed functionality — every data source should have clear intent coverage (WHEN and WHY to use it) per the Instruction Review quality bar. Built-in capabilities don't need exact names; actions/plugins should be named.

  2. Do NOT list tool names, descriptions, or parameters in instructions — these are already in the plugin metadata (ai-plugin.json, MCP manifests, capability config). Instructions should contain decision logic only: WHEN to use each tool, chaining rules, and failure handling.

  3. Stay within the 8,000-character instruction limit — if close to the limit, cut tool descriptions first

  4. Add at least 1 conversation starter per added capability/plugin

  5. Remove starters that reference removed capabilities

  6. Run the Diagnostic Checklist against the updated instructions to verify quality

7. App Name Requirement

Always update the app name and description to something meaningful. Never leave defaults like "My Agent".


References

Shared
  • Authentication — OAuth discovery, credentials, oauth/register lifecycle, OAuthPluginVault

  • Best Practices — Security, performance, testing, compliance

  • Conversation Design — Authoring instructions and conversation starters from scratch

  • Instruction Review — Auditing, diagnosing, and improving existing instructions; anti-pattern detection; before/after rewrites

  • Deployment — ATK CLI workflows, environments, CI/CD

  • Localization — Multi-language support, tokenized manifests, language files

  • Workspace Gates — Detailed gate rules, examples, anti-patterns

Scaffolding
JSON Development
  • Editing Workflow — Step-by-step JSON development instructions

  • Schema — Official JSON schema for agent manifests

  • API Plugins — OpenAPI integration for JSON agents

  • MCP Plugin — MCP server integration with RemoteMCPServer, OAuth, response semantics, logo handling

  • Examples — JSON manifest examples

Files

references/api-plugins.md

API Plugin Architecture for M365 JSON Agents

Note: This guide is for JSON-based agents. API plugins are defined using JSON manifest files that reference OpenAPI specifications.

Overview

API plugins (also called "actions") allow your M365 Copilot agent to interact with external REST APIs. They consist of:

  1. OpenAPI Specification - Describes the REST API endpoints, parameters, and responses

  2. API Plugin Manifest (JSON) - Configures how M365 Copilot interacts with the API

  3. Declarative Agent Reference - Links the plugin to your agent via the actions array


⛔ Post-npx -y --package @microsoft/m365agentstoolkit-cli atk add action Checklist — MANDATORY

After running npx -y --package @microsoft/m365agentstoolkit-cli atk add action, you MUST complete ALL of these before validating and deploying:

  1. Update name_for_human in ai-plugin.json — descriptive, user-facing name (max 20 chars)

  2. Update description_for_model in ai-plugin.json — detailed guidance for the AI on when and how to use each function

  3. Customize adaptive cards in appPackage/adaptiveCards/ for each operation — different layouts per HTTP verb (list view for GET collections, detail view for GET by ID, confirmation for DELETE, etc.)

  4. Add confirmation dialogs for all destructive operations (POST, PUT, PATCH, DELETE)

  5. Deploy with npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local --interactive false

Skipping ANY of these steps = incomplete work. The npx -y --package @microsoft/m365agentstoolkit-cli atk add action command generates scaffolding — you must finish the job by customizing every generated file.


Adding API Plugins with ATK CLI

⚠️ IMPORTANT: When adding an API, OpenAPI spec, or REST API to your agent, you MUST use the npx -y --package @microsoft/m365agentstoolkit-cli atk add action command. This is the required method for adding API plugins to M365 Copilot agents. Do NOT manually create plugin files - the path resolution between local packaging and M365 service validation is complex and error-prone.

⛔ ONE PLUGIN PER API — HARD RULE: Always add ALL operations from the same OpenAPI spec in a single npx -y --package @microsoft/m365agentstoolkit-cli atk add action call. List every operation in the --api-operation parameter as a comma-separated list. NEVER run separate npx -y --package @microsoft/m365agentstoolkit-cli atk add action calls for different operations from the same spec — this creates multiple plugins instead of one unified plugin. One OpenAPI spec = one npx -y --package @microsoft/m365agentstoolkit-cli atk add action call = one plugin.

The npx -y --package @microsoft/m365agentstoolkit-cli atk add action Command

ALWAYS use this command when asked to add an API, OpenAPI specification, or REST API to an M365 Copilot agent:

npx -y --package @microsoft/m365agentstoolkit-cli atk add action \
  --api-plugin-type api-spec \
  --openapi-spec-type enter-url-or-open-local-file \
  --openapi-spec-location URL_OR_FILE_PATH \
  --api-operation "OPERATIONS_TO_MAP" \
  -i false
Command Parameters
ParameterDescription
--api-plugin-typeType of plugin. Use api-spec for OpenAPI-based plugins.
--openapi-spec-typeHow to provide the spec. Use enter-url-or-open-local-file.
--openapi-spec-locationURL or local file path to the OpenAPI specification.
--api-operationComma-separated list of operations to include (format: METHOD /path).
-i falseNon-interactive mode.
⚠️ CRITICAL: Use Absolute Paths for Local Files

When using a local OpenAPI specification file, you MUST use an absolute path:

# ✅ CORRECT - Absolute path
--openapi-spec-location /home/user/project/openapi.json

# ❌ WRONG - Relative path (will fail!)
--openapi-spec-location ./openapi.json
--openapi-spec-location openapi.json

Why? The ATK CLI executes from a temporary directory, so relative paths cannot be resolved. Always use the full absolute path to your OpenAPI specification file.

Operation Format

The --api-operation parameter uses the format: METHOD /path,METHOD /path,...

Example with Repairs API (URL):

npx -y --package @microsoft/m365agentstoolkit-cli atk add action \
  --api-plugin-type api-spec \
  --openapi-spec-type enter-url-or-open-local-file \
  --openapi-spec-location "https://repairshub.azurewebsites.net/openapi.json" \
  --api-operation "GET /repairs,GET /repairs/{id},POST /repairs,PATCH /repairs/{id},DELETE /repairs/{id}" \
  -i false

Example with local file (absolute path):

npx -y --package @microsoft/m365agentstoolkit-cli atk add action \
  --api-plugin-type api-spec \
  --openapi-spec-type enter-url-or-open-local-file \
  --openapi-spec-location /home/user/myproject/nhl-openapi.json \
  --api-operation "GET /v1/standings/now,GET /v1/score/now,GET /v1/schedule/now" \
  -i false
What the Command Creates

After running npx -y --package @microsoft/m365agentstoolkit-cli atk add action, the following files are created/updated:

appPackage/
├── declarativeAgent.json     # Updated with action reference
├── ai-plugin.json            # API plugin manifest (generated)
├── adaptiveCards/            # Response templates (auto-generated)
│   ├── getRepairs.json
│   ├── getRepairById.json
│   └── ...
└── apiSpecificationFile/
    ├── openapi.yaml          # OpenAPI spec (converted to 3.0 if needed)
    └── openapi.yaml.original # Original spec (if converted)

Note: OpenAPI 3.1 specifications are automatically converted to OpenAPI 3.0 format for compatibility.

Post-Generation: Enhance the Plugin

After running npx -y --package @microsoft/m365agentstoolkit-cli atk add action, you MUST enhance the generated files:

  1. Update name_for_human - Make it descriptive and user-friendly

  2. Update description_for_model - Add detailed guidance on when and how to use each function

  3. 🎨 Update adaptive cards for each action - Customize the auto-generated cards in appPackage/adaptiveCards/ to present meaningful, well-structured data for each operation

Example plugin enhancement:

{
  "name_for_human": "NHL Data API",
  "description_for_human": "Access real-time NHL standings, scores, and player statistics",
  "description_for_model": "Use this plugin to access real-time NHL data. Call getCurrentStandings for league standings. Call getTodaysScores for today's game scores or getScoresByDate for historical scores (YYYY-MM-DD format). Call getCurrentTeamStats or getCurrentRoster with a 3-letter team code (TOR, BOS, NYR, MTL, EDM, VGK, etc.). Always provide context about the data format and available parameters."
}

The description_for_model is critical - it tells the AI when to use each function and what parameters to provide.

🎨 Post-Generation: Enhance Adaptive Cards for Each Action

The npx -y --package @microsoft/m365agentstoolkit-cli atk add action command auto-generates basic adaptive cards in appPackage/adaptiveCards/ — one per operation. These default cards are generic and only display raw data. You MUST customize each card to provide a valuable UX tailored to the data each action returns.

⚠️ CRITICAL: Check ALL Operations Have Adaptive Cards

After running npx -y --package @microsoft/m365agentstoolkit-cli atk add action, verify that an adaptive card exists for EVERY operation. The command may not generate cards for POST, PATCH, or DELETE operations. If any operation is missing an adaptive card, CREATE one manually in appPackage/adaptiveCards/.

  • GET operations → List or detail layout showing returned data

  • POST operations → Confirmation/summary layout showing what was created

  • PATCH operations → Summary layout showing what was updated

  • DELETE operations → Confirmation layout confirming what was deleted

Why Customize Adaptive Cards?
  • Default cards are generic — they list all response fields as plain text with no hierarchy or formatting

  • Users expect rich, scannable output — titles, subtitles, key metrics, and visual hierarchy help users quickly understand results

  • Different actions need different layouts — a list of items needs a different card than a single detail view or a confirmation response

How to Enhance Each Card

For each adaptive card file generated in appPackage/adaptiveCards/:

  1. Examine the API response schema — understand what fields each operation returns

  2. Identify the most important fields — determine what users care about most (title, status, date, assignee, etc.)

  3. Design a clear visual hierarchy — use weight: "Bolder", size: "Medium"/"Large", spacing, and separator to create structure

  4. Use appropriate layouts — ColumnSet for side-by-side data, FactSet for key-value pairs, Container for grouping

  5. Add conditional styling — use "color" properties and "style" to highlight important states (e.g., urgent, overdue)

  6. Include action buttons — add Action.OpenUrl for links to external resources when the data includes URLs

  7. Handle arrays with $foreach — use ${$root} for the items array and ${$data} for individual items within the template

Example: Before vs After Enhancement

❌ Auto-generated card (generic — poor UX):

{
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.5",
  "body": [
    {
      "type": "TextBlock",
      "text": "id: ${if(id, id, 'N/A')}",
      "wrap": true
    },
    {
      "type": "TextBlock",
      "text": "title: ${if(title, title, 'N/A')}",
      "wrap": true
    },
    {
      "type": "TextBlock",
      "text": "description: ${if(description, description, 'N/A')}",
      "wrap": true
    },
    {
      "type": "TextBlock",
      "text": "assignedTo: ${if(assignedTo, assignedTo, 'N/A')}",
      "wrap": true
    },
    {
      "type": "TextBlock",
      "text": "date: ${if(date, date, 'N/A')}",
      "wrap": true
    },
    {
      "type": "TextBlock",
      "text": "image: ${if(image, image, 'N/A')}",
      "wrap": true
    }
  ]
}

✅ Enhanced card (rich UX — tailored to data):

{
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.5",
  "body": [
    {
      "type": "ColumnSet",
      "columns": [
        {
          "type": "Column",
          "width": "auto",
          "items": [
            {
              "type": "Image",
              "url": "${image}",
              "size": "Medium",
              "altText": "${title}"
            }
          ]
        },
        {
          "type": "Column",
          "width": "stretch",
          "items": [
            {
              "type": "TextBlock",
              "text": "${title}",
              "weight": "Bolder",
              "size": "Medium",
              "wrap": true
            },
            {
              "type": "TextBlock",
              "text": "Assigned to: ${assignedTo}",
              "spacing": "None",
              "isSubtle": true,
              "wrap": true
            }
          ]
        }
      ]
    },
    {
      "type": "TextBlock",
      "text": "${description}",
      "wrap": true,
      "spacing": "Medium"
    },
    {
      "type": "FactSet",
      "separator": true,
      "facts": [
        {
          "title": "Repair ID",
          "value": "#${id}"
        },
        {
          "title": "Date",
          "value": "${date}"
        }
      ]
    }
  ]
}
Example: List Action Card (Array Response with $foreach)

When an action returns an array of items, use the $foreach data binding pattern to render each item:

{
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "type": "AdaptiveCard",
  "version": "1.5",
  "body": [
    {
      "type": "Container",
      "$data": "${$root}",
      "separator": true,
      "items": [
        {
          "type": "TextBlock",
          "text": "${title}",
          "weight": "Bolder",
          "wrap": true
        },
        {
          "type": "ColumnSet",
          "columns": [
            {
              "type": "Column",
              "width": "stretch",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "Assigned to: ${assignedTo}",
                  "isSubtle": true,
                  "spacing": "None",
                  "wrap": true
                }
              ]
            },
            {
              "type": "Column",
              "width": "auto",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "${date}",
                  "isSubtle": true,
                  "spacing": "None",
                  "horizontalAlignment": "Right"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
Card Design Guidelines Per Action Type
Action TypeRecommended LayoutKey Elements
List / Search (GET returning array)Repeating Container with $data: "${$root}"Title, subtitle, key status field per item, separator between items
Get by ID (GET returning single object)ColumnSet + FactSetImage/icon, title, description, key-value pairs for metadata
Create (POST)Confirmation summaryShow created item's key fields, success indicator
Update (PATCH/PUT)Before/after or summaryShow updated fields, confirmation status
Delete (DELETE)Simple confirmationItem identifier, success message

When to Use API Plugins

Use API plugins (actions) when the agent needs to:

  • Call external APIs not available through M365 capabilities

  • Perform CRUD operations (Create, Read, Update, Delete)

  • Access real-time transactional data from external systems

  • Modify state in external systems (create tickets, update records)

  • Integrate with line-of-business systems (CRM, ticketing, databases)

Don't use API plugins when:

  • M365 capabilities already provide the data (use capabilities like OneDriveAndSharePoint, Email, etc.)

  • Read-only access to documents is sufficient

  • No external API interaction needed

API Plugin vs Capabilities Decision Tree

Need external API?
  Yes → Create API Plugin Action
  No → Use built-in Capability
    ↓
Need real-time transactional data?
  Yes → API Plugin
  No → Capability (SharePoint, Connectors)
    ↓
Need CRUD operations?
  Yes → API Plugin
  No → Capability
    ↓
Need to modify state?
  Yes → API Plugin
  No → Capability

API Plugin Manifest Structure (v2.4)

The API plugin manifest is a JSON file that configures how M365 Copilot interacts with your API.

Basic Structure
{
  "schema_version": "v2.4",
  "name_for_human": "Plugin Name",
  "namespace": "pluginNamespace",
  "description_for_human": "What this plugin does for users",
  "description_for_model": "Detailed instructions for the AI model on when and how to use this plugin",
  "functions": [],
  "runtimes": []
}
Complete Example: Repairs API Plugin
{
  "schema_version": "v2.4",
  "name_for_human": "Repairs API",
  "namespace": "repairs",
  "description_for_human": "Manage repair tickets and track issues",
  "description_for_model": "Use this plugin to search, create, update, and delete repair tickets. Call getRepairs to list all repairs or filter by assignee. Call getRepairById to get details about a specific repair. Call createRepair to create new tickets. Call updateRepair to modify existing repairs. Call deleteRepair to remove completed repairs.",
  "logo_url": "https://repairshub.azurewebsites.net/logo.png",
  "contact_email": "[email protected]",
  "legal_info_url": "https://contoso.com/terms",
  "privacy_policy_url": "https://contoso.com/privacy",
  "functions": [
    {
      "name": "getRepairs",
      "description": "Get all repairs, optionally filtered by assignee",
      "capabilities": {
        "response_semantics": {
          "data_path": "$",
          "properties": {
            "title": "$.title",
            "subtitle": "$.assignedTo"
          }
        }
      }
    },
    {
      "name": "getRepairById",
      "description": "Get a specific repair by its ID"
    },
    {
      "name": "createRepair",
      "description": "Create a new repair ticket",
      "capabilities": {
        "confirmation": {
          "type": "AdaptiveCard",
          "title": "Create Repair?",
          "body": "Are you sure you want to create this repair ticket?"
        }
      }
    },
    {
      "name": "updateRepair",
      "description": "Update an existing repair ticket",
      "capabilities": {
        "confirmation": {
          "type": "AdaptiveCard",
          "title": "Update Repair?",
          "body": "Are you sure you want to update this repair?"
        }
      }
    },
    {
      "name": "deleteRepair",
      "description": "Delete a repair ticket",
      "capabilities": {
        "confirmation": {
          "type": "AdaptiveCard",
          "title": "Delete Repair?",
          "body": "Are you sure you want to permanently delete this repair?"
        }
      }
    }
  ],
  "runtimes": [
    {
      "type": "OpenApi",
      "auth": {
        "type": "None"
      },
      "spec": {
        "url": "apiSpecificationFile/openapi.json"
      },
      "run_for_functions": ["getRepairs", "getRepairById", "createRepair", "updateRepair", "deleteRepair"]
    }
  ]
}

OpenAPI Specification

The OpenAPI specification describes your REST API endpoints. Here's an example based on the Repairs API:

Example: Repairs API OpenAPI Spec
{
  "openapi": "3.1.0",
  "info": {
    "title": "Repairs API",
    "description": "A simple service to manage repairs for various items",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://repairshub.azurewebsites.net/"
    }
  ],
  "paths": {
    "/repairs": {
      "get": {
        "operationId": "getRepairs",
        "summary": "Get all repairs",
        "parameters": [
          {
            "name": "assignedTo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter repairs by assignee name"
          }
        ],
        "responses": {
          "200": {
            "description": "List of repairs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Repair"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createRepair",
        "summary": "Create a new repair",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRepairRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Repair created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Repair"
                }
              }
            }
          }
        }
      }
    },
    "/repairs/{id}": {
      "get": {
        "operationId": "getRepairById",
        "summary": "Get a repair by ID",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Repair found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Repair"
                }
              }
            }
          },
          "404": {
            "description": "Repair not found"
          }
        }
      },
      "patch": {
        "operationId": "updateRepair",
        "summary": "Update a repair by ID",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRepairRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Repair updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Repair"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteRepair",
        "summary": "Delete a repair by ID",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Repair deleted"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Repair": {
        "type": "object",
        "properties": {
          "id": { "type": "number" },
          "title": { "type": "string" },
          "description": { "type": "string" },
          "assignedTo": { "type": "string" },
          "date": { "type": "string" },
          "image": { "type": "string" }
        },
        "required": ["id", "title", "description", "assignedTo", "date", "image"]
      },
      "CreateRepairRequest": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "description": { "type": "string" },
          "assignedTo": { "type": "string" },
          "date": { "type": "string" },
          "image": { "type": "string" }
        },
        "required": ["title", "description", "assignedTo", "date", "image"]
      },
      "UpdateRepairRequest": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "description": { "type": "string" },
          "assignedTo": { "type": "string" },
          "date": { "type": "string" },
          "image": { "type": "string" }
        }
      }
    }
  }
}

Linking API Plugin to Declarative Agent

Reference the API plugin in your declarativeAgent.json:

{
  "version": "v1.6",
  "name": "Repairs Agent",
  "description": "An agent to manage repair tickets",
  "instructions": "You help users manage repair tickets. Use the repairs API to search, create, update, and delete repairs.",
  "actions": [
    {
      "id": "repairsPlugin",
      "file": "plugins/repairs-plugin.json"
    }
  ],
  "conversation_starters": [
    {
      "title": "My Repairs",
      "text": "What repairs are assigned to me?"
    },
    {
      "title": "Create Repair",
      "text": "I need to create a new repair ticket"
    }
  ]
}

Authentication for API Plugins

No Authentication

For public APIs or development:

{
  "runtimes": [
    {
      "type": "OpenApi",
      "auth": {
        "type": "None"
      },
      "spec": {
        "url": "apiSpecificationFile/openapi.json"
      }
    }
  ]
}
OAuth Authentication

For OAuth2-protected APIs:

{
  "runtimes": [
    {
      "type": "OpenApi",
      "auth": {
        "type": "OAuthPluginVault",
        "reference_id": "your-oauth-reference-id"
      },
      "spec": {
        "url": "apiSpecificationFile/openapi.json"
      }
    }
  ]
}
API Key Authentication

For API key-protected APIs:

{
  "runtimes": [
    {
      "type": "OpenApi",
      "auth": {
        "type": "ApiKeyPluginVault",
        "reference_id": "your-apikey-reference-id"
      },
      "spec": {
        "url": "apiSpecificationFile/openapi.json"
      }
    }
  ]
}

Note: The reference_id is obtained when configuring authentication in the Microsoft 365 admin center or through the ATK CLI during provisioning.


API Plugin Best Practices

1. Operation Naming in OpenAPI

Use clear operationId values that describe the action:

{
  "get": {
    "operationId": "searchTickets",
    "summary": "Search for tickets by keyword or status"
  },
  "post": {
    "operationId": "createTicket",
    "summary": "Create a new ticket"
  }
}

✅ Good: searchTickets, createTicket, updateTicketStatus ❌ Bad: tickets, doSomething, api1

2. Descriptive Function Entries

Provide detailed descriptions in the plugin manifest:

{
  "functions": [
    {
      "name": "searchTickets",
      "description": "Search for support tickets. Use this when the user asks about finding tickets, checking ticket status, or looking up issues. Supports filtering by status (open, closed, pending) and assignee."
    }
  ]
}
3. Add Confirmations for Destructive Operations

Always add confirmation dialogs for create, update, and delete operations:

{
  "functions": [
    {
      "name": "deleteTicket",
      "description": "Permanently delete a ticket",
      "capabilities": {
        "confirmation": {
          "type": "AdaptiveCard",
          "title": "Delete Ticket?",
          "body": "This action cannot be undone. Are you sure you want to delete this ticket?"
        }
      }
    }
  ]
}
4. Use Response Semantics for Rich Display

Configure how results are displayed to users:

{
  "functions": [
    {
      "name": "getRepairs",
      "description": "Get all repairs",
      "capabilities": {
        "response_semantics": {
          "data_path": "$",
          "properties": {
            "title": "$.title",
            "subtitle": "$.assignedTo",
            "url": "$.url"
          },
          "static_template": {
            "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
            "type": "AdaptiveCard",
            "version": "1.5",
            "body": [
              {
                "type": "TextBlock",
                "text": "${title}",
                "weight": "Bolder",
                "size": "Medium"
              },
              {
                "type": "TextBlock",
                "text": "Assigned to: ${assignedTo}",
                "spacing": "None"
              },
              {
                "type": "TextBlock",
                "text": "${description}",
                "wrap": true
              }
            ]
          }
        }
      }
    }
  ]
}

Common API Plugin Patterns

Pattern: CRM Integration

Plugin Manifest:

{
  "schema_version": "v2.4",
  "name_for_human": "CRM API",
  "namespace": "crm",
  "description_for_human": "Manage customer accounts and opportunities",
  "description_for_model": "Use this plugin to search for customer accounts, view account details, list opportunities, and update opportunity status in the CRM system.",
  "functions": [
    {
      "name": "searchAccounts",
      "description": "Search for customer accounts by name or industry"
    },
    {
      "name": "getAccount",
      "description": "Get detailed information about a specific account"
    },
    {
      "name": "listOpportunities",
      "description": "List sales opportunities, optionally filtered by account or stage"
    },
    {
      "name": "updateOpportunityStage",
      "description": "Update the stage of a sales opportunity",
      "capabilities": {
        "confirmation": {
          "type": "AdaptiveCard",
          "title": "Update Opportunity?",
          "body": "Update the opportunity stage?"
        }
      }
    }
  ],
  "runtimes": [
    {
      "type": "OpenApi",
      "auth": {
        "type": "OAuthPluginVault",
        "reference_id": "crm-oauth-ref"
      },
      "spec": {
        "url": "apiSpecificationFile/crm-openapi.json"
      }
    }
  ]
}
Pattern: Ticketing System

Plugin Manifest:

{
  "schema_version": "v2.4",
  "name_for_human": "Ticket System",
  "namespace": "tickets",
  "description_for_human": "Manage support tickets and track issues",
  "description_for_model": "Use this plugin to search support tickets, create new tickets, add comments, and update ticket status. Always confirm with the user before creating or modifying tickets.",
  "functions": [
    {
      "name": "searchTickets",
      "description": "Search for tickets by keyword, status, or assignee",
      "capabilities": {
        "response_semantics": {
          "data_path": "$.tickets",
          "properties": {
            "title": "$.title",
            "subtitle": "$.status"
          }
        }
      }
    },
    {
      "name": "getTicket",
      "description": "Get detailed information about a specific ticket including comments and history"
    },
    {
      "name": "createTicket",
      "description": "Create a new support ticket",
      "capabilities": {
        "confirmation": {
          "type": "AdaptiveCard",
          "title": "Create Ticket?",
          "body": "Create a new support ticket?"
        }
      }
    },
    {
      "name": "addComment",
      "description": "Add a comment to an existing ticket",
      "capabilities": {
        "confirmation": {
          "type": "AdaptiveCard",
          "title": "Add Comment?",
          "body": "Add this comment to the ticket?"
        }
      }
    },
    {
      "name": "updateTicketStatus",
      "description": "Update the status of a ticket (open, in-progress, resolved, closed)",
      "capabilities": {
        "confirmation": {
          "type": "AdaptiveCard",
          "title": "Update Status?",
          "body": "Update the ticket status?"
        }
      }
    }
  ],
  "runtimes": [
    {
      "type": "OpenApi",
      "auth": {
        "type": "ApiKeyPluginVault",
        "reference_id": "tickets-apikey-ref"
      },
      "spec": {
        "url": "apiSpecificationFile/tickets-openapi.yaml"
      }
    }
  ]
}
Pattern: Analytics/Reporting

Plugin Manifest:

{
  "schema_version": "v2.4",
  "name_for_human": "Analytics API",
  "namespace": "analytics",
  "description_for_human": "Query business metrics and generate reports",
  "description_for_model": "Use this plugin to retrieve business metrics, run analytics queries, and generate reports. This is a read-only API for data analysis.",
  "functions": [
    {
      "name": "getMetrics",
      "description": "Get key business metrics for a specified date range"
    },
    {
      "name": "runQuery",
      "description": "Execute a custom analytics query with filters and dimensions"
    },
    {
      "name": "getReport",
      "description": "Get a pre-built report by name or ID"
    }
  ],
  "runtimes": [
    {
      "type": "OpenApi",
      "auth": {
        "type": "OAuthPluginVault",
        "reference_id": "analytics-oauth-ref"
      },
      "spec": {
        "url": "apiSpecificationFile/analytics-openapi.json"
      }
    }
  ]
}

Testing API Plugins

1. Validate OpenAPI Specification

Before adding to your agent, validate your OpenAPI spec:

# Using online validator
# Visit https://editor.swagger.io and paste your spec

# Or use a local tool
npx @apidevtools/swagger-cli validate openapi.json
2. Test API Endpoints Directly

Use tools like Bruno, Postman, or REST Client extension to test your API:

### Get all repairs
GET https://repairshub.azurewebsites.net/repairs

### Get repair by ID
GET https://repairshub.azurewebsites.net/repairs/1

### Create a new repair
POST https://repairshub.azurewebsites.net/repairs
Content-Type: application/json

{
  "title": "Fix broken laptop",
  "description": "Screen is cracked",
  "assignedTo": "John Doe",
  "date": "2026-01-20",
  "image": "https://example.com/image.jpg"
}
3. Provision and Test in M365 Copilot
# Provision the agent
npx -y --package @microsoft/m365agentstoolkit-cli atk provision --env local --interactive false

# Use the returned test URL to test in M365 Copilot

Test with various prompts:

  • "Show me all repairs"

  • "Find repairs assigned to John"

  • "Create a new repair for a broken laptop"

  • "Update repair #1 to assign it to Jane"


Security Considerations

1. Credential Management
  • Never hardcode credentials in JSON files

  • Use reference_id for OAuth and API key authentication

  • Store secrets in environment variables or secure vaults

  • Rotate credentials regularly

2. Input Validation
  • Validate all inputs in your API backend

  • Prevent injection attacks

  • Limit string lengths and ranges

  • Use strong typing in OpenAPI schemas

3. Confirmation Dialogs
  • Always add confirmations for state-changing operations

  • Be clear about what action will be taken

  • Allow users to cancel destructive operations

4. Rate Limiting
  • Implement rate limiting in your API backend

  • Handle 429 (Too Many Requests) responses gracefully

  • Consider caching for frequently-accessed data

5. Data Privacy
  • Only request necessary data from your API

  • Follow data residency requirements

  • Log API calls for audit purposes

  • Handle PII appropriately


Common Issues and Solutions

IssueCauseSolution
"Function not found"operationId in OpenAPI doesn't match name in functionsEnsure function names match OpenAPI operationIds
Authentication failsInvalid or expired reference_idRe-register authentication in admin center
CORS errorsAPI doesn't allow agent originConfigure API CORS to allow M365 origins
Timeout errorsAPI response too slowOptimize API, add caching, increase timeout
Schema mismatchPlugin expects different response formatUpdate OpenAPI spec to match actual API response
"No operations selected"Empty --api-operation parameterSpecify operations in correct format: METHOD /path
"File not found" during npx -y --package @microsoft/m365agentstoolkit-cli atk add actionRelative path used for local OpenAPI specUse absolute path: /full/path/to/openapi.json
"File not found in zip archive" during provisionManual plugin creation with incorrect spec pathUse npx -y --package @microsoft/m365agentstoolkit-cli atk add action command - do not manually create plugins
OpenAPI spec path resolution failsPath conflicts between zipAppPackage and M365 serviceThe ATK CLI handles this correctly - always use npx -y --package @microsoft/m365agentstoolkit-cli atk add action
Why Manual Plugin Creation Fails

If you try to manually create API plugin files, you'll encounter path resolution conflicts:

  1. zipAppPackage resolves spec paths relative to the plugin file location

  2. M365 extendToM365 resolves spec paths relative to the zip archive root

These different resolution strategies make manual path configuration nearly impossible. The npx -y --package @microsoft/m365agentstoolkit-cli atk add action command handles this complexity automatically by:

  • Placing the OpenAPI spec in apiSpecificationFile/

  • Using the correct relative path apiSpecificationFile/openapi.yaml in the plugin

  • Ensuring the zip archive structure matches the path expectations


Authentication for API Plugins

The examples above use "auth": {"type": "None"} (unauthenticated APIs). If your API requires OAuth authentication, see authentication.md for:

  • Discovering OAuth endpoints from well-known metadata

  • Obtaining client credentials (via Dynamic Client Registration or manual entry)

  • Configuring the oauth/register lifecycle step in m365agents.yml

  • Using OAuthPluginVault in the plugin manifest runtime block

The oauth/register step must be added to m365agents.yml before teamsApp/zipAppPackage. The resulting <PREFIX>_MCP_AUTH_ID environment variable is referenced in the plugin's runtime auth block.


Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs Node.js

Security

What our scanner noticed· Low risk

  • Phrasing that asks for actions without confirmation
  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Microsoft
Publisher
Microsoft (Official)
Source
microsoft/skills · .github/plugins/microsoft-365-agents-toolkit/skills/declarative-agent-developer · commit 0696600
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Microsoft.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download declarative-agent-developer.zip