M365 declarative agents can't connect to Connector Namespace hosted MCP servers — need custom Entra app for inbound auth
- Dominant language
- JavaScript
- Stars
- 20
- Forks
- 7
- Avg merge
- 7d 9h
- Merged PRs (30d)
- 1
Description
### What problem are you trying to solve?
## Summary
A Microsoft 365 Copilot **declarative agent** cannot authenticate to a **Connector Namespace hosted MCP server** (e.g. the hosted Azure SQL MCP server), even though the same server works fine from GitHub Copilot in VS Code. The agent gets a 401 that the LLM narrates as "your permissions don't allow access," but it's an **auth-model mismatch**, not a SQL/RBAC problem.
## Why the agent can't connect
### What the schema requires
Every declarative agent that talks to an MCP server ships a *plugin manifest* (`ai-plugin.json`) that declares how the agent authenticates to that server. The shape of that manifest is fixed by Microsoft's published **plugin manifest schema**, so the schema is the authoritative statement of which auth options the agent even supports. Its latest version (v2.4) allows exactly three runtime `auth.type` values:
```json
"enum": ["None", "OAuthPluginVault", "ApiKeyPluginVault"]
```
(`https://developer.microsoft.com/json-schemas/copilot/plugin/v2.4/schema.json`, `$defs/auth-object`.) The only other property is `reference_id`, described as a value "acquired independently when providing the necessary authentication configuration values" so as to "prevent the need for storing secret values in the plugin manifest." In other words, an authenticated agent **must** supply its own out-of-band credentials (a vault reference). There is **no** auth type that lets the agent sign in as a Microsoft first-party identity.
### Concrete example
The tutorial [Build plugins from an MCP server for Microsoft 365 Copilot](https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/build-mcp-plugins) shows a concrete example of that schema constraint: its only OAuth path is *"OAuth (with static registration),"* where the developer [registers their own OAuth app](https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/build-mcp-plugins#register-an-oauth-app-with-github) and supplies its client ID + secret at provision — that's what produces the `OAuthPluginVault` `reference_id`. The sample uses a GitHub OAuth app only because the sample server is GitHub-protected; the model is **provider-agnostic** — you bring your own client for whichever IdP guards the server. For an Entra-protected server like the hosted SQL MCP server (authorization server `login.microsoftonline.com`), the equivalent is a custom **Entra** app.
### Why that breaks on a hosted server
The hosted MCP server's protected-resource metadata (RFC 9728) fixes the token audience to the first-party `apihub.azure.com` resource (appId `fe053c5f-3692-4f14-aef2-ee34fc081cae`), scope `https://apihub.azure.com/.default` — something a customer cannot register or own. So the developer's custom client has **no valid resource to request a token for**. VS Code and Azure CLI work only because they are fixed, pre-authorized first-party clients on that resource's allowlist (Azure CLI `04b07795-8ddb-461a-bbee-02f9e1bf7b46`, VS Code `aebc6443-996d-45c2-90f0-388ff96faa56`); a declarative agent has no equivalent fixed 1P client to add.
## Repro
1. Create a hosted Azure SQL MCP server following the quickstart in [microsoft/sql-server-samples#1485](https://github.com/microsoft/sql-server-samples/pull/1485).
2. Create a declarative agent over that MCP server following the [Build plugins from an MCP server](https://learn.microsoft.com/en-us/microsoft-365/copilot/extensibility/build-mcp-plugins) tutorial.
3. Go to https://m365.cloud.microsoft/chat, find the agent, and ask it to return the tables in the SQL database.
**Result:** the agent returns a 401-driven "your permissions don't allow access" message. The same MCP server, added to VS Code, connects and lists its tools successfully — confirming the failure is the inbound-auth model, not SQL/RBAC.
### Supporting evidence
- Hosted SQL MCP protected-resource metadata → `authorization_servers: login.microsoftonline.com//v2.0`, `scopes_supported: ["https://apihub.azure.com/.default"]`.
- Device-code test with a custom app for `https://apihub.azure.com/user_impersonation` → `AADSTS650053` (scope not exposed to custom apps).
### Proposed solution
## The fix
**Connector Namespace should support configuring inbound auth against a customer's own Entra app** — a resource the customer owns, exposing a consentable scope such as `access_as_user` — instead of hardwiring the audience to `apihub.azure.com`.
This is the right fix because it:
- Matches the declarative agent's existing model exactly (register one Entra app, use it as the OAuth client, server validates that audience).
- Is **single-sided** — a change only on the Connector Namespace / Logic Apps side, with no dependency on the M365 Copilot runtime.
## Why the alternative fixes don't work
**Pre-authorizing "the agent's client ID" on apihub** (how CLI/VS Code work): there's no single agent client ID to allowlist — static registration means a different custom app per developer/tenant. Even if a fixed 1P agent client existed, the vault uses a confidential-client flow (clientId + secret), while 1P public clients use PKCE with no holdable secret. It would also require a two-sided redesign (M365 adds a 1P-identity auth mode *and* apihub allowlists it), and the M365 side doesn't exist in the schema.
**Customer-side delegated permission + admin consent on apihub:** the `apihub.azure.com` service principal isn't present/grantable in the customer tenant (`az ad sp show fe053c5f-...` → "does not exist"), so it never appears in "APIs my organization uses," and a custom app requesting its scope is rejected with `AADSTS650053: scope 'user_impersonation' doesn't exist on the resource` — the scope is deliberately gated to the pre-authorized 1P allowlist.
### Alternatives considered
_No response_
### Additional context
_No response_
Contributor guide
Research direction
Start with the Connector Namespace inbound-auth behavior described in the issue, then review the linked Microsoft 365 Copilot MCP plugin tutorial and the hosted SQL MCP quickstart. Inspect the protected-resource metadata and reproduce the 401 with the listed declarative-agent steps. Done means a customer-owned Entra app can provide a consentable audience and the declarative agent can connect successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure
- Domain
- api, authentication, authorization, cloud
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100