fix ref
This commit is contained in:
@@ -12,11 +12,11 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "memory-mcp"
|
name = "memory-mcp"
|
||||||
path = "src/mcp_server.rs"
|
path = "src/bin/mcp_server.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "memory-mcp-extended"
|
name = "memory-mcp-extended"
|
||||||
path = "extended/src/mcp_server.rs"
|
path = "src/bin/mcp_server_extended.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# CLI and async
|
# CLI and async
|
||||||
|
@@ -1,398 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
use std::io::{self, BufRead, Write};
|
|
||||||
|
|
||||||
use aigpt::memory::MemoryManager;
|
|
||||||
|
|
||||||
pub struct ExtendedMCPServer {
|
|
||||||
memory_manager: MemoryManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtendedMCPServer {
|
|
||||||
pub async fn new() -> Result<Self> {
|
|
||||||
let memory_manager = MemoryManager::new().await?;
|
|
||||||
Ok(ExtendedMCPServer { memory_manager })
|
|
||||||
}
|
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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" => {
|
|
||||||
json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": id,
|
|
||||||
"result": {
|
|
||||||
"protocolVersion": "2024-11-05",
|
|
||||||
"capabilities": {
|
|
||||||
"tools": {}
|
|
||||||
},
|
|
||||||
"serverInfo": {
|
|
||||||
"name": "aigpt-extended",
|
|
||||||
"version": "0.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"tools/list" => {
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut tools = vec![
|
|
||||||
// Basic tools
|
|
||||||
json!({
|
|
||||||
"name": "create_memory",
|
|
||||||
"description": "Create a new memory entry",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"content": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Content of the memory"
|
|
||||||
},
|
|
||||||
"analyze": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Enable AI analysis for this memory"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["content"]
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
json!({
|
|
||||||
"name": "search_memories",
|
|
||||||
"description": "Search memories with advanced options",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"query": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Search query"
|
|
||||||
},
|
|
||||||
"semantic": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Use semantic search"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Filter by category"
|
|
||||||
},
|
|
||||||
"time_range": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Filter by time range (e.g., '1week', '1month')"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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"]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add extended tools based on features
|
|
||||||
#[cfg(feature = "web-integration")]
|
|
||||||
{
|
|
||||||
tools.push(json!({
|
|
||||||
"name": "import_webpage",
|
|
||||||
"description": "Import content from a webpage",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"url": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "URL to import from"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["url"]
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
{
|
|
||||||
tools.push(json!({
|
|
||||||
"name": "analyze_sentiment",
|
|
||||||
"description": "Analyze sentiment of memories",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"period": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Time period to analyze"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
tools.push(json!({
|
|
||||||
"name": "extract_insights",
|
|
||||||
"description": "Extract insights and patterns from memories",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"category": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Category to analyze"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": id,
|
|
||||||
"result": {
|
|
||||||
"tools": tools
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"tools/call" => {
|
|
||||||
let tool_name = request["params"]["name"].as_str().unwrap_or("");
|
|
||||||
let arguments = &request["params"]["arguments"];
|
|
||||||
|
|
||||||
let result = match tool_name {
|
|
||||||
"create_memory" => {
|
|
||||||
let content = arguments["content"].as_str().unwrap_or("");
|
|
||||||
let analyze = arguments["analyze"].as_bool().unwrap_or(false);
|
|
||||||
|
|
||||||
let final_content = if analyze {
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
{
|
|
||||||
format!("[AI分析] 感情: neutral, カテゴリ: general\n{}", content)
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "ai-analysis"))]
|
|
||||||
{
|
|
||||||
content.to_string()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
content.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.memory_manager.create_memory(&final_content) {
|
|
||||||
Ok(id) => json!({
|
|
||||||
"success": true,
|
|
||||||
"id": id,
|
|
||||||
"message": if analyze { "Memory created with AI analysis" } else { "Memory created successfully" }
|
|
||||||
}),
|
|
||||||
Err(e) => json!({
|
|
||||||
"success": false,
|
|
||||||
"error": e.to_string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"search_memories" => {
|
|
||||||
let query = arguments["query"].as_str().unwrap_or("");
|
|
||||||
let semantic = arguments["semantic"].as_bool().unwrap_or(false);
|
|
||||||
|
|
||||||
let memories = if semantic {
|
|
||||||
#[cfg(feature = "semantic-search")]
|
|
||||||
{
|
|
||||||
// Mock semantic search for now
|
|
||||||
self.memory_manager.search_memories(query)
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "semantic-search"))]
|
|
||||||
{
|
|
||||||
self.memory_manager.search_memories(query)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.memory_manager.search_memories(query)
|
|
||||||
};
|
|
||||||
|
|
||||||
json!({
|
|
||||||
"success": true,
|
|
||||||
"memories": memories.into_iter().map(|m| json!({
|
|
||||||
"id": m.id,
|
|
||||||
"content": m.content,
|
|
||||||
"created_at": m.created_at,
|
|
||||||
"updated_at": m.updated_at
|
|
||||||
})).collect::<Vec<_>>(),
|
|
||||||
"search_type": if semantic { "semantic" } else { "keyword" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"update_memory" => {
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"delete_memory" => {
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "web-integration")]
|
|
||||||
"import_webpage" => {
|
|
||||||
let url = arguments["url"].as_str().unwrap_or("");
|
|
||||||
match self.import_from_web(url).await {
|
|
||||||
Ok(content) => {
|
|
||||||
match self.memory_manager.create_memory(&content) {
|
|
||||||
Ok(id) => json!({
|
|
||||||
"success": true,
|
|
||||||
"id": id,
|
|
||||||
"message": format!("Webpage imported successfully from {}", url)
|
|
||||||
}),
|
|
||||||
Err(e) => json!({
|
|
||||||
"success": false,
|
|
||||||
"error": e.to_string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => json!({
|
|
||||||
"success": false,
|
|
||||||
"error": format!("Failed to import webpage: {}", e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
"analyze_sentiment" => {
|
|
||||||
json!({
|
|
||||||
"success": true,
|
|
||||||
"analysis": {
|
|
||||||
"positive": 60,
|
|
||||||
"neutral": 30,
|
|
||||||
"negative": 10,
|
|
||||||
"dominant_sentiment": "positive"
|
|
||||||
},
|
|
||||||
"message": "Sentiment analysis completed"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
"extract_insights" => {
|
|
||||||
json!({
|
|
||||||
"success": true,
|
|
||||||
"insights": {
|
|
||||||
"most_frequent_topics": ["programming", "ai", "productivity"],
|
|
||||||
"learning_frequency": "5 times per week",
|
|
||||||
"growth_trend": "increasing",
|
|
||||||
"recommendations": ["Focus more on advanced topics", "Consider practical applications"]
|
|
||||||
},
|
|
||||||
"message": "Insights extracted successfully"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => json!({
|
|
||||||
"success": false,
|
|
||||||
"error": format!("Unknown tool: {}", tool_name)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": id,
|
|
||||||
"result": {
|
|
||||||
"content": [{
|
|
||||||
"type": "text",
|
|
||||||
"text": result.to_string()
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": id,
|
|
||||||
"error": {
|
|
||||||
"code": -32601,
|
|
||||||
"message": "Method not found"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "web-integration")]
|
|
||||||
async fn import_from_web(&self, url: &str) -> Result<String> {
|
|
||||||
let response = reqwest::get(url).await?;
|
|
||||||
let content = response.text().await?;
|
|
||||||
|
|
||||||
let document = scraper::Html::parse_document(&content);
|
|
||||||
let title_selector = scraper::Selector::parse("title").unwrap();
|
|
||||||
let body_selector = scraper::Selector::parse("p").unwrap();
|
|
||||||
|
|
||||||
let title = document.select(&title_selector)
|
|
||||||
.next()
|
|
||||||
.map(|el| el.inner_html())
|
|
||||||
.unwrap_or_else(|| "Untitled".to_string());
|
|
||||||
|
|
||||||
let paragraphs: Vec<String> = document.select(&body_selector)
|
|
||||||
.map(|el| el.inner_html())
|
|
||||||
.take(5)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(format!("# {}\nURL: {}\n\n{}", title, url, paragraphs.join("\n\n")))
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,3 +0,0 @@
|
|||||||
// Re-export core modules to make them available to extended version
|
|
||||||
pub use aigpt::memory;
|
|
||||||
pub use aigpt::mcp;
|
|
@@ -1,250 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use clap::{Parser, Subcommand};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
// Re-use core modules from parent
|
|
||||||
use aigpt::memory::MemoryManager;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(name = "aigpt-extended")]
|
|
||||||
#[command(about = "Extended Claude Memory Tool with AI analysis and web integration")]
|
|
||||||
struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Commands {
|
|
||||||
/// Create a new memory entry
|
|
||||||
Create {
|
|
||||||
content: String,
|
|
||||||
#[arg(long)]
|
|
||||||
analyze: bool,
|
|
||||||
},
|
|
||||||
/// Search memories with advanced options
|
|
||||||
Search {
|
|
||||||
query: String,
|
|
||||||
#[arg(long)]
|
|
||||||
semantic: bool,
|
|
||||||
#[arg(long)]
|
|
||||||
category: Option<String>,
|
|
||||||
#[arg(long)]
|
|
||||||
time_range: Option<String>,
|
|
||||||
},
|
|
||||||
/// Import content from web
|
|
||||||
Import {
|
|
||||||
#[arg(long)]
|
|
||||||
url: Option<String>,
|
|
||||||
#[arg(long)]
|
|
||||||
file: Option<PathBuf>,
|
|
||||||
},
|
|
||||||
/// Analyze memories for insights
|
|
||||||
Analyze {
|
|
||||||
#[arg(long)]
|
|
||||||
sentiment: bool,
|
|
||||||
#[arg(long)]
|
|
||||||
patterns: bool,
|
|
||||||
#[arg(long)]
|
|
||||||
period: Option<String>,
|
|
||||||
},
|
|
||||||
/// Sync with external services
|
|
||||||
Sync {
|
|
||||||
service: String,
|
|
||||||
},
|
|
||||||
/// Run in standard mode (fallback to simple)
|
|
||||||
Simple {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: SimpleCommands,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum SimpleCommands {
|
|
||||||
Create { content: String },
|
|
||||||
Search { query: String },
|
|
||||||
List,
|
|
||||||
Delete { id: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
let mut memory_manager = MemoryManager::new().await?;
|
|
||||||
|
|
||||||
match cli.command {
|
|
||||||
Commands::Create { content, analyze } => {
|
|
||||||
if analyze {
|
|
||||||
println!("🧠 AI分析付きでメモリーを作成中...");
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
{
|
|
||||||
let analyzed_content = ai_analyze(&content).await?;
|
|
||||||
let id = memory_manager.create_memory(&analyzed_content)?;
|
|
||||||
println!("✅ 分析済みメモリーを作成: {}", id);
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "ai-analysis"))]
|
|
||||||
{
|
|
||||||
println!("⚠️ AI分析機能が無効です。通常のメモリーとして保存します。");
|
|
||||||
let id = memory_manager.create_memory(&content)?;
|
|
||||||
println!("✅ メモリーを作成: {}", id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let id = memory_manager.create_memory(&content)?;
|
|
||||||
println!("✅ メモリーを作成: {}", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Commands::Search { query, semantic, category, time_range } => {
|
|
||||||
if semantic {
|
|
||||||
#[cfg(feature = "semantic-search")]
|
|
||||||
{
|
|
||||||
println!("🔍 セマンティック検索を実行中...");
|
|
||||||
let results = semantic_search(&memory_manager, &query).await?;
|
|
||||||
print_search_results(results);
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "semantic-search"))]
|
|
||||||
{
|
|
||||||
println!("⚠️ セマンティック検索機能が無効です。通常検索を実行します。");
|
|
||||||
let results = memory_manager.search_memories(&query);
|
|
||||||
print_search_results(results);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let results = memory_manager.search_memories(&query);
|
|
||||||
print_search_results(results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Commands::Import { url, file } => {
|
|
||||||
#[cfg(feature = "web-integration")]
|
|
||||||
{
|
|
||||||
if let Some(url) = url {
|
|
||||||
println!("🌐 Webページをインポート中: {}", url);
|
|
||||||
let content = import_from_web(&url).await?;
|
|
||||||
let id = memory_manager.create_memory(&content)?;
|
|
||||||
println!("✅ Webコンテンツをメモリーに保存: {}", id);
|
|
||||||
} else if let Some(file) = file {
|
|
||||||
println!("📄 ファイルをインポート中: {}", file.display());
|
|
||||||
let content = std::fs::read_to_string(file)?;
|
|
||||||
let id = memory_manager.create_memory(&content)?;
|
|
||||||
println!("✅ ファイルをメモリーに保存: {}", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "web-integration"))]
|
|
||||||
{
|
|
||||||
println!("⚠️ Web統合機能が無効です。");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Commands::Analyze { sentiment, patterns, period } => {
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
{
|
|
||||||
println!("📊 メモリー分析を実行中...");
|
|
||||||
if sentiment {
|
|
||||||
analyze_sentiment(&memory_manager).await?;
|
|
||||||
}
|
|
||||||
if patterns {
|
|
||||||
analyze_patterns(&memory_manager, period).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "ai-analysis"))]
|
|
||||||
{
|
|
||||||
println!("⚠️ AI分析機能が無効です。");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Commands::Sync { service } => {
|
|
||||||
println!("🔄 {}との同期機能は開発中です", service);
|
|
||||||
}
|
|
||||||
Commands::Simple { command } => {
|
|
||||||
// Fallback to simple mode
|
|
||||||
match command {
|
|
||||||
SimpleCommands::Create { content } => {
|
|
||||||
let id = memory_manager.create_memory(&content)?;
|
|
||||||
println!("✅ メモリーを作成: {}", id);
|
|
||||||
}
|
|
||||||
SimpleCommands::Search { query } => {
|
|
||||||
let results = memory_manager.search_memories(&query);
|
|
||||||
print_search_results(results);
|
|
||||||
}
|
|
||||||
SimpleCommands::List => {
|
|
||||||
// List all memories (simplified)
|
|
||||||
let results = memory_manager.search_memories("");
|
|
||||||
print_search_results(results);
|
|
||||||
}
|
|
||||||
SimpleCommands::Delete { id } => {
|
|
||||||
memory_manager.delete_memory(&id)?;
|
|
||||||
println!("🗑️ メモリーを削除: {}", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_search_results(results: Vec<aigpt::memory::Memory>) {
|
|
||||||
if results.is_empty() {
|
|
||||||
println!("🔍 検索結果が見つかりませんでした");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("🔍 {} 件の結果が見つかりました:", results.len());
|
|
||||||
for memory in results {
|
|
||||||
println!("📝 [{}] {} ({})",
|
|
||||||
memory.id,
|
|
||||||
memory.content.chars().take(50).collect::<String>(),
|
|
||||||
memory.created_at.format("%Y-%m-%d %H:%M")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extended features (only compiled when features are enabled)
|
|
||||||
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
async fn ai_analyze(content: &str) -> Result<String> {
|
|
||||||
// Mock AI analysis for now
|
|
||||||
Ok(format!("[AI分析] 感情: neutral, カテゴリ: general\n{}", content))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "semantic-search")]
|
|
||||||
async fn semantic_search(memory_manager: &MemoryManager, query: &str) -> Result<Vec<aigpt::memory::Memory>> {
|
|
||||||
// Mock semantic search - in reality would use embeddings
|
|
||||||
Ok(memory_manager.search_memories(query))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "web-integration")]
|
|
||||||
async fn import_from_web(url: &str) -> Result<String> {
|
|
||||||
let response = reqwest::get(url).await?;
|
|
||||||
let content = response.text().await?;
|
|
||||||
|
|
||||||
// Basic HTML parsing
|
|
||||||
let document = scraper::Html::parse_document(&content);
|
|
||||||
let title_selector = scraper::Selector::parse("title").unwrap();
|
|
||||||
let body_selector = scraper::Selector::parse("p").unwrap();
|
|
||||||
|
|
||||||
let title = document.select(&title_selector)
|
|
||||||
.next()
|
|
||||||
.map(|el| el.inner_html())
|
|
||||||
.unwrap_or_else(|| "Untitled".to_string());
|
|
||||||
|
|
||||||
let paragraphs: Vec<String> = document.select(&body_selector)
|
|
||||||
.map(|el| el.inner_html())
|
|
||||||
.take(5) // First 5 paragraphs
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(format!("# {}\nURL: {}\n\n{}", title, url, paragraphs.join("\n\n")))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
async fn analyze_sentiment(memory_manager: &MemoryManager) -> Result<()> {
|
|
||||||
println!("📊 センチメント分析結果:");
|
|
||||||
println!(" - ポジティブ: 60%");
|
|
||||||
println!(" - ニュートラル: 30%");
|
|
||||||
println!(" - ネガティブ: 10%");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ai-analysis")]
|
|
||||||
async fn analyze_patterns(memory_manager: &MemoryManager, period: Option<String>) -> Result<()> {
|
|
||||||
let period_str = period.unwrap_or_else(|| "1week".to_string());
|
|
||||||
println!("📈 学習パターン分析 ({})", period_str);
|
|
||||||
println!(" - 最多トピック: プログラミング");
|
|
||||||
println!(" - 学習頻度: 週5回");
|
|
||||||
println!(" - 成長傾向: 上昇");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@@ -1,14 +1,11 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
mod memory;
|
use aigpt::mcp::BaseMCPServer;
|
||||||
mod mcp;
|
|
||||||
|
|
||||||
use mcp::MCPServer;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// 環境変数から自動実行設定を読み込み
|
// 環境変数から設定を読み込み
|
||||||
let auto_execute = env::var("MEMORY_AUTO_EXECUTE")
|
let auto_execute = env::var("MEMORY_AUTO_EXECUTE")
|
||||||
.unwrap_or_else(|_| "false".to_string())
|
.unwrap_or_else(|_| "false".to_string())
|
||||||
.parse::<bool>()
|
.parse::<bool>()
|
||||||
@@ -27,14 +24,14 @@ async fn main() -> Result<()> {
|
|||||||
let trigger_sensitivity = env::var("TRIGGER_SENSITIVITY")
|
let trigger_sensitivity = env::var("TRIGGER_SENSITIVITY")
|
||||||
.unwrap_or_else(|_| "medium".to_string());
|
.unwrap_or_else(|_| "medium".to_string());
|
||||||
|
|
||||||
// 設定をログ出力(デバッグ用)
|
// 設定をログ出力
|
||||||
eprintln!("Memory MCP Server starting with config:");
|
eprintln!("Memory MCP Server (Standard) starting with config:");
|
||||||
eprintln!(" AUTO_EXECUTE: {}", auto_execute);
|
eprintln!(" AUTO_EXECUTE: {}", auto_execute);
|
||||||
eprintln!(" AUTO_SAVE: {}", auto_save);
|
eprintln!(" AUTO_SAVE: {}", auto_save);
|
||||||
eprintln!(" AUTO_SEARCH: {}", auto_search);
|
eprintln!(" AUTO_SEARCH: {}", auto_search);
|
||||||
eprintln!(" TRIGGER_SENSITIVITY: {}", trigger_sensitivity);
|
eprintln!(" TRIGGER_SENSITIVITY: {}", trigger_sensitivity);
|
||||||
|
|
||||||
let mut server = MCPServer::new().await?;
|
let mut server = BaseMCPServer::new().await?;
|
||||||
server.run().await?;
|
server.run().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
@@ -1,10 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
// Re-use core modules from parent (these imports removed as they're unused)
|
use aigpt::mcp::ExtendedMCPServer;
|
||||||
|
|
||||||
mod extended_mcp;
|
|
||||||
use extended_mcp::ExtendedMCPServer;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
@@ -6,7 +6,7 @@ pub mod memory;
|
|||||||
pub mod mcp;
|
pub mod mcp;
|
||||||
|
|
||||||
use memory::MemoryManager;
|
use memory::MemoryManager;
|
||||||
use mcp::MCPServer;
|
use mcp::BaseMCPServer;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "aigpt")]
|
#[command(name = "aigpt")]
|
||||||
@@ -35,7 +35,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Server | Commands::Serve => {
|
Commands::Server | Commands::Serve => {
|
||||||
let mut server = MCPServer::new().await?;
|
let mut server = BaseMCPServer::new().await?;
|
||||||
server.run().await?;
|
server.run().await?;
|
||||||
}
|
}
|
||||||
Commands::Import { file } => {
|
Commands::Import { file } => {
|
||||||
|
250
src/mcp.rs
250
src/mcp.rs
@@ -1,250 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
use std::io::{self, BufRead, Write};
|
|
||||||
|
|
||||||
use crate::memory::MemoryManager;
|
|
||||||
|
|
||||||
pub struct MCPServer {
|
|
||||||
memory_manager: MemoryManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MCPServer {
|
|
||||||
pub async fn new() -> Result<Self> {
|
|
||||||
let memory_manager = MemoryManager::new().await?;
|
|
||||||
Ok(MCPServer { memory_manager })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(&mut self) -> Result<()> {
|
|
||||||
let stdin = io::stdin();
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
|
|
||||||
// Set up line-based reading
|
|
||||||
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(_) => {
|
|
||||||
// EOF or error, exit gracefully
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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" => {
|
|
||||||
json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": id,
|
|
||||||
"result": {
|
|
||||||
"protocolVersion": "2024-11-05",
|
|
||||||
"capabilities": {
|
|
||||||
"tools": {}
|
|
||||||
},
|
|
||||||
"serverInfo": {
|
|
||||||
"name": "aigpt",
|
|
||||||
"version": "0.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"tools/list" => {
|
|
||||||
json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": id,
|
|
||||||
"result": {
|
|
||||||
"tools": [
|
|
||||||
{
|
|
||||||
"name": "create_memory",
|
|
||||||
"description": "Create a new memory entry",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"content": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Content of the memory"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["content"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "delete_memory",
|
|
||||||
"description": "Delete a memory entry",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "ID of the memory to delete"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["id"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "search_memories",
|
|
||||||
"description": "Search memories by content",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"query": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Search query"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["query"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "list_conversations",
|
|
||||||
"description": "List all imported conversations",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"tools/call" => {
|
|
||||||
let tool_name = request["params"]["name"].as_str().unwrap_or("");
|
|
||||||
let arguments = &request["params"]["arguments"];
|
|
||||||
|
|
||||||
let result = match tool_name {
|
|
||||||
"create_memory" => {
|
|
||||||
let content = arguments["content"].as_str().unwrap_or("");
|
|
||||||
match self.memory_manager.create_memory(content) {
|
|
||||||
Ok(id) => json!({
|
|
||||||
"success": true,
|
|
||||||
"id": id,
|
|
||||||
"message": "Memory created successfully"
|
|
||||||
}),
|
|
||||||
Err(e) => json!({
|
|
||||||
"success": false,
|
|
||||||
"error": e.to_string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"update_memory" => {
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"delete_memory" => {
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"search_memories" => {
|
|
||||||
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,
|
|
||||||
"created_at": m.created_at,
|
|
||||||
"updated_at": m.updated_at
|
|
||||||
})).collect::<Vec<_>>()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"list_conversations" => {
|
|
||||||
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<_>>()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => json!({
|
|
||||||
"success": false,
|
|
||||||
"error": format!("Unknown tool: {}", tool_name)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": id,
|
|
||||||
"result": {
|
|
||||||
"content": [{
|
|
||||||
"type": "text",
|
|
||||||
"text": result.to_string()
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
json!({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": id,
|
|
||||||
"error": {
|
|
||||||
"code": -32601,
|
|
||||||
"message": "Method not found"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
280
src/mcp/base.rs
Normal file
280
src/mcp/base.rs
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use std::io::{self, BufRead, Write};
|
||||||
|
|
||||||
|
use crate::memory::MemoryManager;
|
||||||
|
|
||||||
|
pub struct BaseMCPServer {
|
||||||
|
pub memory_manager: MemoryManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BaseMCPServer {
|
||||||
|
pub async fn new() -> Result<Self> {
|
||||||
|
let memory_manager = MemoryManager::new().await?;
|
||||||
|
Ok(BaseMCPServer { memory_manager })
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Content of the memory"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ツール呼び出しハンドラ
|
||||||
|
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),
|
||||||
|
"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("");
|
||||||
|
match self.memory_manager.create_memory(content) {
|
||||||
|
Ok(id) => 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,
|
||||||
|
"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<_>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不明なメソッドハンドラ
|
||||||
|
fn handle_unknown_method(&self, id: Value) -> Value {
|
||||||
|
json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": id,
|
||||||
|
"error": {
|
||||||
|
"code": -32601,
|
||||||
|
"message": "Method not found"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
293
src/mcp/extended.rs
Normal file
293
src/mcp/extended.rs
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
use super::base::BaseMCPServer;
|
||||||
|
|
||||||
|
pub struct ExtendedMCPServer {
|
||||||
|
base: BaseMCPServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtendedMCPServer {
|
||||||
|
pub async fn new() -> Result<Self> {
|
||||||
|
let base = BaseMCPServer::new().await?;
|
||||||
|
Ok(ExtendedMCPServer { base })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
|
self.base.run().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_request(&mut self, request: Value) -> Value {
|
||||||
|
self.base.handle_request(request).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拡張ツールを追加
|
||||||
|
pub fn get_available_tools(&self) -> Vec<Value> {
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut tools = self.base.get_available_tools();
|
||||||
|
|
||||||
|
// AI分析ツールを追加
|
||||||
|
#[cfg(feature = "ai-analysis")]
|
||||||
|
{
|
||||||
|
tools.push(json!({
|
||||||
|
"name": "analyze_sentiment",
|
||||||
|
"description": "Analyze sentiment of memories",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"period": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Time period to analyze"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
tools.push(json!({
|
||||||
|
"name": "extract_insights",
|
||||||
|
"description": "Extract insights and patterns from memories",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"category": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Category to analyze"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web統合ツールを追加
|
||||||
|
#[cfg(feature = "web-integration")]
|
||||||
|
{
|
||||||
|
tools.push(json!({
|
||||||
|
"name": "import_webpage",
|
||||||
|
"description": "Import content from a webpage",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL to import from"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["url"]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// セマンティック検索強化
|
||||||
|
#[cfg(feature = "semantic-search")]
|
||||||
|
{
|
||||||
|
// create_memoryを拡張版で上書き
|
||||||
|
if let Some(pos) = tools.iter().position(|tool| tool["name"] == "create_memory") {
|
||||||
|
tools[pos] = json!({
|
||||||
|
"name": "create_memory",
|
||||||
|
"description": "Create a new memory entry with optional AI analysis",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Content of the memory"
|
||||||
|
},
|
||||||
|
"analyze": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Enable AI analysis for this memory"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["content"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// search_memoriesを拡張版で上書き
|
||||||
|
if let Some(pos) = tools.iter().position(|tool| tool["name"] == "search_memories") {
|
||||||
|
tools[pos] = json!({
|
||||||
|
"name": "search_memories",
|
||||||
|
"description": "Search memories with advanced options",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search query"
|
||||||
|
},
|
||||||
|
"semantic": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Use semantic search"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by category"
|
||||||
|
},
|
||||||
|
"time_range": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by time range (e.g., '1week', '1month')"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["query"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tools
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拡張ツール実行
|
||||||
|
pub async fn execute_tool(&mut self, tool_name: &str, arguments: &Value) -> Value {
|
||||||
|
match tool_name {
|
||||||
|
// 拡張機能
|
||||||
|
#[cfg(feature = "ai-analysis")]
|
||||||
|
"analyze_sentiment" => self.tool_analyze_sentiment(arguments).await,
|
||||||
|
#[cfg(feature = "ai-analysis")]
|
||||||
|
"extract_insights" => self.tool_extract_insights(arguments).await,
|
||||||
|
#[cfg(feature = "web-integration")]
|
||||||
|
"import_webpage" => self.tool_import_webpage(arguments).await,
|
||||||
|
|
||||||
|
// 拡張版の基本ツール (AI分析付き)
|
||||||
|
"create_memory" => self.tool_create_memory_extended(arguments).await,
|
||||||
|
"search_memories" => self.tool_search_memories_extended(arguments).await,
|
||||||
|
|
||||||
|
// 基本ツールにフォールバック
|
||||||
|
_ => self.base.execute_tool(tool_name, arguments).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拡張ツール実装
|
||||||
|
async fn tool_create_memory_extended(&mut self, arguments: &Value) -> Value {
|
||||||
|
let content = arguments["content"].as_str().unwrap_or("");
|
||||||
|
let analyze = arguments["analyze"].as_bool().unwrap_or(false);
|
||||||
|
|
||||||
|
let final_content = if analyze {
|
||||||
|
#[cfg(feature = "ai-analysis")]
|
||||||
|
{
|
||||||
|
format!("[AI分析] 感情: neutral, カテゴリ: general\n{}", content)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "ai-analysis"))]
|
||||||
|
{
|
||||||
|
content.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.base.memory_manager.create_memory(&final_content) {
|
||||||
|
Ok(id) => json!({
|
||||||
|
"success": true,
|
||||||
|
"id": id,
|
||||||
|
"message": if analyze { "Memory created with AI analysis" } else { "Memory created successfully" }
|
||||||
|
}),
|
||||||
|
Err(e) => json!({
|
||||||
|
"success": false,
|
||||||
|
"error": e.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn tool_search_memories_extended(&mut self, arguments: &Value) -> Value {
|
||||||
|
let query = arguments["query"].as_str().unwrap_or("");
|
||||||
|
let semantic = arguments["semantic"].as_bool().unwrap_or(false);
|
||||||
|
|
||||||
|
let memories = if semantic {
|
||||||
|
#[cfg(feature = "semantic-search")]
|
||||||
|
{
|
||||||
|
// モックセマンティック検索
|
||||||
|
self.base.memory_manager.search_memories(query)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "semantic-search"))]
|
||||||
|
{
|
||||||
|
self.base.memory_manager.search_memories(query)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.base.memory_manager.search_memories(query)
|
||||||
|
};
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"success": true,
|
||||||
|
"memories": memories.into_iter().map(|m| json!({
|
||||||
|
"id": m.id,
|
||||||
|
"content": m.content,
|
||||||
|
"created_at": m.created_at,
|
||||||
|
"updated_at": m.updated_at
|
||||||
|
})).collect::<Vec<_>>(),
|
||||||
|
"search_type": if semantic { "semantic" } else { "keyword" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ai-analysis")]
|
||||||
|
async fn tool_analyze_sentiment(&mut self, _arguments: &Value) -> Value {
|
||||||
|
json!({
|
||||||
|
"success": true,
|
||||||
|
"analysis": {
|
||||||
|
"positive": 60,
|
||||||
|
"neutral": 30,
|
||||||
|
"negative": 10,
|
||||||
|
"dominant_sentiment": "positive"
|
||||||
|
},
|
||||||
|
"message": "Sentiment analysis completed"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ai-analysis")]
|
||||||
|
async fn tool_extract_insights(&mut self, _arguments: &Value) -> Value {
|
||||||
|
json!({
|
||||||
|
"success": true,
|
||||||
|
"insights": {
|
||||||
|
"most_frequent_topics": ["programming", "ai", "productivity"],
|
||||||
|
"learning_frequency": "5 times per week",
|
||||||
|
"growth_trend": "increasing",
|
||||||
|
"recommendations": ["Focus more on advanced topics", "Consider practical applications"]
|
||||||
|
},
|
||||||
|
"message": "Insights extracted successfully"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "web-integration")]
|
||||||
|
async fn tool_import_webpage(&mut self, arguments: &Value) -> Value {
|
||||||
|
let url = arguments["url"].as_str().unwrap_or("");
|
||||||
|
match self.import_from_web(url).await {
|
||||||
|
Ok(content) => {
|
||||||
|
match self.base.memory_manager.create_memory(&content) {
|
||||||
|
Ok(id) => json!({
|
||||||
|
"success": true,
|
||||||
|
"id": id,
|
||||||
|
"message": format!("Webpage imported successfully from {}", url)
|
||||||
|
}),
|
||||||
|
Err(e) => json!({
|
||||||
|
"success": false,
|
||||||
|
"error": e.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => json!({
|
||||||
|
"success": false,
|
||||||
|
"error": format!("Failed to import webpage: {}", e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "web-integration")]
|
||||||
|
async fn import_from_web(&self, url: &str) -> Result<String> {
|
||||||
|
let response = reqwest::get(url).await?;
|
||||||
|
let content = response.text().await?;
|
||||||
|
|
||||||
|
let document = scraper::Html::parse_document(&content);
|
||||||
|
let title_selector = scraper::Selector::parse("title").unwrap();
|
||||||
|
let body_selector = scraper::Selector::parse("p").unwrap();
|
||||||
|
|
||||||
|
let title = document.select(&title_selector)
|
||||||
|
.next()
|
||||||
|
.map(|el| el.inner_html())
|
||||||
|
.unwrap_or_else(|| "Untitled".to_string());
|
||||||
|
|
||||||
|
let paragraphs: Vec<String> = document.select(&body_selector)
|
||||||
|
.map(|el| el.inner_html())
|
||||||
|
.take(5)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(format!("# {}\nURL: {}\n\n{}", title, url, paragraphs.join("\n\n")))
|
||||||
|
}
|
||||||
|
}
|
5
src/mcp/mod.rs
Normal file
5
src/mcp/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod base;
|
||||||
|
pub mod extended;
|
||||||
|
|
||||||
|
pub use base::BaseMCPServer;
|
||||||
|
pub use extended::ExtendedMCPServer;
|
@@ -238,4 +238,4 @@ impl MemoryManager {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user