import { DATE_RANGE_PATTERN } from '@ghostfolio/api/dtos/date-range-filter.dto'; import { isValidEncodedDataSource } from '@ghostfolio/api/helper/data-source.helper'; import { COMMENT_MAXIMUM_LENGTH, DATE_RANGES, DEFAULT_DATE_RANGE, MCP_MAX_ACCOUNTS, MCP_MAX_ACTIVITIES, SEARCH_QUERY_MAXIMUM_LENGTH, SEARCH_QUERY_MINIMUM_LENGTH, SYMBOL_MAXIMUM_LENGTH } from '@ghostfolio/common/config'; import { isValidCurrencyCode, isValidDateAfter1970 } from '@ghostfolio/common/helper'; import { AssetClass, DataSource, Type as ActivityType } from '@prisma/client'; 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 = z .union( [ z.enum(DataSource), z.string().refine(isValidEncodedDataSource, { error: DATA_SOURCE_PARAMETER_ERROR }) ], { error: DATA_SOURCE_PARAMETER_ERROR } ) .describe('The data source of the asset profile'); const SYMBOL_PARAMETER = z .string() .trim() .min(1) .max(SYMBOL_MAXIMUM_LENGTH) .describe('The symbol of the asset profile'); const HOLDING_PARAMETER = z.object({ dataSource: DATA_SOURCE_PARAMETER, symbol: SYMBOL_PARAMETER }); export const GET_ACCOUNTS_PARAMETERS = z.object({ accountIds: z .array(z.string().min(1)) .min(1) .max(MCP_MAX_ACCOUNTS) .optional() .describe( `The identifiers of the accounts to get, at most ${MCP_MAX_ACCOUNTS}` ), assetClasses: z .array(z.enum(AssetClass)) .min(1) .optional() .describe('The asset classes of the accounts to get'), holding: HOLDING_PARAMETER.optional().describe( 'The asset profile of the accounts to get' ) }); export const GET_ACTIVITIES_PARAMETERS = z.object({ activityTypes: z .array(z.enum(ActivityType)) .min(1) .optional() .describe('The types of the activities to get'), assetClasses: z .array(z.enum(AssetClass)) .min(1) .optional() .describe('The asset classes of the activities to get'), holding: HOLDING_PARAMETER.optional().describe( 'The asset profile of the activities to get' ), range: z .string() .regex(DATE_RANGE_PATTERN) .optional() .describe( `The date range of the activities to get, either ${DATE_RANGES.join( ', ' )} or a calendar year like 2024` ), skip: z .number() .int() .min(0) .optional() .describe('The number of activities to skip'), take: z .number() .int() .min(1) .max(MCP_MAX_ACTIVITIES) .default(MCP_MAX_ACTIVITIES) .describe(`The number of activities to get, at most ${MCP_MAX_ACTIVITIES}`) }); export const GET_PERFORMANCE_PARAMETERS = z.object({ accountIds: z .array(z.string().min(1)) .min(1) .max(MCP_MAX_ACCOUNTS) .optional() .describe( `The identifiers of the accounts of the performance, at most ${MCP_MAX_ACCOUNTS}` ), assetClasses: z .array(z.enum(AssetClass)) .min(1) .optional() .describe('The asset classes of the performance'), holding: HOLDING_PARAMETER.optional().describe( 'The asset profile of the performance' ), range: z .string() .regex(DATE_RANGE_PATTERN) .default(DEFAULT_DATE_RANGE) .describe( `The date range of the performance, either ${DATE_RANGES.join( ', ' )} or a calendar year like 2024` ) }); export const IMPORT_ACTIVITIES_PARAMETERS = z.object({ activities: z .array( z.object({ accountId: z .string() .min(1) .optional() .describe('The identifier of the account of the activity'), comment: z .string() .trim() .max(COMMENT_MAXIMUM_LENGTH) .optional() .describe('The comment of the activity'), currency: z .string() .refine(isValidCurrencyCode) .describe( 'The currency of the fee and of the unit price, as an ISO 4217 code in upper case' ), dataSource: DATA_SOURCE_PARAMETER.optional(), date: z .string() .refine(isValidDateAfter1970) .describe( 'The date of the activity, as an ISO 8601 date or date and time' ), fee: z.number().min(0).describe('The fee of the activity'), quantity: z.number().min(0).describe('The quantity of the activity'), symbol: SYMBOL_PARAMETER.describe( 'The symbol of the asset profile, or the description of a non-investment activity when no data source is given' ), type: z .enum(ActivityType) .describe( 'The type of the activity; DEPOSIT is incoming cash and WITHDRAWAL is outgoing cash' ), updateAccountBalance: z .boolean() .optional() .describe( 'Whether to update the account cash balance; deposits and withdrawals with an account update it by default, while other activity types do not' ), unitPrice: z.number().min(0).describe('The unit price of the activity') }) ) .min(1) .max(MCP_MAX_ACTIVITIES) .describe(`The activities to import, at most ${MCP_MAX_ACTIVITIES}`) }); export const SEARCH_ASSET_PROFILES_PARAMETERS = z.object({ query: z .string() .trim() .min(SEARCH_QUERY_MINIMUM_LENGTH) .max(SEARCH_QUERY_MAXIMUM_LENGTH) .describe( 'The name, ticker symbol or ISIN of the financial asset, for example Apple, AAPL, Bitcoin or US0378331005' ) });