use crate::{
ElicitCommunicator, ElicitError, ElicitErrorKind, ElicitIntrospect, ElicitResult, Elicitation,
ElicitationPattern, Generator, PatternDetails, Prompt, Select, TypeMetadata,
datetime_common::{DateTimeComponents, DateTimeInputMethod},
mcp,
};
use chrono::{DateTime, Duration, FixedOffset, NaiveDateTime, TimeZone, Utc};
crate::default_style!(DateTime<Utc> => DateTimeUtcStyle);
crate::default_style!(DateTime<FixedOffset> => DateTimeFixedOffsetStyle);
crate::default_style!(NaiveDateTime => NaiveDateTimeStyle);
crate::default_style!(DateTimeUtcGenerationMode => DateTimeUtcGenerationModeStyle);
crate::default_style!(NaiveDateTimeGenerationMode => NaiveDateTimeGenerationModeStyle);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DateTimeUtcGenerationMode {
Now,
UnixEpoch,
Offset {
seconds: i64,
},
}
impl Select for DateTimeUtcGenerationMode {
fn options() -> Vec<Self> {
vec![
DateTimeUtcGenerationMode::Now,
DateTimeUtcGenerationMode::UnixEpoch,
DateTimeUtcGenerationMode::Offset { seconds: 0 },
]
}
fn labels() -> Vec<String> {
vec![
"Now (Current UTC)".to_string(),
"Unix Epoch (1970-01-01)".to_string(),
"Offset (Custom)".to_string(),
]
}
fn from_label(label: &str) -> Option<Self> {
match label {
"Now (Current UTC)" => Some(DateTimeUtcGenerationMode::Now),
"Unix Epoch (1970-01-01)" => Some(DateTimeUtcGenerationMode::UnixEpoch),
"Offset (Custom)" => Some(DateTimeUtcGenerationMode::Offset { seconds: 0 }),
_ => None,
}
}
}
impl Prompt for DateTimeUtcGenerationMode {
fn prompt() -> Option<&'static str> {
Some("How should UTC datetimes be generated?")
}
}
impl Elicitation for DateTimeUtcGenerationMode {
type Style = DateTimeUtcGenerationModeStyle;
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let params = mcp::select_params(
Self::prompt().unwrap_or("Select an option:"),
&Self::labels(),
);
let result = communicator
.call_tool(rmcp::model::CallToolRequestParams {
meta: None,
name: mcp::tool_names::elicit_select().into(),
arguments: Some(params),
task: None,
})
.await?;
let value = mcp::extract_value(result)?;
let label = mcp::parse_string(value)?;
let selected = Self::from_label(&label).ok_or_else(|| {
ElicitError::new(ElicitErrorKind::ParseError(
"Invalid DateTime<Utc> generation mode".to_string(),
))
})?;
match selected {
DateTimeUtcGenerationMode::Now => Ok(DateTimeUtcGenerationMode::Now),
DateTimeUtcGenerationMode::UnixEpoch => Ok(DateTimeUtcGenerationMode::UnixEpoch),
DateTimeUtcGenerationMode::Offset { .. } => {
let seconds = i64::elicit(communicator).await?;
Ok(DateTimeUtcGenerationMode::Offset { seconds })
}
}
}
}
impl ElicitIntrospect for DateTimeUtcGenerationMode {
fn pattern() -> ElicitationPattern {
ElicitationPattern::Select
}
fn metadata() -> TypeMetadata {
TypeMetadata {
type_name: "DateTimeUtcGenerationMode",
description: Self::prompt(),
details: PatternDetails::Select {
options: Self::labels(),
},
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DateTimeUtcGenerator {
mode: DateTimeUtcGenerationMode,
reference: DateTime<Utc>,
}
impl DateTimeUtcGenerator {
pub fn new(mode: DateTimeUtcGenerationMode) -> Self {
Self {
mode,
reference: Utc::now(),
}
}
pub fn with_reference(mode: DateTimeUtcGenerationMode, reference: DateTime<Utc>) -> Self {
Self { mode, reference }
}
pub fn mode(&self) -> DateTimeUtcGenerationMode {
self.mode
}
pub fn reference(&self) -> DateTime<Utc> {
self.reference
}
}
impl Generator for DateTimeUtcGenerator {
type Target = DateTime<Utc>;
fn generate(&self) -> Self::Target {
match self.mode {
DateTimeUtcGenerationMode::Now => Utc::now(),
DateTimeUtcGenerationMode::UnixEpoch => DateTime::UNIX_EPOCH,
DateTimeUtcGenerationMode::Offset { seconds } => {
if seconds >= 0 {
self.reference + Duration::try_seconds(seconds).unwrap_or(Duration::zero())
} else {
self.reference - Duration::try_seconds(-seconds).unwrap_or(Duration::zero())
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NaiveDateTimeGenerationMode {
Now,
UnixEpoch,
Offset {
seconds: i64,
},
}
impl Select for NaiveDateTimeGenerationMode {
fn options() -> Vec<Self> {
vec![
NaiveDateTimeGenerationMode::Now,
NaiveDateTimeGenerationMode::UnixEpoch,
NaiveDateTimeGenerationMode::Offset { seconds: 0 },
]
}
fn labels() -> Vec<String> {
vec![
"Now (Current time)".to_string(),
"Unix Epoch (1970-01-01)".to_string(),
"Offset (Custom)".to_string(),
]
}
fn from_label(label: &str) -> Option<Self> {
match label {
"Now (Current time)" => Some(NaiveDateTimeGenerationMode::Now),
"Unix Epoch (1970-01-01)" => Some(NaiveDateTimeGenerationMode::UnixEpoch),
"Offset (Custom)" => Some(NaiveDateTimeGenerationMode::Offset { seconds: 0 }),
_ => None,
}
}
}
impl Prompt for NaiveDateTimeGenerationMode {
fn prompt() -> Option<&'static str> {
Some("How should naive datetimes be generated?")
}
}
impl Elicitation for NaiveDateTimeGenerationMode {
type Style = NaiveDateTimeGenerationModeStyle;
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let params = mcp::select_params(
Self::prompt().unwrap_or("Select an option:"),
&Self::labels(),
);
let result = communicator
.call_tool(rmcp::model::CallToolRequestParams {
meta: None,
name: mcp::tool_names::elicit_select().into(),
arguments: Some(params),
task: None,
})
.await?;
let value = mcp::extract_value(result)?;
let label = mcp::parse_string(value)?;
let selected = Self::from_label(&label).ok_or_else(|| {
ElicitError::new(ElicitErrorKind::ParseError(
"Invalid NaiveDateTime generation mode".to_string(),
))
})?;
match selected {
NaiveDateTimeGenerationMode::Now => Ok(NaiveDateTimeGenerationMode::Now),
NaiveDateTimeGenerationMode::UnixEpoch => Ok(NaiveDateTimeGenerationMode::UnixEpoch),
NaiveDateTimeGenerationMode::Offset { .. } => {
let seconds = i64::elicit(communicator).await?;
Ok(NaiveDateTimeGenerationMode::Offset { seconds })
}
}
}
}
impl ElicitIntrospect for NaiveDateTimeGenerationMode {
fn pattern() -> ElicitationPattern {
ElicitationPattern::Select
}
fn metadata() -> TypeMetadata {
TypeMetadata {
type_name: "NaiveDateTimeGenerationMode",
description: Self::prompt(),
details: PatternDetails::Select {
options: Self::labels(),
},
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct NaiveDateTimeGenerator {
mode: NaiveDateTimeGenerationMode,
reference: NaiveDateTime,
}
impl NaiveDateTimeGenerator {
pub fn new(mode: NaiveDateTimeGenerationMode) -> Self {
Self {
mode,
reference: Utc::now().naive_utc(),
}
}
pub fn with_reference(mode: NaiveDateTimeGenerationMode, reference: NaiveDateTime) -> Self {
Self { mode, reference }
}
pub fn mode(&self) -> NaiveDateTimeGenerationMode {
self.mode
}
pub fn reference(&self) -> NaiveDateTime {
self.reference
}
}
impl Generator for NaiveDateTimeGenerator {
type Target = NaiveDateTime;
fn generate(&self) -> Self::Target {
match self.mode {
NaiveDateTimeGenerationMode::Now => Utc::now().naive_utc(),
NaiveDateTimeGenerationMode::UnixEpoch => DateTime::UNIX_EPOCH.naive_utc(),
NaiveDateTimeGenerationMode::Offset { seconds } => {
if seconds >= 0 {
self.reference + Duration::try_seconds(seconds).unwrap_or(Duration::zero())
} else {
self.reference - Duration::try_seconds(-seconds).unwrap_or(Duration::zero())
}
}
}
}
}
impl Prompt for DateTime<Utc> {
fn prompt() -> Option<&'static str> {
Some("Enter UTC datetime:")
}
}
impl Elicitation for DateTime<Utc> {
type Style = DateTimeUtcStyle;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting DateTime<Utc>");
let method = DateTimeInputMethod::elicit(communicator).await?;
tracing::debug!(?method, "Input method selected");
match method {
DateTimeInputMethod::Iso8601String => {
let prompt = "Enter ISO 8601 datetime (e.g., \"2024-07-11T15:30:00Z\"):";
let params = mcp::text_params(prompt);
let result = communicator
.call_tool(rmcp::model::CallToolRequestParams {
meta: None,
name: mcp::tool_names::elicit_text().into(),
arguments: Some(params),
task: None,
})
.await?;
let value = mcp::extract_value(result)?;
let iso_string = mcp::parse_string(value)?;
DateTime::parse_from_rfc3339(&iso_string)
.map(|dt| dt.with_timezone(&Utc))
.map_err(|e| {
ElicitError::new(ElicitErrorKind::ParseError(format!(
"Invalid ISO 8601 datetime: {}",
e
)))
})
}
DateTimeInputMethod::ManualComponents => {
let components = DateTimeComponents::elicit(communicator).await?;
Utc.with_ymd_and_hms(
components.year,
components.month as u32,
components.day as u32,
components.hour as u32,
components.minute as u32,
components.second as u32,
)
.single()
.ok_or_else(|| {
ElicitError::new(ElicitErrorKind::ParseError(format!(
"Invalid datetime components: {}-{:02}-{:02} {:02}:{:02}:{:02}",
components.year,
components.month,
components.day,
components.hour,
components.minute,
components.second
)))
})
}
}
}
#[cfg(kani)]
fn kani_proof() {
DateTimeInputMethod::kani_proof();
DateTimeComponents::kani_proof();
assert!(
true,
"chrono::DateTime<Utc> verified via component composition + trusted chrono crate"
);
}
}
impl ElicitIntrospect for DateTime<Utc> {
fn pattern() -> ElicitationPattern {
ElicitationPattern::Primitive
}
fn metadata() -> TypeMetadata {
TypeMetadata {
type_name: "chrono::DateTime<Utc>",
description: Self::prompt(),
details: PatternDetails::Primitive,
}
}
}
impl Prompt for DateTime<FixedOffset> {
fn prompt() -> Option<&'static str> {
Some("Enter datetime with timezone offset:")
}
}
impl Elicitation for DateTime<FixedOffset> {
type Style = DateTimeFixedOffsetStyle;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting DateTime<FixedOffset>");
let method = DateTimeInputMethod::elicit(communicator).await?;
tracing::debug!(?method, "Input method selected");
match method {
DateTimeInputMethod::Iso8601String => {
let prompt =
"Enter ISO 8601 datetime with offset (e.g., \"2024-07-11T15:30:00+05:00\"):";
let params = mcp::text_params(prompt);
let result = communicator
.call_tool(rmcp::model::CallToolRequestParams {
meta: None,
name: mcp::tool_names::elicit_text().into(),
arguments: Some(params),
task: None,
})
.await?;
let value = mcp::extract_value(result)?;
let iso_string = mcp::parse_string(value)?;
DateTime::parse_from_rfc3339(&iso_string).map_err(|e| {
ElicitError::new(ElicitErrorKind::ParseError(format!(
"Invalid ISO 8601 datetime: {}",
e
)))
})
}
DateTimeInputMethod::ManualComponents => {
let components = DateTimeComponents::elicit(communicator).await?;
let offset_prompt = "Enter timezone offset in hours (e.g., +5 or -8):";
let offset_params = mcp::number_params(offset_prompt, -12, 14);
let offset_result = communicator
.call_tool(rmcp::model::CallToolRequestParams {
meta: None,
name: mcp::tool_names::elicit_number().into(),
arguments: Some(offset_params),
task: None,
})
.await?;
let offset_value = mcp::extract_value(offset_result)?;
let offset_hours = mcp::parse_integer::<i64>(offset_value)? as i32;
let offset = FixedOffset::east_opt(offset_hours * 3600).ok_or_else(|| {
ElicitError::new(ElicitErrorKind::ParseError(format!(
"Invalid timezone offset: {} hours",
offset_hours
)))
})?;
offset
.with_ymd_and_hms(
components.year,
components.month as u32,
components.day as u32,
components.hour as u32,
components.minute as u32,
components.second as u32,
)
.single()
.ok_or_else(|| {
ElicitError::new(ElicitErrorKind::ParseError(format!(
"Invalid datetime components: {}-{:02}-{:02} {:02}:{:02}:{:02}",
components.year,
components.month,
components.day,
components.hour,
components.minute,
components.second
)))
})
}
}
}
}
impl ElicitIntrospect for DateTime<FixedOffset> {
fn pattern() -> ElicitationPattern {
ElicitationPattern::Primitive
}
fn metadata() -> TypeMetadata {
TypeMetadata {
type_name: "chrono::DateTime<FixedOffset>",
description: Self::prompt(),
details: PatternDetails::Primitive,
}
}
}
impl Prompt for NaiveDateTime {
fn prompt() -> Option<&'static str> {
Some("Enter datetime (no timezone):")
}
}
impl Elicitation for NaiveDateTime {
type Style = NaiveDateTimeStyle;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting NaiveDateTime");
let method = DateTimeInputMethod::elicit(communicator).await?;
tracing::debug!(?method, "Input method selected");
match method {
DateTimeInputMethod::Iso8601String => {
let prompt = "Enter datetime (e.g., \"2024-07-11T15:30:00\"):";
let params = mcp::text_params(prompt);
let result = communicator
.call_tool(rmcp::model::CallToolRequestParams {
meta: None,
name: mcp::tool_names::elicit_text().into(),
arguments: Some(params),
task: None,
})
.await?;
let value = mcp::extract_value(result)?;
let iso_string = mcp::parse_string(value)?;
NaiveDateTime::parse_from_str(&iso_string, "%Y-%m-%dT%H:%M:%S").map_err(|e| {
ElicitError::new(ElicitErrorKind::ParseError(format!(
"Invalid datetime: {}",
e
)))
})
}
DateTimeInputMethod::ManualComponents => {
let components = DateTimeComponents::elicit(communicator).await?;
chrono::NaiveDate::from_ymd_opt(
components.year,
components.month as u32,
components.day as u32,
)
.and_then(|date| {
date.and_hms_opt(
components.hour as u32,
components.minute as u32,
components.second as u32,
)
})
.ok_or_else(|| {
ElicitError::new(ElicitErrorKind::ParseError(format!(
"Invalid datetime components: {}-{:02}-{:02} {:02}:{:02}:{:02}",
components.year,
components.month,
components.day,
components.hour,
components.minute,
components.second
)))
})
}
}
}
}
impl ElicitIntrospect for NaiveDateTime {
fn pattern() -> ElicitationPattern {
ElicitationPattern::Primitive
}
fn metadata() -> TypeMetadata {
TypeMetadata {
type_name: "chrono::NaiveDateTime",
description: Self::prompt(),
details: PatternDetails::Primitive,
}
}
}