swarm repositories / source
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormain <main@swarm.moe>2026-03-20 23:15:05 -0400
committermain <main@swarm.moe>2026-03-20 23:15:05 -0400
commite325cd23f19378f543981071673c1d03be438fa5 (patch)
treee89fdfd82b65f6b373b15da59c430a0ceb428afc
parentbb92a05eb5446e07c6288e266bd06d7b5899eee5 (diff)
downloadlibmcp-main.zip
Discipline MCP timestamp text projectionsHEADmain
-rw-r--r--Cargo.lock53
-rw-r--r--Cargo.toml1
-rw-r--r--crates/libmcp/Cargo.toml1
-rw-r--r--crates/libmcp/src/lib.rs2
-rw-r--r--crates/libmcp/src/projection.rs50
5 files changed, 106 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8de3b10..3fe188b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index b071541..623ceec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"));
+ }
}