Skip to content

Entra Authentication for MCP doesn't work #12308

Closed

Description

@adameska

Description

The request to my entra app to login and authorize is including resource when it shouldn't And i can't control what is in the resource and it's not matching verbatim what my app registration has as an Application ID URI (my MCP has resource: "http://localhost:3000" and my entra has http://localhost:3000 as the ID but opencode is passing resource of "http://localhost:3000/" which is causing getting a token to fail. My MCP is returning an authorization server of
"https://login.microsoftonline.com/tenat-guid/v2.0" which doesn't support dynamic client registration (so i set a client id and scopes, it didn't look like i could set resource even if i wanted to?)

We shouldn't need to set resource at all... the scope "http://localhost:3000/basic" should be all that's needed for entra auth to work.

Plugins

none

OpenCode version

1.1.51

Steps to reproduce

  1. Add a MCP resource that uses entra auth
  2. Try and authenticate with automatic oauth token generation instead of setting it yourself

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. github-actions commented on Feb 5, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    The core issue seems to be that OpenCode is passing a 'resource' parameter that doesn't match your Entra configuration. Issues #7306 and #7135 discuss similar problems with OAuth configuration overrides and path handling.

    Feel free to ignore if none of these address your specific case.

  2. sergey-tihon commented on Mar 3, 2026

    @sergey-tihon

    Investigation: Root cause traced to MCP TypeScript SDK sending resource parameter to Entra ID

    I've investigated this issue. Here's what happens:

    1. The MCP server responds correctly (per RFC 9728)

    The server returns a WWW-Authenticate header pointing to its Protected Resource Metadata:

    WWW-Authenticate: Bearer resource_metadata="https://my.server.com/.well-known/oauth-protected-resource/mcp/"
    

    That metadata document contains:

    {
      "resource": "https://my.server.com/mcp/",
      "authorization_servers": ["https://login.microsoftonline.com/<tenant>/v2.0"],
      "scopes_supported": ["api://<app-id>/User.Mcp"]
    }

    2. The @modelcontextprotocol/sdk sends resource to Entra ID

    The SDK's startAuthorization() reads the resource field from the PRM document and appends resource=https://my.server.com/mcp/ to the Entra ID /authorize URL. executeTokenRequest() does the same for the /token endpoint.

    3. Entra ID v2.0 rejects the resource parameter

    Microsoft Entra ID v2.0 endpoints do not support RFC 8707 Resource Indicators. In v2.0, the target resource is encoded within the scope parameter (e.g., api://<app-id>/User.Mcp). When resource is present alongside scope pointing to different identifiers, Entra returns AADSTS9010010.

    4. The spec already has a fix, the SDK hasn't caught up

    SEP-835 (merged) updated the MCP auth spec with a scope selection strategy: use scope from WWW-Authenticate first, then fall back to scopes_supported from PRM — making the resource parameter unnecessary when scopes are available. However, the TypeScript SDK still unconditionally sends resource.

    This is tracked upstream as modelcontextprotocol/typescript-sdk#862. The SDK maintainers prefer to solve this at the spec level (done) and via pluggable auth (typescript-sdk#485) rather than adding vendor-specific carve-outs.

    TL;DR: The SDK sends resource per RFC 8707, Entra ID doesn't support RFC 8707, and the spec-level fix (SEP-835) exists but the SDK hasn't implemented it yet. OpenCode needs to strip resource at its layer until the SDK catches up.

  3. adameska commented on Mar 3, 2026

    @adameska
    Author

    Agreed, it would be nice to not have a vendor specific case for this, but until then this does fix the problem and is scoped to entra: #12321

  4. DavidParks8 commented on Mar 19, 2026

    @DavidParks8

    There doesn’t need to be a vendor specific case. Just add another identifier uri to the entra app with the trailing slash and everything starts working.

  5. DavidParks8 commented on Mar 19, 2026

    @DavidParks8

    The resource parameter validation in the V2 end point is requiring an exact match. That means you need to have an identifier with the trailing slash and another identifier without it because some MCP clients will include it and some will not. I am raising that issue internally right now to try and ignore the trailing slash difference.

    This is actually a really important security issue to get right because without the resource parameter, someone can steal your tokens by hosting a malicious MCP server that advertises scopes for another service. This is called a confused deputy attack.

  6. DavidParks8 commented on Mar 19, 2026

    @DavidParks8

    @adameska please abandon the PR. We should not introduce an easily exploitable vulnerability into opencode.

  7. sergey-tihon commented on Mar 19, 2026

    @sergey-tihon

    @DavidParks8 thank you for looking at this issue, but it is not only '/' issue.

    Here is configuration of my mcp

        "entra-remote": {
          "type": "remote",
          "url": "https://domain.com/mcp/",
          "enabled": true,
          "oauth": {
            "clientId": "c1e199f6-ae8d-40a3-9c0c-b921e1af6474",
            "scope": "api://domain.com/e0630cc0-d0c2-4512-beda-dec29dd569bc/User.Mcp"
          }
        },
    

    where e0630cc0-d0c2-4512-beda-dec29dd569bc application id of mcp server app, c1e199f6-ae8d-40a3-9c0c-b921e1af6474 is the connector app where we allow opencode localhost callback url.

    the same pair of clientId and scope works in PowerApp custom connector to connect same mcp server to Copilot Studio Agent.

    when i execute auth from opencode

    Image

    it fails with error

    Request Id: f8cb6001-b9ac-4d32-8740-7ed346793d00
    Correlation Id: 4eb8dc33-7c4e-4e42-ba5b-ff239de6f8da
    Timestamp: 2026-03-19T07:05:35Z
    Message: AADSTS9010010: The resource parameter provided in the request doesn't match with the requested scopes.
    

    redirect url looks like this
    https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/authorize?response_type=code&client_id=c1e199f6-ae8d-40a3-9c0c-b921e1af6474&code_challenge=fFFoIXv8ix4UmmrIncHCLktPiVhP_x-EFQA7ilvHYvE&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A19876%2Fmcp%2Foauth%2Fcallback&state=3ab490b787e62afb0ee1a22782748f163146ef4282293490b6a772f956c5c543&scope=api%3A%2F%2Fe0630cc0-d0c2-4512-beda-dec29dd569bc%2FUser.Mcp+api%3A%2F%2Fe0630cc0-d0c2-4512-beda-dec29dd569bc%2FUser.McpPlus&resource=https%3A%2F%2Fdomain.com%2Fmcp%2F

    p.s. entra id application id uri must not end in a slash

    There doesn’t need to be a vendor specific case. Just add another identifier uri to the entra app with the trailing slash and everything starts working.

    Image

    p.p.s. opencode uses 127.0.0.1 instead of localhost and there is no way to add 127.0.0.1 as return url in entra id, but this the next issues after resource

  8. DavidParks8 commented on Mar 19, 2026

    @DavidParks8

    It seems there is a difference between msft internal app validation and 3p app validation. I'll code up a fix today so trailing slash is normalized internally at login.microsoftonline.com

  9. DavidParks8 commented on Mar 19, 2026

    @DavidParks8

    @sergey-tihon can you also share your app registration manifest, but with sensitive data stripped?

  10. adameska commented on Mar 19, 2026

    @adameska
    Author

    It seems there is a difference between msft internal app validation and 3p app validation. I'll code up a fix today so trailing slash is normalized internally at login.microsoftonline.com

    This would be great! Yes Microsoft doesn't allow the app identifier to end with a trailing slash so currently it's impossible to get them to match. But I think using resource at all is discouraged in the v2 flow so that's why I attempted that. If we can remove the trailing slash then I should be able to get things working.

    Also the change to localhost from 127.0.0.1 is required since Microsoft will only allow callback urls that are http if it's localhost (that word exactly) otherwise it requires https

  11. adameska commented on Mar 19, 2026

    @adameska
    Author

    @sergey-tihon can you also share your app registration manifest, but with sensitive data stripped?

    Here is mine, if resource is required i would need to change my appId to be "https://mcpserver.company.com" instead of the default since MCP requires that resource to also match the URL of the hosted mcp resource...:

    {
    	"id": "objectid",
    	"deletedDateTime": null,
    	"appId": "appid",
    	"applicationTemplateId": null,
    	"disabledByMicrosoftStatus": null,
    	"createdByAppId": "azure",
    	"createdDateTime": "2026-02-03T15:59:18Z",
    	"displayName": "DEV - Agent Instructions",
    	"description": "MCP Server to serve up instruction and prompt resources",
    	"groupMembershipClaims": null,
    	"identifierUris": [
    		"api://appid"
    	],
    	"isDeviceOnlyAuthSupported": null,
    	"isFallbackPublicClient": null,
    	"nativeAuthenticationApisEnabled": null,
    	"notes": null,
    	"publisherDomain": "mycompany.onmicrosoft.com",
    	"serviceManagementReference": null,
    	"signInAudience": "AzureADMyOrg",
    	"tags": [
    		"Owners=AI Devs",
    		"Environment=development",
    		"UniqueName=AI.AgentInstructions"
    	],
    	"tokenEncryptionKeyId": null,
    	"samlMetadataUrl": null,
    	"defaultRedirectUri": null,
    	"certification": null,
    	"servicePrincipalLockConfiguration": null,
    	"requestSignatureVerification": null,
    	"addIns": [],
    	"api": {
    		"acceptMappedClaims": true,
    		"knownClientApplications": [],
    		"requestedAccessTokenVersion": 2,
    		"oauth2PermissionScopes": [
    			{
    				"adminConsentDescription": "basic",
    				"adminConsentDisplayName": "basic",
    				"id": "abc",
    				"isEnabled": true,
    				"type": "User",
    				"userConsentDescription": "basic",
    				"userConsentDisplayName": "basic",
    				"value": "basic"
    			}
    		],
    		"preAuthorizedApplications": [
    			{
    				"appId": "",
    				"delegatedPermissionIds": [
    					"abc"
    				]
    			}
    		]
    	},
    	"appRoles": [
    		{
    			"allowedMemberTypes": [
    				"User"
    			],
    			"description": "PromptsRead",
    			"displayName": "PromptsRead",
    			"id": "",
    			"isEnabled": true,
    			"origin": "Application",
    			"value": "PromptsRead"
    		},
    		{
    			"allowedMemberTypes": [
    				"User"
    			],
    			"description": "InstructionsRead",
    			"displayName": "InstructionsRead",
    			"id": "",
    			"isEnabled": true,
    			"origin": "Application",
    			"value": "InstructionsRead"
    		}
    	],
    	"info": {
    		"logoUrl": null,
    		"marketingUrl": null,
    		"privacyStatementUrl": null,
    		"supportUrl": null,
    		"termsOfServiceUrl": null
    	},
    	"keyCredentials": [],
    	"optionalClaims": {
    		"accessToken": [
    			{
    				"additionalProperties": [],
    				"essential": false,
    				"name": "auth_time",
    				"source": null
    			},
    			{
    				"additionalProperties": [],
    				"essential": false,
    				"name": "email",
    				"source": null
    			},
    			{
    				"additionalProperties": [],
    				"essential": false,
    				"name": "upn",
    				"source": null
    			}
    		],
    		"idToken": [],
    		"saml2Token": []
    	},
    	"parentalControlSettings": {
    		"countriesBlockedForMinors": [],
    		"legalAgeGroupRule": "Allow"
    	},
    	"passwordCredentials": [],
    	"publicClient": {
    		"redirectUris": []
    	},
    	"requiredResourceAccess": [
    		{
    			"resourceAppId": "00000003-0000-0000-c000-000000000000",
    			"resourceAccess": [
    				{
    					"id": "64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0",
    					"type": "Scope"
    				},
    				{
    					"id": "14dad69e-099b-42c9-810b-d002981feec1",
    					"type": "Scope"
    				}
    			]
    		}
    	],
    	"verifiedPublisher": {
    		"displayName": null,
    		"verifiedPublisherId": null,
    		"addedDateTime": null
    	},
    	"web": {
    		"homePageUrl": null,
    		"logoutUrl": null,
    		"redirectUris": [],
    		"implicitGrantSettings": {
    			"enableAccessTokenIssuance": true,
    			"enableIdTokenIssuance": true
    		},
    		"redirectUriSettings": []
    	},
    	"spa": {
    		"redirectUris": [
    			"http://localhost:19876/mcp/oauth/callback",
    			"http://localhost:5533/"
    		]
    	}
    }
  12. sergey-tihon commented on Mar 19, 2026

    @sergey-tihon

    @DavidParks8 sure. you can find 2 manifest below

    Main App Manifest (Backend/API/MCP) (main_app_manifest.json)
    {
    	"id": "99f0b3e2-393b-448b-8cda-94be0a7345fa",
    	"deletedDateTime": null,
    	"appId": "e0630cc0-d0c2-4512-beda-dec29dd569bc",
    	"applicationTemplateId": null,
    	"disabledByMicrosoftStatus": null,
    	"createdByAppId": null,
    	"createdDateTime": "2022-10-20T12:43:58Z",
    	"displayName": "myapp-prod",
    	"description": null,
    	"groupMembershipClaims": "ApplicationGroup",
    	"identifierUris": [
    		"https://app.example.com/mcp",
    		"api://app.example.com/e0630cc0-d0c2-4512-beda-dec29dd569bc",
    		"api://cloud.app.example.com/e0630cc0-d0c2-4512-beda-dec29dd569bc",
    		"api://e0630cc0-d0c2-4512-beda-dec29dd569bc",
    		"api://ui.app.example.com/e0630cc0-d0c2-4512-beda-dec29dd569bc"
    	],
    	"isDeviceOnlyAuthSupported": false,
    	"isFallbackPublicClient": false,
    	"nativeAuthenticationApisEnabled": null,
    	"notes": null,
    	"publisherDomain": "CONTOSO.onmicrosoft.com",
    	"serviceManagementReference": null,
    	"signInAudience": "AzureADMyOrg",
    	"tags": [],
    	"tokenEncryptionKeyId": null,
    	"samlMetadataUrl": null,
    	"defaultRedirectUri": null,
    	"certification": null,
    	"servicePrincipalLockConfiguration": null,
    	"requestSignatureVerification": null,
    	"addIns": [],
    	"api": {
    		"acceptMappedClaims": false,
    		"knownClientApplications": [],
    		"requestedAccessTokenVersion": 2,
    		"oauth2PermissionScopes": [
    			{
    				"adminConsentDescription": "Use MCP Server extra tools",
    				"adminConsentDisplayName": "Use MCP Server extra tools",
    				"id": "4bc15a96-b613-4607-9ac1-51f05d894320",
    				"isEnabled": true,
    				"type": "User",
    				"userConsentDescription": "Use MCP Server extra tools",
    				"userConsentDisplayName": "Use MCP Server extra tools",
    				"value": "User.McpPlus"
    			},
    			{
    				"adminConsentDescription": "Use MCP Server",
    				"adminConsentDisplayName": "Use MCP Server",
    				"id": "29450ca4-66cf-42c8-84d4-f87e92b2fb55",
    				"isEnabled": true,
    				"type": "User",
    				"userConsentDescription": "Use MCP Server",
    				"userConsentDisplayName": "Use MCP Server",
    				"value": "User.Mcp"
    			},
    			{
    				"adminConsentDescription": "Access to file metadata (download url, preview url, project code, contacts and etc)",
    				"adminConsentDisplayName": "Access documents metadata",
    				"id": "e56a56ab-01ac-4a9b-b229-762da01ab3a5",
    				"isEnabled": true,
    				"type": "User",
    				"userConsentDescription": "Access to file metadata (download url, preview url, project code, contacts and etc)",
    				"userConsentDisplayName": "Access documents metadata",
    				"value": "Files.Details"
    			},
    			{
    				"adminConsentDescription": "Search all documents",
    				"adminConsentDisplayName": "Search all documents",
    				"id": "00a5dd9b-9def-487d-880a-9e25d056ef46",
    				"isEnabled": true,
    				"type": "User",
    				"userConsentDescription": "Search all documents",
    				"userConsentDisplayName": "Search all documents",
    				"value": "Files.Search"
    			},
    			{
    				"adminConsentDescription": "Use experimental tools",
    				"adminConsentDisplayName": "Use experimental tools",
    				"id": "f48f23e1-d087-44e0-b69e-b27bf035e282",
    				"isEnabled": true,
    				"type": "User",
    				"userConsentDescription": "Use experimental tools",
    				"userConsentDisplayName": "Use experimental tools",
    				"value": "Experimental.Tools"
    			},
    			{
    				"adminConsentDescription": "Allows the app to read the signed-in user's profile.",
    				"adminConsentDisplayName": "Read user profile",
    				"id": "1df87b64-f079-4893-8559-ddca2ffec997",
    				"isEnabled": true,
    				"type": "User",
    				"userConsentDescription": "Allows the app to read your profile.",
    				"userConsentDisplayName": "Read your profile",
    				"value": "User.Read"
    			}
    		],
    		"preAuthorizedApplications": [
    			{
    				"appId": "04b07795-8ddb-461a-bbee-02f9e1bf7b46",
    				"delegatedPermissionIds": [
    					"29450ca4-66cf-42c8-84d4-f87e92b2fb55",
    					"4bc15a96-b613-4607-9ac1-51f05d894320",
    					"e56a56ab-01ac-4a9b-b229-762da01ab3a5"
    				]
    			},
    			{
    				"appId": "a8ade976-1dab-4375-9583-e7d010c2b764",
    				"delegatedPermissionIds": [
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"1df87b64-f079-4893-8559-ddca2ffec997",
    					"29450ca4-66cf-42c8-84d4-f87e92b2fb55",
    					"e56a56ab-01ac-4a9b-b229-762da01ab3a5"
    				]
    			},
    			{
    				"appId": "ba9f306a-e8fa-4d3f-8b3c-952d2fe72378",
    				"delegatedPermissionIds": [
    					"4bc15a96-b613-4607-9ac1-51f05d894320",
    					"29450ca4-66cf-42c8-84d4-f87e92b2fb55"
    				]
    			},
    			{
    				"appId": "a148733f-13d0-490c-be20-c627d7f49910",
    				"delegatedPermissionIds": [
    					"29450ca4-66cf-42c8-84d4-f87e92b2fb55"
    				]
    			},
    			{
    				"appId": "22cf1b3f-2609-494a-8736-98dd32ac392f",
    				"delegatedPermissionIds": [
    					"29450ca4-66cf-42c8-84d4-f87e92b2fb55",
    					"4bc15a96-b613-4607-9ac1-51f05d894320"
    				]
    			},
    			{
    				"appId": "61d9c2a8-2065-47c7-b93c-d3f544cf9d26",
    				"delegatedPermissionIds": [
    					"29450ca4-66cf-42c8-84d4-f87e92b2fb55",
    					"4bc15a96-b613-4607-9ac1-51f05d894320"
    				]
    			},
    			{
    				"appId": "aebc6443-996d-45c2-90f0-388ff96faa56",
    				"delegatedPermissionIds": [
    					"29450ca4-66cf-42c8-84d4-f87e92b2fb55",
    					"4bc15a96-b613-4607-9ac1-51f05d894320"
    				]
    			},
    			{
    				"appId": "c1e199f6-ae8d-40a3-9c0c-b921e1af6474",
    				"delegatedPermissionIds": [
    					"29450ca4-66cf-42c8-84d4-f87e92b2fb55",
    					"4bc15a96-b613-4607-9ac1-51f05d894320"
    				]
    			},
    			{
    				"appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c",
    				"delegatedPermissionIds": [
    					"1df87b64-f079-4893-8559-ddca2ffec997",
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"e56a56ab-01ac-4a9b-b229-762da01ab3a5"
    				]
    			},
    			{
    				"appId": "ea5a67f6-b6f3-4338-b240-c655ddc3cc8e",
    				"delegatedPermissionIds": [
    					"1df87b64-f079-4893-8559-ddca2ffec997",
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"e56a56ab-01ac-4a9b-b229-762da01ab3a5"
    				]
    			},
    			{
    				"appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
    				"delegatedPermissionIds": [
    					"1df87b64-f079-4893-8559-ddca2ffec997",
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"e56a56ab-01ac-4a9b-b229-762da01ab3a5"
    				]
    			},
    			{
    				"appId": "93d53678-613d-4013-afc1-62e9e444a0a5",
    				"delegatedPermissionIds": [
    					"1df87b64-f079-4893-8559-ddca2ffec997",
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"e56a56ab-01ac-4a9b-b229-762da01ab3a5"
    				]
    			},
    			{
    				"appId": "39207959-1658-49d1-b164-d3a2bc17b6a5",
    				"delegatedPermissionIds": [
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"1df87b64-f079-4893-8559-ddca2ffec997"
    				]
    			},
    			{
    				"appId": "bb9415ea-4b3c-4e1b-af38-025144276196",
    				"delegatedPermissionIds": [
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"1df87b64-f079-4893-8559-ddca2ffec997"
    				]
    			},
    			{
    				"appId": "303026a5-4e03-488b-8ab8-d7d013f7e0c7",
    				"delegatedPermissionIds": [
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"e56a56ab-01ac-4a9b-b229-762da01ab3a5",
    					"1df87b64-f079-4893-8559-ddca2ffec997"
    				]
    			},
    			{
    				"appId": "2bfda454-7707-4bb8-a51a-dcd4504252db",
    				"delegatedPermissionIds": [
    					"1df87b64-f079-4893-8559-ddca2ffec997",
    					"f48f23e1-d087-44e0-b69e-b27bf035e282",
    					"00a5dd9b-9def-487d-880a-9e25d056ef46",
    					"e56a56ab-01ac-4a9b-b229-762da01ab3a5"
    				]
    			}
    		]
    	},
    	"appRoles": [
    		{
    			"allowedMemberTypes": [
    				"User"
    			],
    			"description": "Has access to administrative features",
    			"displayName": "Administrator",
    			"id": "cf6a71a6-df77-4578-b5de-e6482f3f19f6",
    			"isEnabled": true,
    			"origin": "Application",
    			"value": "Admin"
    		},
    		{
    			"allowedMemberTypes": [
    				"User"
    			],
    			"description": "Can moderate content",
    			"displayName": "Content Moderator",
    			"id": "44a8acfc-1013-4144-8812-b97f558336d6",
    			"isEnabled": true,
    			"origin": "Application",
    			"value": "Moderator"
    		},
    		{
    			"allowedMemberTypes": [
    				"User"
    			],
    			"description": "Has access to Swagger UI",
    			"displayName": "Swagger UI",
    			"id": "67b8a138-8948-44f0-b07e-7c7ef2766c6e",
    			"isEnabled": true,
    			"origin": "Application",
    			"value": "Swagger"
    		},
    		{
    			"allowedMemberTypes": [
    				"User"
    			],
    			"description": "Has access to early access features (private beta)",
    			"displayName": "Early Access",
    			"id": "606f7969-e8c1-4f09-861d-3b94a65f7418",
    			"isEnabled": true,
    			"origin": "Application",
    			"value": "EarlyAccess"
    		}
    	],
    	"info": {
    		"logoUrl": null,
    		"marketingUrl": null,
    		"privacyStatementUrl": null,
    		"supportUrl": null,
    		"termsOfServiceUrl": null
    	},
    	"keyCredentials": [
    		{
    			"customKeyIdentifier": "AABBCCDD00112233445566778899AABBCCDDEEFF",
    			"displayName": "CN=example-app-prod",
    			"endDateTime": "2027-03-17T11:56:28Z",
    			"key": null,
    			"keyId": "3d6c6208-63c3-b767-827e-0101fc361d1e",
    			"startDateTime": "2025-03-17T11:46:28Z",
    			"type": "AsymmetricX509Cert",
    			"usage": "Verify"
    		},
    		{
    			"customKeyIdentifier": "AABBCCDD00112233445566778899AABBCCDDEEFF",
    			"displayName": "CN=example-app-prod",
    			"endDateTime": "2027-03-17T11:56:28Z",
    			"key": null,
    			"keyId": "52c735f8-9015-46c7-bbe4-c218b98b5e9a",
    			"startDateTime": "2025-03-17T11:46:28Z",
    			"type": "AsymmetricX509Cert",
    			"usage": "Verify"
    		}
    	],
    	"optionalClaims": {
    		"accessToken": [],
    		"idToken": [],
    		"saml2Token": []
    	},
    	"parentalControlSettings": {
    		"countriesBlockedForMinors": [],
    		"legalAgeGroupRule": "Allow"
    	},
    	"passwordCredentials": [],
    	"publicClient": {
    		"redirectUris": []
    	},
    	"requiredResourceAccess": [
    		{
    			"resourceAppId": "00000003-0000-0000-c000-000000000000",
    			"resourceAccess": [
    				{
    					"id": "df85f4d6-205c-4ac5-a5ea-6bf408dba283",
    					"type": "Scope"
    				},
    				{
    					"id": "5c28f0bf-8a70-41f1-8ab2-9032436ddb65",
    					"type": "Scope"
    				},
    				{
    					"id": "14dad69e-099b-42c9-810b-d002981feec1",
    					"type": "Scope"
    				},
    				{
    					"id": "205e70e5-aba6-4c52-a976-6d2d46c48043",
    					"type": "Scope"
    				},
    				{
    					"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
    					"type": "Scope"
    				},
    				{
    					"id": "37f7f235-527c-4136-accd-4a02d197296e",
    					"type": "Scope"
    				},
    				{
    					"id": "7ab1d382-f21e-4acd-a863-ba3e13f7da61",
    					"type": "Role"
    				},
    				{
    					"id": "883ea226-0bf2-4a8f-9f9d-92c9162a727d",
    					"type": "Role"
    				},
    				{
    					"id": "f12eb8d6-28e3-46e6-b2c0-b7e4dc69fc95",
    					"type": "Role"
    				},
    				{
    					"id": "df021288-bdef-4463-88db-98f22de89214",
    					"type": "Role"
    				}
    			]
    		},
    		{
    			"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
    			"resourceAccess": [
    				{
    					"id": "4e0d77b0-96ba-4398-af14-3baa780278f4",
    					"type": "Scope"
    				},
    				{
    					"id": "c8e3537c-ec53-43b9-bed3-b2bd3617ae97",
    					"type": "Role"
    				},
    				{
    					"id": "20d37865-089c-4dee-8c41-6967602d4ac8",
    					"type": "Role"
    				}
    			]
    		}
    	],
    	"verifiedPublisher": {
    		"displayName": null,
    		"verifiedPublisherId": null,
    		"addedDateTime": null
    	},
    	"web": {
    		"homePageUrl": null,
    		"logoutUrl": "https://ui.app.example.com/signout-oidc",
    		"redirectUris": [
    			"https://app.example.com/signin-oidc",
    			"https://ui.app.example.com/signin-oidc",
    			"https://cloud.app.example.com/signin-oidc"
    		],
    		"implicitGrantSettings": {
    			"enableAccessTokenIssuance": true,
    			"enableIdTokenIssuance": true
    		},
    		"redirectUriSettings": [
    			{
    				"uri": "https://app.example.com/signin-oidc",
    				"index": null
    			},
    			{
    				"uri": "https://ui.app.example.com/signin-oidc",
    				"index": null
    			},
    			{
    				"uri": "https://cloud.app.example.com/signin-oidc",
    				"index": null
    			}
    		]
    	},
    	"spa": {
    		"redirectUris": []
    	}
    }
    Connector App Manifest (User auth, token exchange for mcp) (connector_app_manifest.json)
    {
    	"id": "47b1e109-8cc1-4a83-a52c-c9846fb2a95f",
    	"deletedDateTime": null,
    	"appId": "c1e199f6-ae8d-40a3-9c0c-b921e1af6474",
    	"applicationTemplateId": null,
    	"disabledByMicrosoftStatus": null,
    	"createdByAppId": "18ed3507-a475-4ccb-b669-d66bc9f2a36e",
    	"createdDateTime": "2025-08-20T16:49:27Z",
    	"displayName": "myapp-prod-connector",
    	"description": null,
    	"groupMembershipClaims": null,
    	"identifierUris": [
    		"api://botid-c1e199f6-ae8d-40a3-9c0c-b921e1af6474"
    	],
    	"isDeviceOnlyAuthSupported": null,
    	"isFallbackPublicClient": false,
    	"nativeAuthenticationApisEnabled": null,
    	"notes": null,
    	"publisherDomain": "CONTOSO.onmicrosoft.com",
    	"serviceManagementReference": null,
    	"signInAudience": "AzureADMyOrg",
    	"tags": [],
    	"tokenEncryptionKeyId": null,
    	"samlMetadataUrl": null,
    	"defaultRedirectUri": null,
    	"certification": null,
    	"optionalClaims": null,
    	"requestSignatureVerification": null,
    	"addIns": [],
    	"api": {
    		"acceptMappedClaims": null,
    		"knownClientApplications": [],
    		"requestedAccessTokenVersion": 2,
    		"oauth2PermissionScopes": [
    			{
    				"adminConsentDescription": "Allow to sign in",
    				"adminConsentDisplayName": "Allow to sign in",
    				"id": "9ab1995f-79d7-4dd1-8144-02eb497d8ef0",
    				"isEnabled": true,
    				"type": "User",
    				"userConsentDescription": null,
    				"userConsentDisplayName": null,
    				"value": "Placeholder"
    			}
    		],
    		"preAuthorizedApplications": [
    			{
    				"appId": "9f2fa2e2-b09e-4ac7-a64f-29ae31229703",
    				"delegatedPermissionIds": [
    					"9ab1995f-79d7-4dd1-8144-02eb497d8ef0"
    				]
    			},
    			{
    				"appId": "195b235f-d425-4665-ab41-4968f0fd63ee",
    				"delegatedPermissionIds": [
    					"9ab1995f-79d7-4dd1-8144-02eb497d8ef0"
    				]
    			},
    			{
    				"appId": "fe053c5f-3692-4f14-aef2-ee34fc081cae",
    				"delegatedPermissionIds": [
    					"9ab1995f-79d7-4dd1-8144-02eb497d8ef0"
    				]
    			},
    			{
    				"appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
    				"delegatedPermissionIds": [
    					"9ab1995f-79d7-4dd1-8144-02eb497d8ef0"
    				]
    			},
    			{
    				"appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346",
    				"delegatedPermissionIds": [
    					"9ab1995f-79d7-4dd1-8144-02eb497d8ef0"
    				]
    			}
    		]
    	},
    	"appRoles": [],
    	"info": {
    		"logoUrl": null,
    		"marketingUrl": null,
    		"privacyStatementUrl": null,
    		"supportUrl": null,
    		"termsOfServiceUrl": null
    	},
    	"keyCredentials": [],
    	"parentalControlSettings": {
    		"countriesBlockedForMinors": [],
    		"legalAgeGroupRule": "Allow"
    	},
    	"passwordCredentials": [
    	],
    	"publicClient": {
    		"redirectUris": []
    	},
    	"requiredResourceAccess": [
    		{
    			"resourceAppId": "e0630cc0-d0c2-4512-beda-dec29dd569bc",
    			"resourceAccess": [
    				{
    					"id": "4bc15a96-b613-4607-9ac1-51f05d894320",
    					"type": "Scope"
    				},
    				{
    					"id": "29450ca4-66cf-42c8-84d4-f87e92b2fb55",
    					"type": "Scope"
    				}
    			]
    		},
    		{
    			"resourceAppId": "00000003-0000-0000-c000-000000000000",
    			"resourceAccess": [
    				{
    					"id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182",
    					"type": "Scope"
    				},
    				{
    					"id": "37f7f235-527c-4136-accd-4a02d197296e",
    					"type": "Scope"
    				},
    				{
    					"id": "14dad69e-099b-42c9-810b-d002981feec1",
    					"type": "Scope"
    				}
    			]
    		}
    	],
    	"verifiedPublisher": {
    		"displayName": null,
    		"verifiedPublisherId": null,
    		"addedDateTime": null
    	},
    	"web": {
    		"homePageUrl": null,
    		"logoutUrl": null,
    		"redirectUris": [
    			"http://localhost:19876/mcp/oauth/callback",
    			"https://dev.partner.example.com/app/mcp-auth-callback",
    			"http://localhost:5173/mcp-auth-callback",
    			"https://stage.partner.example.com/app/mcp-auth-callback",
    			"https://next.partner.example.com/app/mcp-auth-callback",
    			"https://chat.lab.example.com/auth/toolset-signin",
    			"https://global.consent.azure-apim.net/redirect/example-20app-20connector-5f998f9c4d-28edd1166c07dd9d",
    			"https://europe.token.botframework.com/.auth/web/redirect",
    			"https://token.botframework.com/.auth/web/redirect",
    			"https://global.consent.azure-apim.net/redirect/example-2dapp-2dmcp-5ff4e77a5f9f7799e1-5f37921915678c3f1c"
    		],
    		"implicitGrantSettings": {
    			"enableAccessTokenIssuance": true,
    			"enableIdTokenIssuance": true
    		},
    		"redirectUriSettings": [
    			{
    				"uri": "http://localhost:19876/mcp/oauth/callback",
    				"index": null
    			},
    			{
    				"uri": "https://dev.partner.example.com/app/mcp-auth-callback",
    				"index": null
    			},
    			{
    				"uri": "http://localhost:5173/mcp-auth-callback",
    				"index": null
    			},
    			{
    				"uri": "https://stage.partner.example.com/app/mcp-auth-callback",
    				"index": null
    			},
    			{
    				"uri": "https://next.partner.example.com/app/mcp-auth-callback",
    				"index": null
    			},
    			{
    				"uri": "https://chat.lab.example.com/auth/toolset-signin",
    				"index": null
    			},
    			{
    				"uri": "https://global.consent.azure-apim.net/redirect/example-20app-20connector-5f998f9c4d-28edd1166c07dd9d",
    				"index": null
    			},
    			{
    				"uri": "https://europe.token.botframework.com/.auth/web/redirect",
    				"index": null
    			},
    			{
    				"uri": "https://token.botframework.com/.auth/web/redirect",
    				"index": null
    			},
    			{
    				"uri": "https://global.consent.azure-apim.net/redirect/example-2dapp-2dmcp-5ff4e77a5f9f7799e1-5f37921915678c3f1c",
    				"index": null
    			}
    		]
    	},
    	"servicePrincipalLockConfiguration": {
    		"isEnabled": true,
    		"allProperties": true,
    		"credentialsWithUsageVerify": true,
    		"credentialsWithUsageSign": true,
    		"identifierUris": false,
    		"tokenEncryptionKeyId": true
    	},
    	"spa": {
    		"redirectUris": []
    	}
    }
  13. DavidParks8 commented on Mar 20, 2026

    @DavidParks8

    @sergey-tihon there are a number of things that can be done to tighten up the security of that app manifest, but I'm going to focus specifically on the issue we are discussing. The resource parameter validation in the V2 endpoint of Entra sources the allowed values from the identifier URIs and not the redirect URIs. You would need to add the http uri that is being passed by opencode in the resource parameter to you identifier uris array.

    BTW, there is already a PR internally to relax the trailing /issue in resource parameter validation.

  14. sergey-tihon commented on Mar 27, 2026

    @sergey-tihon

    @DavidParks8 How long does such a fix usually take before we can test it?

    there are a number of things that can be done to tighten up the security of that app manifest,

    Thank you for taking a look at the manifest. I’d appreciate it if you could share your thoughts by email at sergey.tihon[at]gmail.com.

  15. sergey-tihon commented on Apr 1, 2026

    @sergey-tihon

    I’ve fixed my issue. Here is the solution that worked for me:

    • Change the resource name in.well-known/oauth-protected-resource from https://app.example.com/mcp/ to https://app.example.com/mcp (remove the trailing slash)
    • Add the new resource name to theidentifierUris list in the MCP server app manifest.
    • Create a new Entra ID application for public clients (e.g. OpenCode) with the PCKE flow enabled:

    The final version of the MCP configuration

    {
      "$schema": "https://opencode.ai/config.json",
      "mcp": {
        "entra-mcp": {
          "type": "remote",
          "url": "https://app.example.com/mcp",
          "oauth": {
            "clientId": "<client_id_of_public_client_app>"
          }
        }
      }
    }
    
  16. removed
    bugSomething isn't working
    on May 3, 2026
  17. sergey-tihon commented on May 4, 2026

    @sergey-tihon

    BTW, there is already a PR internally to relax the trailing /issue in resource parameter validation.

    hi, @DavidParks8 Has this been merged or released yet?

  18. rekram1-node commented on Jun 24, 2026

    @rekram1-node
    Collaborator

    Closing as resolved by the configuration documented in this thread. The working setup uses an exact protected-resource URI registered in Entra identifierUris, a public PKCE client, and the OpenCode loopback callback under Mobile and desktop applications. OpenCode intentionally retains RFC 8707 resource indicators; removing them, as proposed in #12321, would weaken audience binding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions