- Fix f32 comparison: use partial_cmp instead of cmp - Add Datelike import for ordinal() method - Remove unused imports (Arc, Mutex) - Fix unused variable warning (user_type -> _user_type) All errors and most warnings are now resolved.
602 lines
23 KiB
Rust
602 lines
23 KiB
Rust
use anyhow::Result;
|
|
use serde_json::{json, Value};
|
|
use std::io::{self, BufRead, Write};
|
|
|
|
use crate::memory::MemoryManager;
|
|
use crate::game_formatter::{GameFormatter, DiagnosisType};
|
|
use crate::companion::{Companion, CompanionPersonality, CompanionFormatter};
|
|
|
|
pub struct BaseMCPServer {
|
|
pub memory_manager: MemoryManager,
|
|
pub companion: Option<Companion>, // 恋愛コンパニオン(オプション)
|
|
}
|
|
|
|
impl BaseMCPServer {
|
|
pub async fn new() -> Result<Self> {
|
|
let memory_manager = MemoryManager::new().await?;
|
|
Ok(BaseMCPServer {
|
|
memory_manager,
|
|
companion: None, // 初期状態はコンパニオンなし
|
|
})
|
|
}
|
|
|
|
pub async fn run(&mut self) -> Result<()> {
|
|
let stdin = io::stdin();
|
|
let mut stdout = io::stdout();
|
|
|
|
let reader = stdin.lock();
|
|
let lines = reader.lines();
|
|
|
|
for line_result in lines {
|
|
match line_result {
|
|
Ok(line) => {
|
|
let trimmed = line.trim();
|
|
if trimmed.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
if let Ok(request) = serde_json::from_str::<Value>(&trimmed) {
|
|
let response = self.handle_request(request).await;
|
|
let response_str = serde_json::to_string(&response)?;
|
|
stdout.write_all(response_str.as_bytes())?;
|
|
stdout.write_all(b"\n")?;
|
|
stdout.flush()?;
|
|
}
|
|
}
|
|
Err(_) => break,
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn handle_request(&mut self, request: Value) -> Value {
|
|
let method = request["method"].as_str().unwrap_or("");
|
|
let id = request["id"].clone();
|
|
|
|
match method {
|
|
"initialize" => self.handle_initialize(id),
|
|
"tools/list" => self.handle_tools_list(id),
|
|
"tools/call" => self.handle_tools_call(request, id).await,
|
|
_ => self.handle_unknown_method(id),
|
|
}
|
|
}
|
|
|
|
// 初期化ハンドラ
|
|
fn handle_initialize(&self, id: Value) -> Value {
|
|
json!({
|
|
"jsonrpc": "2.0",
|
|
"id": id,
|
|
"result": {
|
|
"protocolVersion": "2024-11-05",
|
|
"capabilities": {
|
|
"tools": {}
|
|
},
|
|
"serverInfo": {
|
|
"name": "aigpt",
|
|
"version": "0.1.0"
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// ツールリストハンドラ (拡張可能)
|
|
pub fn handle_tools_list(&self, id: Value) -> Value {
|
|
let tools = self.get_available_tools();
|
|
json!({
|
|
"jsonrpc": "2.0",
|
|
"id": id,
|
|
"result": {
|
|
"tools": tools
|
|
}
|
|
})
|
|
}
|
|
|
|
// 基本ツール定義 (拡張で上書き可能)
|
|
pub fn get_available_tools(&self) -> Vec<Value> {
|
|
vec![
|
|
json!({
|
|
"name": "create_memory",
|
|
"description": "Create a new memory entry. Simple version with default score (0.5).",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"content": {
|
|
"type": "string",
|
|
"description": "Content of the memory"
|
|
},
|
|
"game_mode": {
|
|
"type": "boolean",
|
|
"description": "Show game-style result (default: true)"
|
|
}
|
|
},
|
|
"required": ["content"]
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "search_memories",
|
|
"description": "Search memories by content",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"query": {
|
|
"type": "string",
|
|
"description": "Search query"
|
|
}
|
|
},
|
|
"required": ["query"]
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "update_memory",
|
|
"description": "Update an existing memory entry",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {
|
|
"type": "string",
|
|
"description": "ID of the memory to update"
|
|
},
|
|
"content": {
|
|
"type": "string",
|
|
"description": "New content for the memory"
|
|
}
|
|
},
|
|
"required": ["id", "content"]
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "delete_memory",
|
|
"description": "Delete a memory entry",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {
|
|
"type": "string",
|
|
"description": "ID of the memory to delete"
|
|
}
|
|
},
|
|
"required": ["id"]
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "list_conversations",
|
|
"description": "List all imported conversations",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {}
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "create_memory_with_ai",
|
|
"description": "✨ RECOMMENDED: Create a memory with psychological priority scoring and game-style results! \n\nHow to use:\n1. Interpret the user's input and extract deeper meaning\n2. Calculate priority_score (0.0-1.0) by adding these 4 scores:\n - Emotional impact (0.0-0.25): How emotionally significant?\n - User relevance (0.0-0.25): How relevant to the user?\n - Novelty/uniqueness (0.0-0.25): How new or unique?\n - Practical utility (0.0-0.25): How useful?\n3. Pass content (original), interpreted_content (your interpretation), and priority_score to this tool\n4. You'll get a game-style result with rarity (Common/Rare/Epic/Legendary), diagnosis type, and XP!\n\nExample:\n- User says: 'Working on AI features today'\n- You interpret: 'User is actively developing autonomous AI capabilities for their project'\n- You score: 0.75 (emotion:0.15 + relevance:0.20 + novelty:0.20 + utility:0.20)\n- System returns: '🟣 EPIC 75点 💡 革新者タイプ!'",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"content": {
|
|
"type": "string",
|
|
"description": "Original user content"
|
|
},
|
|
"interpreted_content": {
|
|
"type": "string",
|
|
"description": "Your interpretation of the content (extract deeper meaning)"
|
|
},
|
|
"priority_score": {
|
|
"type": "number",
|
|
"description": "Priority score 0.0-1.0 (sum of 4 criteria, each 0.0-0.25)",
|
|
"minimum": 0.0,
|
|
"maximum": 1.0
|
|
},
|
|
"user_context": {
|
|
"type": "string",
|
|
"description": "User-specific context (optional)"
|
|
},
|
|
"game_mode": {
|
|
"type": "boolean",
|
|
"description": "Show game-style result (default: true)"
|
|
}
|
|
},
|
|
"required": ["content", "interpreted_content", "priority_score"]
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "list_memories_by_priority",
|
|
"description": "List memories sorted by priority score (high to low) - Shows as ranking!",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"min_score": {
|
|
"type": "number",
|
|
"description": "Minimum priority score (0.0-1.0)",
|
|
"minimum": 0.0,
|
|
"maximum": 1.0
|
|
},
|
|
"limit": {
|
|
"type": "integer",
|
|
"description": "Maximum number of memories to return"
|
|
},
|
|
"game_mode": {
|
|
"type": "boolean",
|
|
"description": "Show as game-style ranking (default: true)"
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "daily_challenge",
|
|
"description": "Get today's daily challenge - Create a memory to earn bonus XP!",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {}
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "create_companion",
|
|
"description": "Create your AI companion - Choose name and personality!",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Companion's name"
|
|
},
|
|
"personality": {
|
|
"type": "string",
|
|
"enum": ["energetic", "intellectual", "practical", "dreamy", "balanced"],
|
|
"description": "Companion's personality type"
|
|
}
|
|
},
|
|
"required": ["name", "personality"]
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "companion_react",
|
|
"description": "Show your companion's reaction to your latest memory",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"memory_id": {
|
|
"type": "string",
|
|
"description": "Memory ID to react to"
|
|
}
|
|
},
|
|
"required": ["memory_id"]
|
|
}
|
|
}),
|
|
json!({
|
|
"name": "companion_profile",
|
|
"description": "View your companion's profile and stats",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {}
|
|
}
|
|
})
|
|
]
|
|
}
|
|
|
|
// ツール呼び出しハンドラ
|
|
async fn handle_tools_call(&mut self, request: Value, id: Value) -> Value {
|
|
let tool_name = request["params"]["name"].as_str().unwrap_or("");
|
|
let arguments = &request["params"]["arguments"];
|
|
|
|
let result = self.execute_tool(tool_name, arguments).await;
|
|
|
|
json!({
|
|
"jsonrpc": "2.0",
|
|
"id": id,
|
|
"result": {
|
|
"content": [{
|
|
"type": "text",
|
|
"text": result.to_string()
|
|
}]
|
|
}
|
|
})
|
|
}
|
|
|
|
// ツール実行 (拡張で上書き可能)
|
|
pub async fn execute_tool(&mut self, tool_name: &str, arguments: &Value) -> Value {
|
|
match tool_name {
|
|
"create_memory" => self.tool_create_memory(arguments),
|
|
"create_memory_with_ai" => self.tool_create_memory_with_ai(arguments).await,
|
|
"list_memories_by_priority" => self.tool_list_memories_by_priority(arguments),
|
|
"daily_challenge" => self.tool_daily_challenge(),
|
|
"create_companion" => self.tool_create_companion(arguments),
|
|
"companion_react" => self.tool_companion_react(arguments),
|
|
"companion_profile" => self.tool_companion_profile(),
|
|
"search_memories" => self.tool_search_memories(arguments),
|
|
"update_memory" => self.tool_update_memory(arguments),
|
|
"delete_memory" => self.tool_delete_memory(arguments),
|
|
"list_conversations" => self.tool_list_conversations(),
|
|
_ => json!({
|
|
"success": false,
|
|
"error": format!("Unknown tool: {}", tool_name)
|
|
})
|
|
}
|
|
}
|
|
|
|
// 基本ツール実装
|
|
fn tool_create_memory(&mut self, arguments: &Value) -> Value {
|
|
let content = arguments["content"].as_str().unwrap_or("");
|
|
let game_mode = arguments["game_mode"].as_bool().unwrap_or(true); // デフォルトON
|
|
|
|
match self.memory_manager.create_memory(content) {
|
|
Ok(id) => {
|
|
if let Some(memory) = self.memory_manager.get_memory(&id) {
|
|
let result = if game_mode {
|
|
GameFormatter::format_memory_result(memory)
|
|
} else {
|
|
format!("Memory created (ID: {})", id)
|
|
};
|
|
|
|
json!({
|
|
"success": true,
|
|
"id": id,
|
|
"game_result": result,
|
|
"message": "Memory created successfully"
|
|
})
|
|
} else {
|
|
json!({
|
|
"success": true,
|
|
"id": id,
|
|
"message": "Memory created successfully"
|
|
})
|
|
}
|
|
}
|
|
Err(e) => json!({
|
|
"success": false,
|
|
"error": e.to_string()
|
|
})
|
|
}
|
|
}
|
|
|
|
fn tool_search_memories(&self, arguments: &Value) -> Value {
|
|
let query = arguments["query"].as_str().unwrap_or("");
|
|
let memories = self.memory_manager.search_memories(query);
|
|
json!({
|
|
"success": true,
|
|
"memories": memories.into_iter().map(|m| json!({
|
|
"id": m.id,
|
|
"content": m.content,
|
|
"interpreted_content": m.interpreted_content,
|
|
"priority_score": m.priority_score,
|
|
"user_context": m.user_context,
|
|
"created_at": m.created_at,
|
|
"updated_at": m.updated_at
|
|
})).collect::<Vec<_>>()
|
|
})
|
|
}
|
|
|
|
fn tool_update_memory(&mut self, arguments: &Value) -> Value {
|
|
let id = arguments["id"].as_str().unwrap_or("");
|
|
let content = arguments["content"].as_str().unwrap_or("");
|
|
match self.memory_manager.update_memory(id, content) {
|
|
Ok(()) => json!({
|
|
"success": true,
|
|
"message": "Memory updated successfully"
|
|
}),
|
|
Err(e) => json!({
|
|
"success": false,
|
|
"error": e.to_string()
|
|
})
|
|
}
|
|
}
|
|
|
|
fn tool_delete_memory(&mut self, arguments: &Value) -> Value {
|
|
let id = arguments["id"].as_str().unwrap_or("");
|
|
match self.memory_manager.delete_memory(id) {
|
|
Ok(()) => json!({
|
|
"success": true,
|
|
"message": "Memory deleted successfully"
|
|
}),
|
|
Err(e) => json!({
|
|
"success": false,
|
|
"error": e.to_string()
|
|
})
|
|
}
|
|
}
|
|
|
|
fn tool_list_conversations(&self) -> Value {
|
|
let conversations = self.memory_manager.list_conversations();
|
|
json!({
|
|
"success": true,
|
|
"conversations": conversations.into_iter().map(|c| json!({
|
|
"id": c.id,
|
|
"title": c.title,
|
|
"created_at": c.created_at,
|
|
"message_count": c.message_count
|
|
})).collect::<Vec<_>>()
|
|
})
|
|
}
|
|
|
|
// AI解釈付きメモリ作成
|
|
async fn tool_create_memory_with_ai(&mut self, arguments: &Value) -> Value {
|
|
let content = arguments["content"].as_str().unwrap_or("");
|
|
let interpreted_content = arguments["interpreted_content"].as_str().unwrap_or(content);
|
|
let priority_score = arguments["priority_score"].as_f64().unwrap_or(0.5) as f32;
|
|
let user_context = arguments["user_context"].as_str();
|
|
let game_mode = arguments["game_mode"].as_bool().unwrap_or(true);
|
|
|
|
// Claude Code から受け取った解釈とスコアでメモリを作成
|
|
match self.memory_manager.create_memory_with_interpretation(
|
|
content,
|
|
interpreted_content,
|
|
priority_score,
|
|
user_context
|
|
) {
|
|
Ok(id) => {
|
|
// 作成したメモリを取得して詳細情報を返す
|
|
if let Some(memory) = self.memory_manager.get_memory(&id) {
|
|
let result = if game_mode {
|
|
// ゲーム風表示
|
|
GameFormatter::format_memory_result(memory)
|
|
} else {
|
|
// 通常表示
|
|
format!("Memory created with AI interpretation\nScore: {}", memory.priority_score)
|
|
};
|
|
|
|
let shareable = GameFormatter::format_shareable_text(memory);
|
|
|
|
json!({
|
|
"success": true,
|
|
"id": id,
|
|
"memory": {
|
|
"content": memory.content,
|
|
"interpreted_content": memory.interpreted_content,
|
|
"priority_score": memory.priority_score,
|
|
"user_context": memory.user_context,
|
|
"created_at": memory.created_at
|
|
},
|
|
"game_result": result,
|
|
"shareable_text": shareable,
|
|
"message": "Memory created with Claude Code's interpretation and priority scoring!"
|
|
})
|
|
} else {
|
|
json!({
|
|
"success": true,
|
|
"id": id,
|
|
"message": "Memory created"
|
|
})
|
|
}
|
|
}
|
|
Err(e) => json!({
|
|
"success": false,
|
|
"error": e.to_string()
|
|
})
|
|
}
|
|
}
|
|
|
|
// 優先順位順にメモリをリスト
|
|
fn tool_list_memories_by_priority(&self, arguments: &Value) -> Value {
|
|
let min_score = arguments["min_score"].as_f64().unwrap_or(0.0) as f32;
|
|
let limit = arguments["limit"].as_u64().map(|l| l as usize);
|
|
let game_mode = arguments["game_mode"].as_bool().unwrap_or(true);
|
|
|
|
let mut memories = self.memory_manager.get_memories_by_priority();
|
|
|
|
// min_scoreでフィルタリング
|
|
memories.retain(|m| m.priority_score >= min_score);
|
|
|
|
// limitを適用
|
|
if let Some(limit) = limit {
|
|
memories.truncate(limit);
|
|
}
|
|
|
|
let ranking_display = if game_mode {
|
|
GameFormatter::format_ranking(&memories, "🏆 メモリーランキング TOP 10")
|
|
} else {
|
|
String::new()
|
|
};
|
|
|
|
json!({
|
|
"success": true,
|
|
"count": memories.len(),
|
|
"ranking_display": ranking_display,
|
|
"memories": memories.into_iter().map(|m| json!({
|
|
"id": m.id,
|
|
"content": m.content,
|
|
"interpreted_content": m.interpreted_content,
|
|
"priority_score": m.priority_score,
|
|
"user_context": m.user_context,
|
|
"created_at": m.created_at,
|
|
"updated_at": m.updated_at
|
|
})).collect::<Vec<_>>()
|
|
})
|
|
}
|
|
|
|
// デイリーチャレンジ
|
|
fn tool_daily_challenge(&self) -> Value {
|
|
let challenge_display = GameFormatter::format_daily_challenge();
|
|
|
|
json!({
|
|
"success": true,
|
|
"challenge_display": challenge_display,
|
|
"message": "Complete today's challenge to earn bonus XP!"
|
|
})
|
|
}
|
|
|
|
// コンパニオン作成
|
|
fn tool_create_companion(&mut self, arguments: &Value) -> Value {
|
|
let name = arguments["name"].as_str().unwrap_or("エミリー");
|
|
let personality_str = arguments["personality"].as_str().unwrap_or("balanced");
|
|
|
|
let personality = match personality_str {
|
|
"energetic" => CompanionPersonality::Energetic,
|
|
"intellectual" => CompanionPersonality::Intellectual,
|
|
"practical" => CompanionPersonality::Practical,
|
|
"dreamy" => CompanionPersonality::Dreamy,
|
|
_ => CompanionPersonality::Balanced,
|
|
};
|
|
|
|
let companion = Companion::new(name.to_string(), personality);
|
|
let profile = CompanionFormatter::format_profile(&companion);
|
|
|
|
self.companion = Some(companion);
|
|
|
|
json!({
|
|
"success": true,
|
|
"profile": profile,
|
|
"message": format!("{}があなたのコンパニオンになりました!", name)
|
|
})
|
|
}
|
|
|
|
// コンパニオンの反応
|
|
fn tool_companion_react(&mut self, arguments: &Value) -> Value {
|
|
if self.companion.is_none() {
|
|
return json!({
|
|
"success": false,
|
|
"error": "コンパニオンが作成されていません。create_companionツールで作成してください。"
|
|
});
|
|
}
|
|
|
|
let memory_id = arguments["memory_id"].as_str().unwrap_or("");
|
|
|
|
if let Some(memory) = self.memory_manager.get_memory(memory_id) {
|
|
let user_type = DiagnosisType::from_memory(memory);
|
|
let companion = self.companion.as_mut().unwrap();
|
|
let reaction = companion.react_to_memory(memory, &user_type);
|
|
let reaction_display = CompanionFormatter::format_reaction(companion, &reaction);
|
|
|
|
json!({
|
|
"success": true,
|
|
"reaction_display": reaction_display,
|
|
"affection_gained": reaction.affection_gained,
|
|
"xp_gained": reaction.xp_gained,
|
|
"level_up": reaction.level_up,
|
|
"message": "コンパニオンが反応しました!"
|
|
})
|
|
} else {
|
|
json!({
|
|
"success": false,
|
|
"error": format!("Memory not found: {}", memory_id)
|
|
})
|
|
}
|
|
}
|
|
|
|
// コンパニオンプロフィール
|
|
fn tool_companion_profile(&self) -> Value {
|
|
if let Some(ref companion) = self.companion {
|
|
let profile = CompanionFormatter::format_profile(companion);
|
|
json!({
|
|
"success": true,
|
|
"profile": profile
|
|
})
|
|
} else {
|
|
json!({
|
|
"success": false,
|
|
"error": "コンパニオンが作成されていません。create_companionツールで作成してください。"
|
|
})
|
|
}
|
|
}
|
|
|
|
// 不明なメソッドハンドラ
|
|
fn handle_unknown_method(&self, id: Value) -> Value {
|
|
json!({
|
|
"jsonrpc": "2.0",
|
|
"id": id,
|
|
"error": {
|
|
"code": -32601,
|
|
"message": "Method not found"
|
|
}
|
|
})
|
|
}
|
|
} |