mcp: part 2

This commit is contained in:
nym21
2025-06-21 20:34:14 +02:00
parent c3ae3cb768
commit c0f4ece17b
21 changed files with 280 additions and 187 deletions

View File

@@ -5,7 +5,7 @@ use axum::{
response::{IntoResponse, Redirect, Response},
routing::get,
};
use brk_interface::{Index, Pagination, Params, ParamsOpt};
use brk_interface::{IdParam, Index, PaginatedIndexParam, PaginationParam, Params, ParamsOpt};
use super::AppState;
@@ -18,7 +18,7 @@ pub trait ApiRoutes {
fn add_api_routes(self) -> Self;
}
const TO_SEPARATOR: &str = "-to-";
const TO_SEPARATOR: &str = "_to_";
impl ApiRoutes for Router<AppState> {
fn add_api_routes(self) -> Self {
@@ -56,29 +56,29 @@ impl ApiRoutes for Router<AppState> {
"/api/vecs/ids",
get(
async |State(app_state): State<AppState>,
Query(pagination): Query<Pagination>|
Query(pagination): Query<PaginationParam>|
-> Response {
Json(app_state.interface.get_vecids(pagination)).into_response()
},
),
)
.route(
"/api/vecs/indexes-to-ids",
"/api/vecs/index-to-ids",
get(
async |State(app_state): State<AppState>,
Query(pagination): Query<Pagination>|
Query(paginated_index): Query<PaginatedIndexParam>|
-> Response {
Json(app_state.interface.get_indexes_to_vecids(pagination)).into_response()
Json(app_state.interface.get_index_to_vecids(paginated_index)).into_response()
},
),
)
.route(
"/api/vecs/ids-to-indexes",
"/api/vecs/id-to-indexes",
get(
async |State(app_state): State<AppState>,
Query(pagination): Query<Pagination>|
Query(param): Query<IdParam>|
-> Response {
Json(app_state.interface.get_vecids_to_indexes(pagination)).into_response()
Json(app_state.interface.get_vecid_to_indexes(param.id)).into_response()
},
),
)
@@ -92,16 +92,18 @@ impl ApiRoutes for Router<AppState> {
Query(params_opt): Query<ParamsOpt>,
state: State<AppState>|
-> Response {
let variant = variant.replace("_", "-");
let variant = variant.replace("-", "_");
let mut split = variant.split(TO_SEPARATOR);
let params = Params::from((
(
Index::try_from(split.next().unwrap()).unwrap(),
split.collect::<Vec<_>>().join(TO_SEPARATOR),
),
params_opt,
));
interface::handler(headers, Query(params), state).await
if let Ok(index) = Index::try_from(split.next().unwrap()) {
let params = Params::from((
(index, split.collect::<Vec<_>>().join(TO_SEPARATOR)),
params_opt,
));
interface::handler(headers, Query(params), state).await
} else {
"Bad variant".into_response()
}
},
),
)

View File

@@ -113,7 +113,7 @@ impl Server {
}))
}
pub async fn serve(self, watch: bool) -> color_eyre::Result<()> {
pub async fn serve(self, watch: bool, mcp: bool) -> color_eyre::Result<()> {
let state = self.0;
if let Some(websites_path) = state.websites_path.clone() {
@@ -162,7 +162,7 @@ impl Server {
let router = Router::new()
.add_api_routes()
.add_website_routes(state.website)
.add_mcp_routes(state.interface)
.add_mcp_routes(state.interface, mcp)
.route("/version", get(Json(VERSION)))
.with_state(state)
.layer(compression_layer)

View File

@@ -1,5 +1,5 @@
use brk_interface::{Interface, Pagination, Params};
use rmcp::{
use brk_interface::{IdParam, Interface, PaginatedIndexParam, PaginationParam, Params};
use brk_rmcp::{
Error as McpError, RoleServer, ServerHandler,
model::{
CallToolResult, Content, Implementation, InitializeRequestParam, InitializeResult,
@@ -8,6 +8,7 @@ use rmcp::{
service::RequestContext,
tool,
};
use log::info;
#[derive(Clone)]
pub struct API {
@@ -23,88 +24,111 @@ impl API {
}
#[tool(description = "
Get the count of all existing indexes
Get the count of all existing indexes.
")]
async fn get_index_count(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_index_count");
Ok(CallToolResult::success(vec![
Content::json(self.interface.get_index_count()).unwrap(),
]))
}
#[tool(description = "
Get the count of all existing vec ids
Get the count of all existing vec ids.
")]
async fn get_vecid_count(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_vecid_count");
Ok(CallToolResult::success(vec![
Content::json(self.interface.get_vecid_count()).unwrap(),
]))
}
#[tool(description = "
Get the count of all existing vecs
Get the count of all existing vecs.
Equals to the sum of supported Indexes of each vec id.
")]
async fn get_variant_count(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_variant_count");
Ok(CallToolResult::success(vec![
Content::json(self.interface.get_vec_count()).unwrap(),
]))
}
#[tool(description = "
Get the list of all existing indexes
Get the list of all existing indexes.
")]
async fn get_indexes(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_indexes");
Ok(CallToolResult::success(vec![
Content::json(self.interface.get_indexes()).unwrap(),
]))
}
#[tool(description = "
Get an object which has all existing indexes as keys and a list of their accepted variants as values
Get an object which has all existing indexes as keys and a list of their accepted variants as values.
")]
async fn get_accepted_indexes(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_accepted_indexes");
Ok(CallToolResult::success(vec![
Content::json(self.interface.get_accepted_indexes()).unwrap(),
]))
}
#[tool(description = "
Get the list of all existing vec ids
Get a paginated list of all existing vec ids.
There are up to 1,000 values per page.
If the `page` param is omitted, it will default to page `0`.
")]
async fn get_vecids(
&self,
#[tool(aggr)] pagination: Pagination,
#[tool(aggr)] pagination: PaginationParam,
) -> Result<CallToolResult, McpError> {
info!("mcp: get_vecids");
Ok(CallToolResult::success(vec![
Content::json(self.interface.get_vecids(pagination)).unwrap(),
]))
}
#[tool(description = "
Get an object which has all existing indexes as keys and a list of ids of vecs which support that index as values
Get a paginated list of all vec ids which support a given index.
There are up to 1,000 values per page.
If the `page` param is omitted, it will default to page `0`.
")]
async fn get_indexes_to_vecids(
async fn get_index_to_vecids(
&self,
#[tool(aggr)] pagination: Pagination,
#[tool(aggr)] paginated_index: PaginatedIndexParam,
) -> Result<CallToolResult, McpError> {
info!("mcp: get_index_to_vecids");
Ok(CallToolResult::success(vec![
Content::json(self.interface.get_indexes_to_vecids(pagination)).unwrap(),
Content::json(self.interface.get_index_to_vecids(paginated_index)).unwrap(),
]))
}
#[tool(description = "
Get an object which has all existing vec ids as keys and a list of indexes supported by that vec id as values
Get a list of all indexes supported by a given vec id.
The list will be empty if the vec id isn't correct.
")]
async fn get_vecids_to_indexes(
async fn get_vecid_to_indexes(
&self,
#[tool(aggr)] pagination: Pagination,
#[tool(aggr)] param: IdParam,
) -> Result<CallToolResult, McpError> {
info!("mcp: get_vecid_to_indexes");
Ok(CallToolResult::success(vec![
Content::json(self.interface.get_vecids_to_indexes(pagination)).unwrap(),
Content::json(self.interface.get_vecid_to_indexes(param.id)).unwrap(),
]))
}
#[tool(description = "Get one or multiple vecs depending on given parameters")]
#[tool(description = "
Get one or multiple vecs depending on given parameters.
If you'd like to request multiple vec ids, simply separate them with a ','.
To get the last value set `-1` to the `from` parameter.
The response's format will depend on the given parameters, it will be:
- A value: If requested only one vec and the given range returns one value (for example: `from=-1`)
- A list: If requested only one vec and the given range returns multiple values (for example: `from=-1000&count=100` or `from=-444&to=-333`)
- A matrix: When multiple vecs are requested, even if they each return one value.
")]
fn get_vecs(&self, #[tool(aggr)] params: Params) -> Result<CallToolResult, McpError> {
info!("mcp: get_vecs");
Ok(CallToolResult::success(vec![
Content::json(self.interface.search_and_format(params).unwrap()).unwrap(),
]))
@@ -114,6 +138,7 @@ Get an object which has all existing vec ids as keys and a list of indexes suppo
Get the running version of the Bitcoin Research Kit
")]
async fn get_version(&self) -> Result<CallToolResult, McpError> {
info!("mcp: get_version");
Ok(CallToolResult::success(vec![Content::text(format!(
"v{VERSION}"
))]))
@@ -124,14 +149,22 @@ Get the running version of the Bitcoin Research Kit
impl ServerHandler for API {
fn get_info(&self) -> ServerInfo {
ServerInfo {
protocol_version: ProtocolVersion::V_2025_03_26,
protocol_version: ProtocolVersion::LATEST,
capabilities: ServerCapabilities::builder().enable_tools().build(),
server_info: Implementation::from_build_env(),
instructions: Some(
"
This server provides an interface to communicate with a running instance of the Bitcoin Research Kit (brk).
Multiple tools are at your disposal including ones to fetch all sorts of Bitcoin on-chain data.
Arrays are also called Vectors (or Vecs).
This server provides an interface to communicate with a running instance of the Bitcoin Research Kit (also called brk or BRK).
Multiple tools are at your disposal including ones to fetch all sorts of Bitcoin on-chain metrics and market prices.
If you're unsure which datasets are available, try out different tools before browsing the web. Each tool gives important information about BRK's capabilities.
Vectors can also be called 'Vecs', 'Arrays' or 'Datasets', they can all be used interchangeably.
An 'Index' (or indexes) is the timeframe of a dataset.
'VecId' (or vecids) are the name of the dataset and what it represents.
"
.to_string(),
),

View File

@@ -1,23 +1,28 @@
use axum::Router;
use brk_interface::Interface;
use rmcp::transport::{
use brk_rmcp::transport::{
StreamableHttpServerConfig,
streamable_http_server::{StreamableHttpService, session::local::LocalSessionManager},
};
mod api;
use api::*;
use log::info;
use crate::AppState;
pub trait MCPRoutes {
fn add_mcp_routes(self, interface: &'static Interface<'static>) -> Self;
fn add_mcp_routes(self, interface: &'static Interface<'static>, mcp: bool) -> Self;
}
impl MCPRoutes for Router<AppState> {
fn add_mcp_routes(self, interface: &'static Interface<'static>) -> Self {
fn add_mcp_routes(self, interface: &'static Interface<'static>, mcp: bool) -> Self {
if !mcp {
return self;
}
let config = StreamableHttpServerConfig {
// stateful_mode: false,
// stateful_mode: false, // breaks Claude
..Default::default()
};
@@ -27,6 +32,8 @@ impl MCPRoutes for Router<AppState> {
config,
);
info!("Setting MCP...");
self.nest_service("/mcp", service)
}
}