feat(mcp): add idx-mcp, a stdio MCP server over idx-core

Phase 4 of docs/DESIGN.md. Eleven grouped tools (quotes, history, screen,
company, analysis, compare, shareholders, investor holdings, search,
ownership changes, status); ownership_sync is listed only with
--allow-sync. Live results keep the Fetched envelope, errors use the CLI's
{error, code, message} body, and investor tools on an empty database
point at the sync path.

idx-core gains Period/Interval FromStr, Investor::status, a ticker
filter on changes, and Display for ErrorCode (now used by the CLI too).
CLI output and goldens are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hermes 2026-10-14 18:19:04 +00:00
commit cda12fe2db
18 changed files with 1546 additions and 38 deletions

View file

@ -0,0 +1,51 @@
//! `idx-mcp`: an MCP server (stdio) over idx-core. See docs/DESIGN.md.
mod params;
mod server;
use std::process::ExitCode;
use clap::Parser;
use idx_core::config::CoreConfig;
use rmcp::ServiceExt;
#[derive(Debug, Parser)]
#[command(
name = "idx-mcp",
version,
about = "MCP server (stdio) for Indonesian stock market (IDX) data"
)]
struct Args {
/// Expose the ownership_sync tool, which downloads the KSEI ownership
/// snapshot (several MB) into the local database.
#[arg(long)]
allow_sync: bool,
}
#[tokio::main]
async fn main() -> ExitCode {
let args = Args::parse();
let config = match CoreConfig::load() {
Ok(config) => config,
Err(err) => {
eprintln!("error: {err}");
return ExitCode::from(err.exit_code() as u8);
}
};
let server = server::IdxServer::new(config, args.allow_sync);
let running = match server.serve(rmcp::transport::stdio()).await {
Ok(running) => running,
Err(err) => {
eprintln!("error: {err}");
return ExitCode::FAILURE;
}
};
match running.waiting().await {
Ok(_) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("error: {err}");
ExitCode::FAILURE
}
}
}