> ## Documentation Index
> Fetch the complete documentation index at: https://gateway.forceaisecurity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Microsoft 365 Copilot (A2A)

> Register a Microsoft 365 agent in the ForceAI Gateway over A2A with Entra OAuth.

The gateway calls a Microsoft agent that is exposed as an A2A server, minting an Entra (Azure AD) OAuth token per request and forwarding it. It supports both the app-identity flow (`client_credentials`) and the delegated flow (`on_behalf_of`) where the real end-user identity is carried through

<Note>
  This is the **outbound** direction: ForceAI reaches into Microsoft. For the reverse, where Copilot Studio or a declarative agent calls ForceAI as a tool, see the [Microsoft 365 Tool Adapter](/agents/m365-adapter).
</Note>

## Prerequisites (Microsoft side)

<Steps>
  <Step title="Expose the agent as an A2A server">
    The target must be a reachable HTTPS A2A endpoint with an agent card. Built-in M365 Copilot agents (Researcher, Analyst) and plain Copilot Studio agents do not qualify; host the agent with the Microsoft Agent Framework (`agent-framework-a2a`, `A2AExecutor`) or the M365 Agents SDK. The endpoint looks like `https://<host>/a2a/<agent>/v1/message:stream`, with the card at `<endpoint>/.well-known/agent-card.json`
  </Step>

  <Step title="Register an Entra app for ForceAI">
    In Azure Portal -> Microsoft Entra ID -> App registrations -> New registration, create the app ForceAI authenticates as. Copy the Tenant ID and Client ID, then create a client secret under Certificates and secrets
  </Step>

  <Step title="Set the scope and consent">
    The scope is the App ID URI of the app protecting the target agent plus `/.default`, for example `api://<agent-app-id>/.default`. For `client_credentials`, grant the application permission on the target and click Grant admin consent, otherwise the token is issued but the downstream call is rejected
  </Step>
</Steps>

## The two Entra flows

<Tabs>
  <Tab title="client_credentials (app identity)">
    ForceAI calls the agent as itself. Simplest to set up. Requires admin consent on the target API. Use when the agent does not need to act as a specific end user
  </Tab>

  <Tab title="on_behalf_of (delegated)">
    ForceAI exchanges the caller's own Entra token for a downstream token, so the agent runs with the end user's identity and permissions. Pass the caller's Entra token to the invoke call in the `x-forceai-user-assertion` header
  </Tab>
</Tabs>

## Register the agent

The M365 backend is a native A2A server, so there is no model template. Set `a2a_backend` to `m365_copilot`, put the agent's A2A endpoint in `agent_card_params.url`, and supply the Entra fields

```bash theme={null}
curl -X POST https://YOUR_GATEWAY_DOMAIN/v1/agents \
  -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{
    "agent_name": "m365-copilot",
    "agent_card_params": {
      "protocolVersion": "0.3", "name": "M365 Copilot Agent",
      "description": "Microsoft 365 agent over A2A",
      "url": "https://<host>/a2a/<agent>/v1/message:stream",
      "capabilities": { "streaming": true },
      "defaultInputModes": ["text"], "defaultOutputModes": ["text"],
      "skills": [{ "id": "chat", "name": "Chat", "description": "M365 agent", "tags": ["m365"] }]
    },
    "forceai_params": {
      "a2a_backend": "m365_copilot",
      "entra_tenant_id": "00000000-0000-0000-0000-000000000000",
      "entra_client_id": "00000000-0000-0000-0000-000000000000",
      "entra_client_secret": "<secret>",
      "entra_scope": "api://<agent-app-id>/.default",
      "entra_flow": "client_credentials"
    }
  }'
```

Or in the dashboard: **Agentic -> Agents -> Add New Agent**, pick **Microsoft 365 Copilot (A2A)**, and fill the Entra fields plus the agent endpoint URL

### Fields

<ParamField path="a2a_backend" type="string" required>
  `m365_copilot`. Note this backend uses `a2a_backend`, not `custom_llm_provider`
</ParamField>

<ParamField path="agent_card_params.url" type="string" required>
  The agent's A2A endpoint, for example `https://<host>/a2a/<agent>/v1/message:stream`
</ParamField>

<ParamField path="entra_tenant_id / entra_client_id / entra_client_secret" type="string" required>
  The Entra app ForceAI authenticates as
</ParamField>

<ParamField path="entra_scope" type="string" required>
  App ID URI of the target plus `/.default`, for example `api://<agent-app-id>/.default`
</ParamField>

<ParamField path="entra_flow" type="string" default="client_credentials">
  `client_credentials` or `on_behalf_of`
</ParamField>

## Approve and invoke

```bash theme={null}
# Approve
curl -X POST https://YOUR_GATEWAY_DOMAIN/v1/agents/{agent_id}/approve \
  -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" -d '{"reason":"onboard"}'

# Invoke (client_credentials)
curl -X POST https://YOUR_GATEWAY_DOMAIN/v1/a2a/{agent_id}/message/send \
  -H "Authorization: Bearer $AGENT_KEY" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"message/send",
       "params":{"message":{"role":"user","parts":[{"kind":"text","text":"summarize my week"}]}}}'
```

For `on_behalf_of`, add the caller's Entra token so the agent runs as the end user:

```bash theme={null}
  -H "x-forceai-user-assertion: <caller-entra-access-token>"
```

The gateway mints and caches the Entra bearer per request (refreshed shortly before expiry) and forwards it to the M365 A2A endpoint. In Logs the request is attributed to the `azure` provider

<Warning>
  If a `client_credentials` call returns a token but the agent still rejects the request, admin consent is missing on the target API. Grant admin consent on the app registration and retry
</Warning>
