Azure / Azure/Connectors

M365 declarative agents can't connect to Connector Namespace hosted MCP servers — need custom Entra app for inbound auth

Open
#146 0 comments 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.