From 57aa11bf83523b8d1073608bebb2332558a51df2 Mon Sep 17 00:00:00 2001 From: Dany Khalife Date: Tue, 17 Mar 2026 22:39:31 -0700 Subject: [PATCH 1/3] add protected resource metadata --- mcpserver/Program.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mcpserver/Program.cs b/mcpserver/Program.cs index 5ac5a95a..e1d1eaa3 100644 --- a/mcpserver/Program.cs +++ b/mcpserver/Program.cs @@ -8,6 +8,7 @@ var tenantId = Environment.GetEnvironmentVariable("TW_ENTRA_TENANT_ID") ?? ""; var audience = Environment.GetEnvironmentVariable("TW_ENTRA_AUDIENCE") ?? ""; +var mcpResource = Environment.GetEnvironmentVariable("TW_MCP_RESOURCE") ?? ""; if (string.IsNullOrWhiteSpace(tenantId)) throw new InvalidOperationException("TW_ENTRA_TENANT_ID must be set to a valid Entra tenant ID."); @@ -15,6 +16,9 @@ if (string.IsNullOrWhiteSpace(audience)) throw new InvalidOperationException("TW_ENTRA_AUDIENCE must be set to a valid Entra audience."); +if (string.IsNullOrWhiteSpace(mcpResource)) + throw new InvalidOperationException("TW_MCP_RESOURCE must be set to the canonical URL of this MCP server (e.g. https://mcp.example.com)."); + var authority = Environment.GetEnvironmentVariable("TW_ENTRA_ISSUER") ?? $"https://login.microsoftonline.com/{tenantId}/v2.0"; var apiUrl = Environment.GetEnvironmentVariable("TW_API_URL") ?? "http://localhost:2021"; @@ -50,7 +54,16 @@ { options.ResourceMetadata = new() { + Resource = mcpResource, AuthorizationServers = { authority }, + ScopesSupported = { + $"{audience}/user:read", + $"{audience}/label:read", + $"{audience}/label:write", + $"{audience}/task:read", + $"{audience}/task:write", + }, + BearerMethodsSupported = { "header" }, }; }); From 10ab65466e33bc3bfc04915d07fe877555df63ef Mon Sep 17 00:00:00 2001 From: Dany Khalife Date: Wed, 18 Mar 2026 17:30:45 -0700 Subject: [PATCH 2/3] update scopes --- .agents/features/api-tokens.md | 2 +- mcpserver/Program.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.agents/features/api-tokens.md b/.agents/features/api-tokens.md index 06b7ad99..9914bb27 100644 --- a/.agents/features/api-tokens.md +++ b/.agents/features/api-tokens.md @@ -5,7 +5,7 @@ Fine-grained access tokens that allow external integrations to interact with Tas ## Capabilities - Create named API tokens with an expiration date -- Scoped permissions: task:read, task:write, label:read, label:write, dav:read, dav:write, user:read, user:write, token:write +- Scoped permissions: Tasks.Read, Tasks.Write, Labels.Read, Labels.Write, Dav.Read, Dav.Write, User.Read, User.Write, Tokens.Write - Write scopes automatically include their corresponding read scope - Tokens are validated the same way as JWT sessions but carry scope restrictions - List and delete existing tokens from the settings UI diff --git a/mcpserver/Program.cs b/mcpserver/Program.cs index e1d1eaa3..6b0ac32c 100644 --- a/mcpserver/Program.cs +++ b/mcpserver/Program.cs @@ -57,11 +57,11 @@ Resource = mcpResource, AuthorizationServers = { authority }, ScopesSupported = { - $"{audience}/user:read", - $"{audience}/label:read", - $"{audience}/label:write", - $"{audience}/task:read", - $"{audience}/task:write", + $"{audience}/User.Read", + $"{audience}/Labels.Read", + $"{audience}/Labels.Write", + $"{audience}/Tasks.Read", + $"{audience}/Tasks.Write", }, BearerMethodsSupported = { "header" }, }; From 9a44a9431d810f30267250790c591066dd70d4f2 Mon Sep 17 00:00:00 2001 From: Dany Khalife Date: Wed, 18 Mar 2026 17:41:25 -0700 Subject: [PATCH 3/3] update go tokens --- apiserver/internal/models/user.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apiserver/internal/models/user.go b/apiserver/internal/models/user.go index 03261294..21b43d33 100644 --- a/apiserver/internal/models/user.go +++ b/apiserver/internal/models/user.go @@ -32,14 +32,14 @@ type SignedInIdentity struct { type ApiTokenScope string const ( - ApiTokenScopeTaskRead ApiTokenScope = "task:read" - ApiTokenScopeTaskWrite ApiTokenScope = "task:write" - ApiTokenScopeLabelRead ApiTokenScope = "label:read" - ApiTokenScopeLabelWrite ApiTokenScope = "label:write" - ApiTokenScopeUserRead ApiTokenScope = "user:read" - ApiTokenScopeUserWrite ApiTokenScope = "user:write" - ApiTokenScopeDavRead ApiTokenScope = "dav:read" - ApiTokenScopeDavWrite ApiTokenScope = "dav:write" + ApiTokenScopeTaskRead ApiTokenScope = "Tasks.Read" + ApiTokenScopeTaskWrite ApiTokenScope = "Tasks.Write" + ApiTokenScopeLabelRead ApiTokenScope = "Labels.Read" + ApiTokenScopeLabelWrite ApiTokenScope = "Labels.Write" + ApiTokenScopeUserRead ApiTokenScope = "User.Read" + ApiTokenScopeUserWrite ApiTokenScope = "User.Write" + ApiTokenScopeDavRead ApiTokenScope = "Dav.Read" + ApiTokenScopeDavWrite ApiTokenScope = "Dav.Write" ) func AllUserScopes() []ApiTokenScope {