diff options
| author | main <main@swarm.moe> | 2026-03-20 23:15:05 -0400 |
|---|---|---|
| committer | main <main@swarm.moe> | 2026-03-20 23:15:05 -0400 |
| commit | e325cd23f19378f543981071673c1d03be438fa5 (patch) | |
| tree | e89fdfd82b65f6b373b15da59c430a0ceb428afc | |
| parent | bb92a05eb5446e07c6288e266bd06d7b5899eee5 (diff) | |
| download | libmcp-main.zip | |
| -rw-r--r-- | Cargo.lock | 53 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/libmcp/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/libmcp/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/libmcp/src/projection.rs | 50 |
5 files changed, 106 insertions, 1 deletions
@@ -27,6 +27,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -262,6 +271,7 @@ dependencies = [ "serde_json", "tempfile", "thiserror", + "time", "tokio", "url", ] @@ -309,6 +319,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -336,6 +352,12 @@ dependencies = [ ] [[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -555,6 +577,37 @@ dependencies = [ ] [[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] name = "tinystr" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -23,6 +23,7 @@ serde_json = "1.0.145" syn = { version = "2.0.108", features = ["full"] } tempfile = "3.23.0" thiserror = "2.0.17" +time = { version = "0.3.44", features = ["formatting"] } tokio = { version = "1.48.0", features = ["io-util", "macros", "rt", "rt-multi-thread", "sync", "time"] } url = "2.5.7" diff --git a/crates/libmcp/Cargo.toml b/crates/libmcp/Cargo.toml index c087ab8..0f12017 100644 --- a/crates/libmcp/Cargo.toml +++ b/crates/libmcp/Cargo.toml @@ -13,6 +13,7 @@ schemars.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true +time.workspace = true tokio.workspace = true url.workspace = true diff --git a/crates/libmcp/src/lib.rs b/crates/libmcp/src/lib.rs index 049cffe..b167964 100644 --- a/crates/libmcp/src/lib.rs +++ b/crates/libmcp/src/lib.rs @@ -35,7 +35,7 @@ pub use normalize::{ }; pub use projection::{ FallbackJsonProjection, ProjectionError, ProjectionPolicy, SelectorProjection, SelectorRef, - StructuredProjection, SurfaceKind, SurfacePolicy, ToolProjection, + StructuredProjection, SurfaceKind, SurfacePolicy, TimestampText, ToolProjection, }; pub use render::{ DetailLevel, JsonPorcelainConfig, PathStyle, RenderConfig, RenderMode, TruncatedText, diff --git a/crates/libmcp/src/projection.rs b/crates/libmcp/src/projection.rs index a6216db..1e42f49 100644 --- a/crates/libmcp/src/projection.rs +++ b/crates/libmcp/src/projection.rs @@ -4,7 +4,9 @@ use crate::render::{DetailLevel, JsonPorcelainConfig, render_json_porcelain}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::fmt; use thiserror::Error; +use time::{OffsetDateTime, format_description::well_known::Rfc3339}; const OVERVIEW_CONCISE_CONFIG: JsonPorcelainConfig = JsonPorcelainConfig { max_lines: 10, @@ -117,6 +119,35 @@ pub struct SelectorRef { pub title: Option<String>, } +/// Uniform RFC3339 timestamp text for model-facing surfaces. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(transparent)] +pub struct TimestampText(String); + +impl TimestampText { + /// Returns the rendered timestamp string. + #[must_use] + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for TimestampText { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(&self.0) + } +} + +impl From<OffsetDateTime> for TimestampText { + fn from(timestamp: OffsetDateTime) -> Self { + Self( + timestamp + .format(&Rfc3339) + .unwrap_or_else(|_| timestamp.unix_timestamp().to_string()), + ) + } +} + /// Anything that can produce a selector reference. pub trait SelectorProjection { /// Builds the selector reference. @@ -236,6 +267,7 @@ impl SurfacePolicy for FallbackJsonProjection { mod tests { use super::{StructuredProjection as _, SurfaceKind, SurfacePolicy as _}; use crate::{DetailLevel, SelectorProjection, SelectorRef, ToolProjection}; + use time::OffsetDateTime; #[derive(Clone, SelectorProjection)] struct HypothesisSelector { @@ -305,4 +337,22 @@ mod tests { }; assert!(porcelain.contains("slug: \"matched-lp-site-traces\"")); } + + #[test] + fn timestamp_text_serializes_as_rfc3339_string() { + let timestamp = OffsetDateTime::from_unix_timestamp(0); + assert!(timestamp.is_ok()); + let timestamp = match timestamp { + Ok(value) => value, + Err(_) => return, + }; + let rendered = super::TimestampText::from(timestamp); + let json = serde_json::to_value(&rendered); + assert!(json.is_ok()); + let json = match json { + Ok(value) => value, + Err(_) => return, + }; + assert_eq!(json, serde_json::json!("1970-01-01T00:00:00Z")); + } } |