Browse Source

Add tool to update account balance to MCP server

pull/8178/head
Thomas Kaul 1 day ago
parent
commit
88ed206acd
  1. 23
      apps/api/src/app/endpoints/mcp/mcp.schemas.spec.ts
  2. 26
      apps/api/src/app/endpoints/mcp/mcp.schemas.ts
  3. 5
      apps/api/src/app/endpoints/mcp/mcp.service.spec.ts
  4. 4
      apps/api/src/app/endpoints/mcp/mcp.service.ts

23
apps/api/src/app/endpoints/mcp/mcp.schemas.spec.ts

@ -7,10 +7,8 @@ import {
SEARCH_QUERY_MINIMUM_LENGTH, SEARCH_QUERY_MINIMUM_LENGTH,
SYMBOL_MAXIMUM_LENGTH SYMBOL_MAXIMUM_LENGTH
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import { addDays, format } from 'date-fns';
import { import {
GET_ACCOUNTS_PARAMETERS, GET_ACCOUNTS_PARAMETERS,
@ -317,6 +315,14 @@ describe('SEARCH_ASSET_PROFILES_PARAMETERS', () => {
}); });
describe('UPDATE_ACCOUNT_BALANCE_PARAMETERS', () => { describe('UPDATE_ACCOUNT_BALANCE_PARAMETERS', () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(new Date('2026-09-03T23:30:00.000Z'));
});
afterAll(() => {
jest.useRealTimers();
});
function parse(parameters: Record<string, unknown>) { function parse(parameters: Record<string, unknown>) {
return UPDATE_ACCOUNT_BALANCE_PARAMETERS.safeParse({ return UPDATE_ACCOUNT_BALANCE_PARAMETERS.safeParse({
accountId: 'account-id', accountId: 'account-id',
@ -327,7 +333,7 @@ describe('UPDATE_ACCOUNT_BALANCE_PARAMETERS', () => {
} }
it('Accepts a cash balance of today', () => { it('Accepts a cash balance of today', () => {
expect(parse({ date: format(new Date(), DATE_FORMAT) })).toBe(true); expect(parse({ date: '2026-09-03' })).toBe(true);
}); });
it('Accepts a negative cash balance', () => { it('Accepts a negative cash balance', () => {
@ -339,12 +345,15 @@ describe('UPDATE_ACCOUNT_BALANCE_PARAMETERS', () => {
}); });
it('Refuses a date at or before the epoch', () => { it('Refuses a date at or before the epoch', () => {
expect(parse({ date: '0000-01-01' })).toBe(false); expect(parse({ date: '1969-12-31' })).toBe(false);
expect(parse({ date: '1970-01-01' })).toBe(false);
});
it('Refuses a date and time', () => {
expect(parse({ date: '2024-01-01T00:30:00+02:00' })).toBe(false);
}); });
it('Refuses a date in the future', () => { it('Refuses a date in the future', () => {
expect(parse({ date: format(addDays(new Date(), 2), DATE_FORMAT) })).toBe( expect(parse({ date: '2026-09-05' })).toBe(false);
false
);
}); });
}); });

26
apps/api/src/app/endpoints/mcp/mcp.schemas.ts

@ -11,14 +11,12 @@ import {
SYMBOL_MAXIMUM_LENGTH SYMBOL_MAXIMUM_LENGTH
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { import {
isAccountBalanceInFuture, isValidAccountBalanceDate,
isValidCurrencyCode, isValidCurrencyCode,
isValidDateAfter1970, isValidDateAfter1970
resetHours
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { AssetClass, DataSource, Type as ActivityType } from '@prisma/client'; import { AssetClass, DataSource, Type as ActivityType } from '@prisma/client';
import { parseISO } from 'date-fns';
import { z } from 'zod'; import { z } from 'zod';
const DATA_SOURCE_PARAMETER_ERROR = `Invalid option: expected one of ${Object.values(DataSource).join('|')} or a data source as given by a tool`; const DATA_SOURCE_PARAMETER_ERROR = `Invalid option: expected one of ${Object.values(DataSource).join('|')} or a data source as given by a tool`;
@ -188,14 +186,16 @@ export const UPDATE_ACCOUNT_BALANCE_PARAMETERS = z.object({
balance: z balance: z
.number() .number()
.describe('The cash balance of the account in the currency of the account'), .describe('The cash balance of the account in the currency of the account'),
date: z date: z.iso
.string() .date({
.refine(isValidDateAfter1970) abort: true,
.refine( error: 'The date must be an ISO 8601 date, for example 2024-01-01'
(date) => { })
return !isAccountBalanceInFuture({ date: resetHours(parseISO(date)) }); .refine(isValidDateAfter1970, {
}, error: 'The date must be after 1970-01-01'
{ error: 'The date must not be in the future' } })
) .refine(isValidAccountBalanceDate, {
error: 'The date must not be in the future'
})
.describe('The date of the cash balance, as an ISO 8601 date') .describe('The date of the cash balance, as an ISO 8601 date')
}); });

5
apps/api/src/app/endpoints/mcp/mcp.service.spec.ts

@ -700,7 +700,10 @@ describe('McpService', () => {
expect(await mcpService.updateAccountBalance(parameters)).toEqual({ expect(await mcpService.updateAccountBalance(parameters)).toEqual({
content: [ content: [
{ text: 'Updated the cash balance of the account', type: 'text' } {
text: 'Updated the cash balance of the account at 2024-01-01',
type: 'text'
}
] ]
}); });

4
apps/api/src/app/endpoints/mcp/mcp.service.ts

@ -280,7 +280,9 @@ export class McpService {
userId: account.userId userId: account.userId
}); });
return this.getTextResult('Updated the cash balance of the account'); return this.getTextResult(
`Updated the cash balance of the account at ${date}`
);
} }
private getHoldingFilterParameters({ private getHoldingFilterParameters({

Loading…
Cancel
Save