From 2ce58d767d78356ac373f3b9056c00eb9ed3f001 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:22:55 +0200 Subject: [PATCH] Task/restrict restricted view and manage permission to MCP (#7783) * Restrict restricted view and manage permission to MCP * Update changelog --- CHANGELOG.md | 4 ++++ ...reate-or-update-access-dialog.component.ts | 5 +++++ .../create-or-update-access-dialog.html | 2 +- libs/common/src/lib/scopes.ts | 21 +++++++++++++++++-- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad6228c2a..56b2f2e35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Restricted the _Restricted view and manage_ permission of the access to share the portfolio to the Model Context Protocol (MCP) (experimental) + ### Fixed - Fixed the loading state of the accounts table on the accounts page diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index b0be5c0c5..8c2bb0353 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -6,6 +6,7 @@ import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Scope, + canGrantRestrictedWriteAccess, getAccessLevel, getScopesOfAccess, getScopesOfAccessLevel, @@ -125,6 +126,10 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit { return this.hasExperimentalFeatures && this.hasPermissionToEnableMcp; } + public get canGrantRestrictedWriteAccess() { + return canGrantRestrictedWriteAccess({ type: this.accessType }); + } + public get canGrantWriteAccess() { return this.hasExperimentalFeatures; } diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html index 89893e570..21c0cd3a8 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -82,7 +82,7 @@ - @if (!isPublicAccess) { + @if (canGrantRestrictedWriteAccess) { = { MCP: [...SCOPES_OF_READ_RESTRICTED_ACCESS, scopes.activityCreate], @@ -72,6 +74,14 @@ const SCOPES_OF_TYPE: Record = { PUBLIC: SCOPES_OF_PUBLIC_ACCESS }; +/** + * Access types which combine a write scope with the restricted read access, + * because their tools change data without exposing the monetary values + */ +export function canGrantRestrictedWriteAccess({ type }: { type: AccessType }) { + return type === 'MCP'; +} + /** * Access level which the scopes of an access grant */ @@ -96,9 +106,16 @@ export function getScopesOfAccess({ }): string[] { const scopesToEvaluate = scopesOfAccess ?? []; + const permitsWriteAccess = + canGrantRestrictedWriteAccess({ type }) || + hasScope(scopesToEvaluate, scopes.portfolioReadValues); + // An unknown scope is dropped return SCOPES_OF_TYPE[type].filter((scope) => { - return scopesToEvaluate.includes(scope); + return ( + scopesToEvaluate.includes(scope) && + (permitsWriteAccess || !SCOPES_OF_WRITE_ACCESS.includes(scope)) + ); }); }