Skip to main content

wesichain_mcp/
protocol.rs

1//! JSON-RPC 2.0 types + MCP message types (spec 2024-11-05).
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6// ── JSON-RPC 2.0 ─────────────────────────────────────────────────────────────
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct JsonRpcRequest {
10    pub jsonrpc: String,
11    pub id: JsonRpcId,
12    pub method: String,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub params: Option<Value>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(untagged)]
19pub enum JsonRpcId {
20    Number(i64),
21    String(String),
22    Null,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct JsonRpcResponse {
27    pub jsonrpc: String,
28    pub id: JsonRpcId,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub result: Option<Value>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub error: Option<JsonRpcError>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct JsonRpcError {
37    pub code: i32,
38    pub message: String,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub data: Option<Value>,
41}
42
43impl JsonRpcRequest {
44    pub fn new(id: i64, method: impl Into<String>, params: Option<Value>) -> Self {
45        Self {
46            jsonrpc: "2.0".to_string(),
47            id: JsonRpcId::Number(id),
48            method: method.into(),
49            params,
50        }
51    }
52}
53
54// ── MCP types — tools ─────────────────────────────────────────────────────────
55
56/// A tool advertised by an MCP server.
57#[derive(Debug, Clone, Deserialize)]
58pub struct McpToolSpec {
59    pub name: String,
60    pub description: String,
61    #[serde(rename = "inputSchema")]
62    pub input_schema: Value,
63}
64
65/// Wrapper around the MCP `tools/list` response.
66#[derive(Debug, Deserialize)]
67pub struct McpToolsListResult {
68    pub tools: Vec<McpToolSpec>,
69}
70
71/// Wrapper around the MCP `tools/call` response.
72#[derive(Debug, Deserialize)]
73pub struct McpToolCallResult {
74    pub content: Vec<McpContent>,
75    #[serde(default)]
76    pub is_error: bool,
77}
78
79#[derive(Debug, Deserialize)]
80pub struct McpContent {
81    #[serde(rename = "type")]
82    pub content_type: String,
83    #[serde(default)]
84    pub text: String,
85}
86
87// ── MCP types — resources ─────────────────────────────────────────────────────
88
89/// A resource advertised by an MCP server (`resources/list`).
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct McpResourceSpec {
92    /// Stable URI identifying the resource (e.g. `file:///src/main.rs`).
93    pub uri: String,
94    /// Human-readable name.
95    pub name: String,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub description: Option<String>,
98    /// MIME type hint (e.g. `text/x-rust`).
99    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
100    pub mime_type: Option<String>,
101}
102
103/// Wrapper around the `resources/list` response.
104#[derive(Debug, Deserialize)]
105pub struct McpResourcesListResult {
106    pub resources: Vec<McpResourceSpec>,
107}
108
109/// A single piece of content returned by `resources/read`.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct McpResourceContent {
112    pub uri: String,
113    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
114    pub mime_type: Option<String>,
115    /// UTF-8 text content (for text resources).
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub text: Option<String>,
118    /// Base64-encoded binary content (for blob resources).
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub blob: Option<String>,
121}
122
123/// Wrapper around the `resources/read` response.
124#[derive(Debug, Deserialize)]
125pub struct McpResourceReadResult {
126    pub contents: Vec<McpResourceContent>,
127}
128
129// ── MCP types — sampling ──────────────────────────────────────────────────────
130
131/// A message in a `sampling/createMessage` request.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SamplingMessage {
134    pub role: String,
135    pub content: SamplingContent,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139#[serde(tag = "type", rename_all = "snake_case")]
140pub enum SamplingContent {
141    Text { text: String },
142}
143
144impl SamplingMessage {
145    pub fn user(text: impl Into<String>) -> Self {
146        Self { role: "user".to_string(), content: SamplingContent::Text { text: text.into() } }
147    }
148
149    pub fn assistant(text: impl Into<String>) -> Self {
150        Self { role: "assistant".to_string(), content: SamplingContent::Text { text: text.into() } }
151    }
152}
153
154/// Parameters for `sampling/createMessage`.
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct SamplingRequest {
157    pub messages: Vec<SamplingMessage>,
158    #[serde(rename = "maxTokens")]
159    pub max_tokens: u32,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub system: Option<String>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub temperature: Option<f32>,
164}
165
166/// Response from `sampling/createMessage`.
167#[derive(Debug, Clone, Deserialize)]
168pub struct SamplingResult {
169    pub role: String,
170    pub content: SamplingContent,
171    pub model: String,
172    #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
173    pub stop_reason: Option<String>,
174}