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))
+ );
});
}