Browse Source

Improve MCP access

pull/8112/head
Thomas Kaul 5 days ago
parent
commit
47d8def4ab
  1. 18
      apps/api/src/app/endpoints/mcp/mcp.controller.ts
  2. 16
      apps/api/src/app/endpoints/mcp/mcp.module.ts
  3. 16
      apps/api/src/services/portfolio-table/portfolio-table.service.spec.ts
  4. 16
      apps/api/src/services/portfolio-table/portfolio-table.service.ts

18
apps/api/src/app/endpoints/mcp/mcp.controller.ts

@ -38,7 +38,7 @@ export class GhostfolioMcpController {
', '
)}. If the access reads the monetary values, these columns are given in addition: ${PortfolioTableService.getAccountsTableValueColumnNames().join(
', '
)}. The allocation in percentage is relative to the accounts of the result, hence the parameters change it. The parameters change the value in base currency as well, with the holding parameter it is the value of the holding in the account without the cash balance. The balance is always the full cash balance of the account.`,
)}. The allocation in percentage is relative to the accounts of the result, hence the parameters change it. The parameters change the value in base currency as well. With the holding parameter, it is the value of the holding in the account without the cash balance. Without the holding parameter, it includes the full cash balance of the account, also with the assetClasses parameter. The balance is always the full cash balance of the account in the currency of the account.`,
name: 'get-accounts',
parameters: GET_ACCOUNTS_PARAMETERS
})
@ -50,7 +50,7 @@ export class GhostfolioMcpController {
return this.mcpService.getAccounts({
...parameters,
userId,
withValues: hasScope(impersonationScopes, scopes.portfolioReadValues)
withValues: this.hasScopeToReadValues(impersonationScopes)
});
}
@ -65,7 +65,7 @@ export class GhostfolioMcpController {
', '
)}. If the access reads the monetary values, these columns are given in addition: ${PortfolioTableService.getActivitiesTableValueColumnNames().join(
', '
)}. At most ${MCP_MAX_ACTIVITIES} activities are given per call, hence narrow the result with the parameters or get the further activities with the skip parameter.`,
)}. The unit price and the fee are in the currency of the activity. At most ${MCP_MAX_ACTIVITIES} activities are given per call, hence narrow the result with the parameters or get the further activities with the skip parameter.`,
name: 'get-activities',
parameters: GET_ACTIVITIES_PARAMETERS
})
@ -78,7 +78,7 @@ export class GhostfolioMcpController {
...parameters,
userId,
userCurrency: userSettings.baseCurrency,
withValues: hasScope(impersonationScopes, scopes.portfolioReadValues)
withValues: this.hasScopeToReadValues(impersonationScopes)
});
}
@ -93,7 +93,7 @@ export class GhostfolioMcpController {
', '
)}. If the access reads the monetary values, these columns are given in addition: ${PortfolioTableService.getPerformanceTableValueColumnNames().join(
', '
)}. The asset performance excludes the effect of the exchange rates, the currency performance is that effect, and the net performance is the sum of both in the base currency of the user. Each performance is the return on average investment (ROAI) and includes the dividends (total return). The accounts and the activities which are excluded from analysis are not part of the performance. The parameters limit the performance to the holdings of the accounts, of the asset classes or of the asset profile.`,
)}. The value in base currency is the value at the end of the date range. The asset performance excludes the effect of the exchange rates, the currency performance is that effect, and the net performance is the sum of both in the base currency of the user. Each performance is the return on average investment (ROAI) and includes the dividends (total return). The accounts and the activities which are excluded from analysis are not part of the performance. The parameters limit the performance to the holdings of the accounts, of the asset classes or of the asset profile.`,
name: 'get-performance',
parameters: GET_PERFORMANCE_PARAMETERS
})
@ -111,7 +111,7 @@ export class GhostfolioMcpController {
userId,
withAssetPerformanceInBaseCurrency:
userSubscription?.type !== SubscriptionType.Basic,
withValues: hasScope(impersonationScopes, scopes.portfolioReadValues)
withValues: this.hasScopeToReadValues(impersonationScopes)
});
}
@ -135,7 +135,7 @@ export class GhostfolioMcpController {
) {
return this.mcpService.getPortfolio({
userId,
withValues: hasScope(impersonationScopes, scopes.portfolioReadValues)
withValues: this.hasScopeToReadValues(impersonationScopes)
});
}
@ -192,4 +192,8 @@ export class GhostfolioMcpController {
) {
return this.mcpService.searchAssetProfiles({ ...parameters, userId });
}
private hasScopeToReadValues(impersonationScopes: string[]) {
return hasScope(impersonationScopes, scopes.portfolioReadValues);
}
}

16
apps/api/src/app/endpoints/mcp/mcp.module.ts

