use rust_mcp::McpServer;
use serde_json::{json, Value};
use std::error::Error;
use reqwest::Client;
struct OpenAiClient {
client: Client,
api_key: String,
}
impl OpenAiClient {
fn new(api_key: String) -> Self {
OpenAiClient {
client: Client::new(),
api_key,
}
}
async fn call_model(&self, prompt: &str, _max_tokens: u32) -> Result<String, String> {
let response = self.client
.post("https://api.openai.com/v1/responses")
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.json(&json!({
"model": "o3", "input": [
{
"role": "user",
"content": [
{
"type": "input_text",
"text": prompt
}
]
}
],
"text": {
"format": {
"type": "text"
}
},
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"tools": [],
"store": true
}))
.send()
.await
.map_err(|e| format!("API request failed: {}", e))?;
let status = response.status();
if !status.is_success() {
let error_text = response.text().await
.map_err(|e| format!("Failed to read error response: {}", e))?;
return Err(format!("API returned error ({}): {}", status, error_text));
}
let response_body = response.text()
.await
.map_err(|e| format!("Failed to read response body: {}", e))?;
eprintln!("OpenAI API Response: {}", response_body);
let response_json: Value = serde_json::from_str(&response_body)
.map_err(|e| format!("Failed to parse response as JSON: {}", e))?;
if response_json.get("error").and_then(|e| e.as_object()).is_some() {
return Err(format!("API returned error in response: {}",
response_json.get("error").unwrap()));
}
let content = response_json.get("output")
.and_then(|output| output.as_array())
.and_then(|output_items| {
output_items.iter()
.find(|item| item.get("type").and_then(|t| t.as_str()) == Some("message"))
.and_then(|message| message.get("content"))
.and_then(|content_array| content_array.as_array())
.and_then(|content_items| {
content_items.iter()
.find(|item| item.get("type").and_then(|t| t.as_str()) == Some("output_text"))
.and_then(|text_item| text_item.get("text").and_then(|t| t.as_str()))
})
})
.unwrap_or("");
let completion = content.trim().to_string();
Ok(completion)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let api_key = std::env::var("OPENAI_API_KEY")
.expect("OPENAI_API_KEY environment variable must be set");
let openai_client = OpenAiClient::new(api_key);
let mut server = McpServer::new("openai-o3", "0.1.0");
server.add_tool(
"complete",
"Generate text using OpenAI's model",
json!({
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "The text prompt to send to the model"
},
"max_tokens": {
"type": "number",
"description": "Maximum number of tokens to generate",
"default": 256
}
},
"required": ["prompt"]
}),
move |args| {
let client = openai_client.client.clone();
let api_key = openai_client.api_key.clone();
async move {
let prompt = match args.get("prompt") {
Some(Value::String(text)) => text.clone(),
_ => return Err("Missing or invalid prompt".to_string()),
};
let max_tokens = match args.get("max_tokens") {
Some(Value::Number(n)) => n.as_u64().unwrap_or(256) as u32,
_ => 256, };
let openai = OpenAiClient { client, api_key };
let completion = openai.call_model(&prompt, max_tokens).await?;
Ok(json!(completion))
}
},
);
server.run_stdio().await?;
Ok(())
}