diff --git a/README.md b/README.md index 30baf23..e7c10ed 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,28 @@ A [MCP(Model Context Protocol)](https://www.anthropic.com/news/model-context-pro Available tools: +### Japan Market + +- `buffett_code_get_jp_company` - Get Japanese company information from Buffett Code +- `buffett_code_get_jp_company_daily` - Get daily Japanese company information from Buffett Code for a specific date +- `buffett_code_get_jp_company_quarterly` - Get quarterly Japanese company information from Buffett Code for a specific year and quarter +- `buffett_code_get_jp_company_daily_market_reaction` - Get the daily market reaction for a JP company as both text and stock price change rate. Currently available only for stocks with sufficient data, on or near quarterly or annual earnings announcement dates. +- `buffett_code_get_jp_company_weekly_stats` - Get weekly statistics calculated for the company or stock, mainly including stock price related statistics. +- `buffett_code_get_jp_company_monthly_stats` - Get monthly statistics calculated for the company or stock, mainly including stock price related statistics. +- `buffett_code_get_jp_company_monthly_kpis` - Get monthly KPIs for a JP company. +- `buffett_code_get_jp_company_quarterly_long_text_content` - Get processed long-form text content from Buffett Code, based on HTML text found in securities reports or quarterly reports. +- `buffett_code_get_jp_company_quarterly_major_shareholders` - Get major shareholders information for a company or stock as listed in its securities report or quarterly report. +- `buffett_code_get_jp_company_quarterly_segments` - Get segment information as disclosed in a company or stock’s securities report or quarterly report. +- `buffett_code_get_jp_company_annually_guidance_revisions` - Get a list of earnings guidance revisions by fiscal year for the company or stock. +- `buffett_code_get_jp_company_similarities` - Get a list of companies similar to the specified company. + +### US Market + - `buffett_code_get_us_company` - Get company information from Buffett Code - `buffett_code_get_us_company_daily` - Get daily company information from Buffett Code for a specific date - `buffett_code_get_us_company_quarterly` - Get quarterly company information from Buffett Code for a specific year and quarter - `buffett_code_get_us_company_stocks` - Get company stock information from Buffett Code for a specific stock -- `buffett_code_get_us_company_stocks_daily` Get daily company stock information from Buffett Code for a specific stock and date +- `buffett_code_get_us_company_stocks_daily` - Get daily company stock information from Buffett Code for a specific stock and date - `buffett_code_get_us_company_stocks_quarterly` - Get quarterly company stock information from Buffett Code for a specific stock and year-quarter ## Quick Start diff --git a/examples/get_jp_company.ts b/examples/get_jp_company.ts new file mode 100644 index 0000000..7e3f8d8 --- /dev/null +++ b/examples/get_jp_company.ts @@ -0,0 +1,85 @@ +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { config } from 'dotenv'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import { CallToolResult, CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; + +// Get the current file's directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Load environment variables from .env file +config(); + +// Get and validate required environment variables +const apiKey = process.env.BUFFETT_CODE_API_KEY; +if (!apiKey) { + throw new Error( + 'BUFFETT_CODE_API_KEY environment variable is required. Set it in your .env file.' + ); +} + +// Compose env for child process +const env = { + BUFFETT_CODE_API_KEY: apiKey, +} as const satisfies Record; + +async function main() { + // Initialize MCP client + const client = new Client( + { + name: 'buffett-code-mcp-example-client', + version: '1.0.0', + }, + { + capabilities: {}, + } + ); + + // Create transport to connect to the server + const transport = new StdioClientTransport({ + command: process.execPath, + args: [ + resolve(__dirname, '../dist/index.js'), // pre-build server + ], + env, + }); + + try { + // Connect to the server + await client.connect(transport); + console.log('Connected to BuffetCode MCP server'); + + const response = (await client.callTool( + { + name: 'buffett_code_get_jp_company', + arguments: { + companyId: '9101', // 日本郵船 + }, + }, + CallToolResultSchema + )) as CallToolResult; + + if ( + Array.isArray(response.content) && + response.content[0]?.type === 'text' + ) { + const data = JSON.parse(response.content[0].text); + console.log('Company data (example):', data); + } else { + console.error('Unexpected response format for example call'); + } + } catch (error) { + console.error('Error:', error); + process.exit(1); + } finally { + await transport.close(); + console.log('Connection closed.'); + } +} + +main().catch((error) => { + console.error('Fatal error in main:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/examples/get_us_company.ts b/examples/get_us_company.ts index d6dfe54..70fa573 100644 --- a/examples/get_us_company.ts +++ b/examples/get_us_company.ts @@ -51,14 +51,6 @@ async function main() { await client.connect(transport); console.log('Connected to BuffetCode MCP server'); - // List available tools - const toolsResponse = await client.listTools(); - - console.log('Available tools:'); - toolsResponse.tools.forEach(tool => { - console.log(`- ${tool.name}: ${tool.description}`); - }); - const response = (await client.callTool( { name: 'buffett_code_get_us_company', diff --git a/examples/list_tools.ts b/examples/list_tools.ts new file mode 100644 index 0000000..c503edf --- /dev/null +++ b/examples/list_tools.ts @@ -0,0 +1,72 @@ +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; +import { config } from 'dotenv'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; + +// Get the current file's directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Load environment variables from .env file +config(); + +// Get and validate required environment variables +const apiKey = process.env.BUFFETT_CODE_API_KEY; +if (!apiKey) { + throw new Error( + 'BUFFETT_CODE_API_KEY environment variable is required. Set it in your .env file.' + ); +} + +// Compose env for child process +const env = { + BUFFETT_CODE_API_KEY: apiKey, +} as const satisfies Record; + +async function main() { + // Initialize MCP client + const client = new Client( + { + name: 'buffett-code-mcp-example-client', + version: '1.0.0', + }, + { + capabilities: {}, + } + ); + + // Create transport to connect to the server + const transport = new StdioClientTransport({ + command: process.execPath, + args: [ + resolve(__dirname, '../dist/index.js'), // pre-build server + ], + env, + }); + + try { + // Connect to the server + await client.connect(transport); + console.log('Connected to BuffetCode MCP server'); + + // List available tools + const toolsResponse = await client.listTools(); + + console.log('Available tools:'); + toolsResponse.tools.forEach(tool => { + console.log(`- ${tool.name}: ${tool.description}`); + }); + } catch (error) { + console.error('Error:', error); + process.exit(1); + } finally { + await transport.close(); + console.log('Connection closed.'); + } +} + +main().catch((error) => { + console.error('Fatal error in main:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 54839e7..37ff54d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "buffette-code-mcp-server", - "version": "0.0.1", + "name": "buffett-code-mcp-server", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "buffette-code-mcp-server", - "version": "0.0.1", + "name": "buffett-code-mcp-server", + "version": "0.1.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.4", @@ -18,7 +18,7 @@ "zod-to-json-schema": "^3.22.4" }, "bin": { - "buffette-code-mcp-server": "dist/index.js" + "buffett-code-mcp-server": "dist/index.js" }, "devDependencies": { "@types/adm-zip": "^0.5.7", @@ -26,6 +26,7 @@ "@typescript-eslint/parser": "^6.19.0", "eslint": "^8.17.0", "eslint-config-prettier": "^9.1.0", + "npm-run-all2": "^8.0.1", "openapi-typescript": "^7.7.1", "prettier": "^3.2.2", "shx": "^0.3.4", @@ -2532,6 +2533,16 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2618,6 +2629,15 @@ "node": ">= 0.8" } }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -2723,6 +2743,98 @@ "node": ">= 0.6" } }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.1.tgz", + "integrity": "sha512-jkhE0AsELQeCtScrcJ/7mSIdk+ZsnWjvKk3KwE96HZ6+OFVB74XhxQtHT1W6kdUfn92fRnBb29Mz82j9bV2XEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "minimatch": "^10.0.1", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" + } + }, + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm-run-all2/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2992,6 +3104,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pkce-challenge": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", @@ -3120,6 +3245,20 @@ "node": ">= 0.8" } }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -3354,6 +3493,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shelljs": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", diff --git a/package.json b/package.json index 27d3d32..330362c 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,10 @@ "fix": "npm run fix:eslint && npm run fix:prettier", "fix:eslint": "eslint \"src/**/*.ts\" --fix", "fix:prettier": "prettier --write \"src/**/*.ts\"", - "examples": "npm run build && node --import ./ts-node-loader.js examples/get_us_company.ts" + "examples": "run-s examples:*", + "examples:list_tools": "npm run build && node --import ./ts-node-loader.js examples/list_tools.ts", + "examples:get_jp_company": "npm run build && node --import ./ts-node-loader.js examples/get_jp_company.ts", + "examples:get_us_company": "npm run build && node --import ./ts-node-loader.js examples/get_us_company.ts" }, "repository": { "type": "git", @@ -53,6 +56,7 @@ "@typescript-eslint/parser": "^6.19.0", "eslint": "^8.17.0", "eslint-config-prettier": "^9.1.0", + "npm-run-all2": "^8.0.1", "openapi-typescript": "^7.7.1", "prettier": "^3.2.2", "shx": "^0.3.4", diff --git a/src/index.ts b/src/index.ts index b2112f6..bf52b7c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,18 @@ import { USCompanyStocksRequestSchema, USCompanyStocksDailyRequestSchema, USCompanyStocksQuarterlyRequestSchema, + JPCompanyRequestSchema, + JPCompanyDailyRequestSchema, + JPCompanyQuarterlyRequestSchema, + JPCompanyDailyMarketReactionRequestSchema, + JPCompanyWeeklyStatsRequestSchema, + JPCompanyMonthlyStatsRequestSchema, + JPCompanyMonthlyKpisRequestSchema, + JPCompanyQuarterlyLongTextContentRequestSchema, + JPCompanyQuarterlyMajorShareholdersRequestSchema, + JPCompanyQuarterlySegmentsRequestSchema, + JPCompanyAnnuallyGuidanceRevisionsRequestSchema, + JPCompanySimilaritiesRequestSchema, } from './schemas.js'; import dotenv from 'dotenv'; import { createBuffetteCodeClient } from './client/index.js'; @@ -49,6 +61,82 @@ const server = new Server( server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ + { + name: 'buffett_code_get_jp_company', + description: 'Get Japanese company information from Buffett Code', + inputSchema: zodToJsonSchema(JPCompanyRequestSchema), + }, + { + name: 'buffett_code_get_jp_company_daily', + description: + 'Get daily Japanese company information from Buffett Code for a specific date', + inputSchema: zodToJsonSchema(JPCompanyDailyRequestSchema), + }, + { + name: 'buffett_code_get_jp_company_quarterly', + description: + 'Get quarterly Japanese company information from Buffett Code for a specific year and quarter', + inputSchema: zodToJsonSchema(JPCompanyQuarterlyRequestSchema), + }, + { + name: 'buffett_code_get_jp_company_daily_market_reaction', + description: + 'Get the daily market reaction for a JP company as both text and stock price change rate. Currently available only for stocks with sufficient data, on or near quarterly or annual earnings announcement dates.', + inputSchema: zodToJsonSchema(JPCompanyDailyMarketReactionRequestSchema), + }, + { + name: 'buffett_code_get_jp_company_weekly_stats', + description: + 'Get weekly statistics calculated for the company or stock, mainly including stock price related statistics.', + inputSchema: zodToJsonSchema(JPCompanyWeeklyStatsRequestSchema), + }, + { + name: 'buffett_code_get_jp_company_monthly_stats', + description: + 'Get monthly statistics calculated for the company or stock, mainly including stock price related statistics.', + inputSchema: zodToJsonSchema(JPCompanyMonthlyStatsRequestSchema), + }, + { + name: 'buffett_code_get_jp_company_monthly_kpis', + description: 'Get monthly KPIs for a JP company.', + inputSchema: zodToJsonSchema(JPCompanyMonthlyKpisRequestSchema), + }, + { + name: 'buffett_code_get_jp_company_quarterly_long_text_content', + description: + 'Get processed long-form text content from Buffett Code, based on HTML text found in securities reports or quarterly reports.', + inputSchema: zodToJsonSchema( + JPCompanyQuarterlyLongTextContentRequestSchema + ), + }, + { + name: 'buffett_code_get_jp_company_quarterly_major_shareholders', + description: + 'Get major shareholders information for a company or stock as listed in its securities report or quarterly report.', + inputSchema: zodToJsonSchema( + JPCompanyQuarterlyMajorShareholdersRequestSchema + ), + }, + { + name: 'buffett_code_get_jp_company_quarterly_segments', + description: + 'Get segment information as disclosed in a company or stock’s securities report or quarterly report.', + inputSchema: zodToJsonSchema(JPCompanyQuarterlySegmentsRequestSchema), + }, + { + name: 'buffett_code_get_jp_company_annually_guidance_revisions', + description: + 'Get a list of earnings guidance revisions by fiscal year for the company or stock.', + inputSchema: zodToJsonSchema( + JPCompanyAnnuallyGuidanceRevisionsRequestSchema + ), + }, + { + name: 'buffett_code_get_jp_company_similarities', + description: + 'Get a list of companies similar to the specified company.', + inputSchema: zodToJsonSchema(JPCompanySimilaritiesRequestSchema), + }, { name: 'buffett_code_get_us_company', description: 'Get company information from Buffett Code', @@ -95,6 +183,241 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } switch (request.params.name) { + case 'buffett_code_get_jp_company': { + const args = JPCompanyRequestSchema.parse(request.params.arguments); + const company_id = args.companyId; + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}', + { params: { path: { company_id } } } + ); + + if (response.error) + throw new Error(`Failed to get company data: ${response.error}`); + const result = response.data; + + return { + content: [{ type: 'text', text: JSON.stringify(result) }], + }; + } + + case 'buffett_code_get_jp_company_daily': { + const args = JPCompanyDailyRequestSchema.parse( + request.params.arguments + ); + const company_id = args.companyId; + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/daily/{date}', + { params: { path: { company_id, date: args.date } } } + ); + + if (response.error) + throw new Error(`Failed to get daily data: ${response.error}`); + const result = response.data; + + return { + content: [{ type: 'text', text: JSON.stringify(result) }], + }; + } + + case 'buffett_code_get_jp_company_quarterly': { + const args = JPCompanyQuarterlyRequestSchema.parse( + request.params.arguments + ); + const company_id = args.companyId; + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/quarterly/{year_quarter}', + { + params: { + path: { company_id, year_quarter: args.year_quarter }, + }, + } + ); + + if (response.error) + throw new Error(`Failed to get quarterly data: ${response.error}`); + const result = response.data; + + return { + content: [{ type: 'text', text: JSON.stringify(result) }], + }; + } + + case 'buffett_code_get_jp_company_daily_market_reaction': { + const args = JPCompanyDailyMarketReactionRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/daily/{date}/market_reaction', + { + params: { + path: { company_id: args.companyId, date: args.date }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + + case 'buffett_code_get_jp_company_weekly_stats': { + const args = JPCompanyWeeklyStatsRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/weekly/{year_week}/stats', + { + params: { + path: { company_id: args.companyId, year_week: args.year_week }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + + case 'buffett_code_get_jp_company_monthly_stats': { + const args = JPCompanyMonthlyStatsRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/monthly/{year_month}/stats', + { + params: { + path: { company_id: args.companyId, year_month: args.year_month }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + + case 'buffett_code_get_jp_company_monthly_kpis': { + const args = JPCompanyMonthlyKpisRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/monthly/{year_month}/kpis', + { + params: { + path: { company_id: args.companyId, year_month: args.year_month }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + + case 'buffett_code_get_jp_company_quarterly_long_text_content': { + const args = JPCompanyQuarterlyLongTextContentRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/quarterly/{year_quarter}/long_text_content', + { + params: { + path: { + company_id: args.companyId, + year_quarter: args.year_quarter, + }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + + case 'buffett_code_get_jp_company_quarterly_major_shareholders': { + const args = JPCompanyQuarterlyMajorShareholdersRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/quarterly/{year_quarter}/major_shareholders', + { + params: { + path: { + company_id: args.companyId, + year_quarter: args.year_quarter, + }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + + case 'buffett_code_get_jp_company_quarterly_segments': { + const args = JPCompanyQuarterlySegmentsRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/quarterly/{year_quarter}/segments', + { + params: { + path: { + company_id: args.companyId, + year_quarter: args.year_quarter, + }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + + case 'buffett_code_get_jp_company_annually_guidance_revisions': { + const args = JPCompanyAnnuallyGuidanceRevisionsRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/annually/{fiscal_year}/guidance_revisions', + { + params: { + path: { + company_id: args.companyId, + fiscal_year: args.fiscal_year, + }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + + case 'buffett_code_get_jp_company_similarities': { + const args = JPCompanySimilaritiesRequestSchema.parse( + request.params.arguments + ); + const response = await buffetteCodeClient.GET( + '/api/v4/jp/companies/{company_id}/similarities', + { + params: { + path: { + company_id: args.companyId, + }, + }, + } + ); + if (response.error) throw new Error(`API Error: ${response.error}`); + return { + content: [{ type: 'text', text: JSON.stringify(response.data) }], + }; + } + case 'buffett_code_get_us_company': { const args = USCompanyRequestSchema.parse(request.params.arguments); const company_id = args.companyId; @@ -127,7 +450,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const company_id = args.companyId; const response = await buffetteCodeClient.GET( '/api/v4/us/companies/{company_id}/daily/{date}', - { params: { path: { company_id, date: args.date } } } + { + params: { + path: { + company_id, + date: args.date, + }, + }, + } ); if (response.error) diff --git a/src/schemas.ts b/src/schemas.ts index b485ca3..409788c 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -2,43 +2,251 @@ import { z } from 'zod'; export const BaseRequestSchema = z.object({}); +// +// JP Company Schemas +// + +export const JPCompanyRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), +}); + +export const JPCompanyDailyRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + date: z + .string() + .regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) + .describe( + 'Date for the requested data in RFC3339 format (e.g., YYYY-MM-DD).' + ), +}); + +export const JPCompanyQuarterlyRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + year_quarter: z + .string() + .regex(/^[0-9]{4}Q[0-9]$/) + .describe( + 'Fiscal year and quarter for the requested data (e.g., YYYYQ[1-4]).' + ), +}); + +// Placeholder schemas for additional JP tools +export const JPCompanyDailyMarketReactionRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + date: z + .string() + .regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) + .describe( + 'Date for the requested data in RFC3339 format (e.g., YYYY-MM-DD).' + ), +}); + +export const JPCompanyWeeklyStatsRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + year_week: z + .string() + .regex(/^[0-9]{4}W[0-9]{2}$/) + .describe( + 'Year and week number for the requested data (e.g., YYYYWww, following ISO 8601 week date format).' + ), +}); + +export const JPCompanyMonthlyStatsRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + year_month: z + .string() + .regex(/^[0-9]{4}-[0-9]{2}$/) + .describe('Year and month for the requested data (e.g., YYYY-MM).'), +}); + +export const JPCompanyMonthlyKpisRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + year_month: z + .string() + .regex(/^[0-9]{4}-[0-9]{2}$/) + .describe('Year and month for the requested data (e.g., YYYY-MM).'), +}); + +export const JPCompanyQuarterlyLongTextContentRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + year_quarter: z + .string() + .regex(/^[0-9]{4}Q[0-9]$/) + .describe( + 'Fiscal year and quarter for the requested data (e.g., YYYYQ[1-4]).' + ), +}); + +export const JPCompanyQuarterlyMajorShareholdersRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + year_quarter: z + .string() + .regex(/^[0-9]{4}Q[0-9]$/) + .describe( + 'Fiscal year and quarter for the requested data (e.g., YYYYQ[1-4]).' + ), +}); + +export const JPCompanyQuarterlySegmentsRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + year_quarter: z + .string() + .regex(/^[0-9]{4}Q[0-9]$/) + .describe( + 'Fiscal year and quarter for the requested data (e.g., YYYYQ[1-4]).' + ), +}); + +export const JPCompanyAnnuallyGuidanceRevisionsRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), + fiscal_year: z + .string() + .regex(/^[0-9]{4}$/) + .describe('Fiscal year for the requested data (e.g., YYYY).'), +}); + +export const JPCompanySimilaritiesRequestSchema = z.object({ + companyId: z + .string() + .regex(/(^[a-zA-Z0-9]{10}$)|(^[a-zA-Z0-9]{4}$)|(^[0-9]{13}$)/) + .describe( + 'Company identifier. Accepts Buffett Code Company ID (10 alphanumeric chars), Ticker Symbol (4 alphanumeric chars), or Corporate Number (13 digits).' + ), +}); + +// +// US Company Schemas +// + export const USCompanyRequestSchema = z.object({ - companyId: z.string().regex(/^[0-9]+$/, 'EDINET code (e.g.: 0001652044)'), + companyId: z + .string() + .regex(/^[0-9]+$/) + .describe('Company identifier (EDINET code, e.g., 0001652044).'), }); export const USCompanyDailyRequestSchema = z.object({ - companyId: z.string().regex(/^[0-9]+$/, 'EDINET code (e.g.: 0001652044)'), + companyId: z + .string() + .regex(/^[0-9]+$/) + .describe('Company identifier (EDINET code, e.g., 0001652044).'), date: z .string() - .regex( - /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/, - 'RFC3339 date string (e.g.: 2024-01-01)' + .regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) + .describe( + 'Date for the requested data in RFC3339 format (e.g., YYYY-MM-DD).' ), }); export const USCompanyQuarterlyRequestSchema = z.object({ - companyId: z.string().regex(/^[0-9]+$/, 'EDINET code (e.g.: 0001652044)'), - year_quarter: z.string().regex(/^[0-9]{4}Q[0-9]$/, 'e.g.: 2020Q4'), + companyId: z + .string() + .regex(/^[0-9]+$/) + .describe('Company identifier (EDINET code, e.g., 0001652044).'), + year_quarter: z + .string() + .regex(/^[0-9]{4}Q[0-9]$/) + .describe( + 'Fiscal year and quarter for the requested data (e.g., YYYYQ[1-4]).' + ), }); export const USCompanyStocksRequestSchema = z.object({ - companyId: z.string().regex(/^[0-9]+$/, 'EDINET code (e.g.: 0001652044)'), - stock_id: z.string().regex(/^[a-z]+$/, 'stock ID (e.g.: goog)'), + companyId: z + .string() + .regex(/^[0-9]+$/) + .describe('Company identifier (EDINET code, e.g., 0001652044).'), + stock_id: z + .string() + .regex(/^[a-z]+$/) + .describe('Stock identifier for the company (e.g., goog).'), }); export const USCompanyStocksDailyRequestSchema = z.object({ - companyId: z.string().regex(/^[0-9]+$/, 'EDINET code (e.g.: 0001652044)'), - stock_id: z.string().regex(/^[a-z]+$/, 'stock ID (e.g.: goog)'), + companyId: z + .string() + .regex(/^[0-9]+$/) + .describe('Company identifier (EDINET code, e.g., 0001652044).'), + stock_id: z + .string() + .regex(/^[a-z]+$/) + .describe('Stock identifier for the company (e.g., goog).'), date: z .string() - .regex( - /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/, - 'RFC3339 date string (e.g.: 2024-01-01)' + .regex(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) + .describe( + 'Date for the requested data in RFC3339 format (e.g., YYYY-MM-DD).' ), }); export const USCompanyStocksQuarterlyRequestSchema = z.object({ - companyId: z.string().regex(/^[0-9]+$/, 'EDINET code (e.g.: 0001652044)'), - stock_id: z.string().regex(/^[a-z]+$/, 'stock ID (e.g.: goog)'), - year_quarter: z.string().regex(/^[0-9]{4}Q[0-9]$/, 'e.g.: 2020Q4'), + companyId: z + .string() + .regex(/^[0-9]+$/) + .describe('Company identifier (EDINET code, e.g., 0001652044).'), + stock_id: z + .string() + .regex(/^[a-z]+$/) + .describe('Stock identifier for the company (e.g., goog).'), + year_quarter: z + .string() + .regex(/^[0-9]{4}Q[0-9]$/) + .describe( + 'Fiscal year and quarter for the requested data (e.g., YYYYQ[1-4]).' + ), });