@ -7,6 +7,7 @@ import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PortfolioTableModule } from '@ghostfolio/api/services/portfolio-table/portfolio-table.module';
import { PortfolioTableService } from '@ghostfolio/api/services/portfolio-table/portfolio-table.service';
import { MCP_ENDPOINT } from '@ghostfolio/common/config';
import { Module } from '@nestjs/common';
@ -37,9 +38,20 @@ import { McpService } from './mcp.service';
useFactory: (configurationService: ConfigurationService) => {
const { hostname } = new URL(configurationService.get('ROOT_URL'));
const instructions = [
'Ghostfolio is a wealth management application. The tools read the portfolio and the watchlist of the user who granted the access and import activities into the portfolio. They give the quantities and the monetary values only if the access has the permission "View" or "View and manage", otherwise no quantity and no monetary value (except the unit price of an activity). Only the tools which the permission of the access covers are listed. To import activities, the access needs the permission "Restricted view and manage" or "View and manage".'
];
if (configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
instructions.push(
`With the Basic subscription, the tool get-performance does not give these columns: ${PortfolioTableService.getAssetPerformanceInBaseCurrencyColumnNames().join(
', '
)}.`
);
}
return new McpStrategy({
instructions:
'Ghostfolio is a wealth management application. The tools read the portfolio and the watchlist of the user who granted the access and import activities into the portfolio. They give the quantities and the monetary values only if the access has the permission "View" or "View and manage", otherwise no quantity and no monetary value (except the unit price of an activity). Only the tools which the permission of the access covers are listed. To import activities, the access needs the permission "Restricted view and manage" or "View and manage".',
instructions: instructions.join(' '),
name: 'ghostfolio',
resolveUser: getMcpUserOfBearerToken,
title: 'Ghostfolio',

16
apps/api/src/services/portfolio-table/portfolio-table.service.spec.ts

@ -140,7 +140,6 @@ function createHolding({
},
dateOfFirstActivity: new Date('2024-01-01'),
grossPerformance: 100,
marketPrice: 400,
netPerformance: 90,
quantity: 5,
valueInBaseCurrency: 2000
@ -303,6 +302,17 @@ describe('PortfolioTableService', () => {
});
});
describe('getAssetPerformanceInBaseCurrencyColumnNames', () => {
it('gives the value columns of the performance which give the asset performance', () => {
expect(
PortfolioTableService.getAssetPerformanceInBaseCurrencyColumnNames()
).toEqual([
'Asset Performance in Base Currency',
'Currency Performance in Base Currency'
]);
});
});
describe('getHoldingsTableColumnNames', () => {
it('gives no column with a monetary value', () => {
expect(PortfolioTableService.getHoldingsTableColumnNames()).toEqual([
@ -343,7 +353,7 @@ describe('PortfolioTableService', () => {
expect(
PortfolioTableService.getPerformanceTableValueColumnNames()
).toEqual([
'Current Value in Base Currency',
'Value in Base Currency',
'Asset Performance in Base Currency',
'Currency Performance in Base Currency',
'Net Performance in Base Currency'
@ -699,7 +709,7 @@ describe('PortfolioTableService', () => {
}
});
it('gives the current value and the performance in base currency with the values', async () => {
it('gives the value and the performance in base currency with the values', async () => {
const result = await getPerformanceTable(
{},
{ withAssetPerformanceInBaseCurrency: true, withValues: true }

16
apps/api/src/services/portfolio-table/portfolio-table.service.ts

@ -37,6 +37,11 @@ const ASSET_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAME =
const CURRENCY_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAME =
'Currency Performance in Base Currency';
const ASSET_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAMES = [
ASSET_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAME,
CURRENCY_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAME
];
const DATA_SOURCE_COLUMN_NAME = 'Data Source';
function getDataSourceColumnDefinition<T>(
@ -340,7 +345,7 @@ export class PortfolioTableService {
getValue: ({ currentValueInBaseCurrency }) => {
return getAmount(currentValueInBaseCurrency);
},
name: 'Current Value in Base Currency'
name: 'Value in Base Currency'
},
{
align: 'right',
@ -459,6 +464,10 @@ export class PortfolioTableService {
);
}
public static getAssetPerformanceInBaseCurrencyColumnNames() {
return [...ASSET_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAMES];
}
public static getHoldingsTableColumnNames() {
return PortfolioTableService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.map(
({ name }) => {
@ -693,10 +702,7 @@ export class PortfolioTableService {
}).filter(({ name }) => {
return (
withAssetPerformanceInBaseCurrency ||
![
ASSET_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAME,
CURRENCY_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAME
].includes(name)
!ASSET_PERFORMANCE_IN_BASE_CURRENCY_COLUMN_NAMES.includes(name)
);
}),
rows: [performance]

Loading…
Cancel
Save