sqlserver_mcp_catalog/tools/call_tool.rs
1// SQL Server 2025 - master/msdb/sandbox combined catalog MCP server.
2
3use crate::auth::auth_manager::AuthManager;
4use crate::core::config_schema::Config;
5use crate::data::store::EndpointRecord;
6use crate::services::api_client::ApiClient;
7use crate::validation::validator::{validate_input, validate_output};
8
9/// Validates arguments against the input schema, resolves the configured
10/// SQL Server credentials, executes the live TDS call, and checks the
11/// response against the output schema before returning it.
12///
13/// An output-schema mismatch is only logged, not raised: `resultset.sql`'s
14/// best-effort column introspection (see
15/// docs/sqlserver-eda-openapi-pipeline/README.md's "Known limitations")
16/// can be wrong about a conditional-result-set object's actual shape, and
17/// rejecting an otherwise-successful call over a documentation gap would
18/// deny the caller real data it already has in hand. Input validation
19/// still hard-fails: those arguments are under the caller's control, not
20/// the database's.
21///
22/// Takes an already-looked-up `EndpointRecord` rather than a `Connection`
23/// and an `operation_id` to look up itself: `rusqlite::Connection` isn't
24/// `Sync`, so a `&Connection` held across this function's `.await` points
25/// (the TDS call) would make the caller's future non-`Send` — a hard
26/// requirement for `#[tool]` methods. Callers look the endpoint up (a
27/// synchronous, `Connection`-scoped step) before calling this function,
28/// not inside it.
29pub async fn call_operation(
30 endpoint: &EndpointRecord,
31 config: &Config,
32 auth_manager: &mut AuthManager,
33 operation_id: &str,
34 args: serde_json::Value,
35) -> anyhow::Result<serde_json::Value> {
36 validate_input(&config.api_version, operation_id, &args)?;
37
38 let client = ApiClient::new(config.clone());
39 let response = client.execute(endpoint, &args, auth_manager).await?;
40
41 if let Err(err) = validate_output(&config.api_version, operation_id, &response) {
42 tracing::warn!(
43 operation_id,
44 error = %err,
45 "response did not match the documented schema; returning it as-is"
46 );
47 }
48 Ok(response)
49}