use std::sync::Arc;
use pmcp::ServerBuilder;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::Result;
pub mod error;
pub mod handler;
pub mod input;
pub mod render_resource;
pub mod render_uri;
pub mod schema;
#[doc(inline)]
pub use error::{to_iserror_result, WorkbookToolError};
#[doc(inline)]
pub use handler::{
sanitize_tool_name, DiffVersionHandler, ExplainHandler, GetManifestHandler,
RenderWorkbookHandler, WorkbookToolHandler,
};
#[doc(inline)]
pub use input::{validate_input, ValidatedInput};
#[doc(inline)]
pub use render_resource::RenderWorkbookResource;
#[doc(inline)]
pub use render_uri::{decode, encode, DecodedRender, MAX_ENCODED_URI_LEN, WORKBOOK_XLSX_MIME};
pub use pmcp_workbook_runtime::{CellMap, Manifest, WorkbookBundle};
pub use pmcp_workbook_runtime::{
load_bundle, BundleLoadError, BundleSource, BundleSourceError, LocalDirSource,
};
#[cfg(feature = "workbook-embedded")]
pub use pmcp_workbook_runtime::EmbeddedSource;
pub const WORKBOOK_TOOL_UI: &str = "ui://workbook/result";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProvStamp {
pub bundle_id: String,
pub version: String,
pub combined_hash: String,
}
impl ProvStamp {
#[must_use]
pub fn from_bundle(bundle: &WorkbookBundle) -> Self {
Self {
bundle_id: bundle.stamp.bundle_id.clone(),
version: bundle.stamp.version.clone(),
combined_hash: bundle.stamp.combined.clone(),
}
}
#[must_use]
pub fn to_json(&self) -> Value {
serde_json::to_value(self).unwrap_or(Value::Null)
}
}
pub trait WorkbookBuilderExt: Sized {
fn with_workbook_bundle(self, source: &dyn BundleSource) -> Self;
fn try_with_workbook_bundle(self, source: &dyn BundleSource) -> Result<Self>;
}
impl WorkbookBuilderExt for ServerBuilder {
fn with_workbook_bundle(self, source: &dyn BundleSource) -> Self {
self.try_with_workbook_bundle(source).expect(
"with_workbook_bundle: BundleLoader load/verify returned an error — \
prefer try_with_workbook_bundle to handle a tampered/malformed bundle \
as a Result (WBSV-08 fail-closed)",
)
}
fn try_with_workbook_bundle(self, source: &dyn BundleSource) -> Result<Self> {
let bundle = Arc::new(load_bundle(source)?);
if bundle.cell_map.tools.is_empty() {
tracing::warn!(
target: "pmcp_server_toolkit::workbook",
bundle_id = %bundle.stamp.bundle_id,
version = %bundle.stamp.version,
"with_workbook_bundle: bundle declares zero tools — the server will \
register no workbook compute tools (set RUST_LOG=warn to surface this)"
);
}
let mut builder = self;
for tool in &bundle.cell_map.tools {
let name = sanitize_tool_name(&tool.name).map_err(|e| {
crate::error::ToolkitError::Synth(format!(
"workbook output Table '{}' has no MCP-mappable tool name: {}",
tool.name, e.reason
))
})?;
builder = builder.tool_arc(
&name,
Arc::new(WorkbookToolHandler::new(bundle.clone(), tool.clone())),
);
}
let builder = builder
.tool_arc(
ExplainHandler::NAME,
Arc::new(ExplainHandler::new(bundle.clone())),
)
.tool_arc(
GetManifestHandler::NAME,
Arc::new(GetManifestHandler::new(bundle.clone())),
)
.tool_arc(
DiffVersionHandler::NAME,
Arc::new(DiffVersionHandler::new(bundle.clone())),
)
.tool_arc(
RenderWorkbookHandler::NAME,
Arc::new(RenderWorkbookHandler::new(bundle.clone())),
)
.resources_arc(Arc::new(RenderWorkbookResource::new(bundle)));
Ok(builder)
}
}