diff --git a/CMakeLists.txt b/CMakeLists.txt index 47db7cb..88a2bdc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,10 +83,11 @@ if(OPENSSL_FOUND) target_compile_definitions(AissiaLLM PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) endif() -# Tools Library (Internal tools + FileSystem tools + MCP client + MCP server) +# Tools Library (Internal tools + FileSystem tools + MCP client + MCP server + MCP Server Tools) add_library(AissiaTools STATIC src/shared/tools/InternalTools.cpp src/shared/tools/FileSystemTools.cpp + src/shared/tools/MCPServerTools.cpp src/shared/mcp/StdioTransport.cpp src/shared/mcp/MCPClient.cpp src/shared/mcp/MCPServer.cpp diff --git a/config/README_MCP.md b/config/README_MCP.md new file mode 100644 index 0000000..911c6bc --- /dev/null +++ b/config/README_MCP.md @@ -0,0 +1,305 @@ +# AISSIA MCP Configuration for Claude Code + +This directory contains an example MCP (Model Context Protocol) configuration for integrating AISSIA with Claude Code. + +## Quick Setup + +### 1. Locate Claude Code MCP Settings + +The MCP configuration file location depends on your operating system: + +**Windows**: +``` +%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json +``` + +Full path example: +``` +C:\Users\YourUsername\AppData\Roaming\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json +``` + +**macOS**: +``` +~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json +``` + +**Linux**: +``` +~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json +``` + +### 2. Copy Configuration + +Copy the contents of `claude_code_mcp_config.json` to the Claude Code MCP settings file. + +**Important**: Update the `command` path to point to your actual AISSIA executable: + +```json +{ + "mcpServers": { + "aissia": { + "command": "C:\\path\\to\\your\\aissia\\build\\aissia.exe", + "args": ["--mcp-server"], + "disabled": false + } + } +} +``` + +### 3. Restart Claude Code + +Restart VS Code (or reload window: `Ctrl+Shift+P` → "Developer: Reload Window") to apply the changes. + +### 4. Verify Integration + +Open Claude Code and check that AISSIA tools are available: + +``` +You: Can you list the available MCP servers? +Claude: I have access to the following MCP servers: +- aissia: 13 tools available +``` + +## Available Tools + +Once configured, Claude will have access to these 13 AISSIA tools: + +### AISSIA Core (5 tools) +1. **chat_with_aissia** ⭐ - Dialogue with AISSIA's AI assistant (Claude Sonnet 4) +2. **transcribe_audio** - Transcribe audio files to text +3. **text_to_speech** - Convert text to speech audio files +4. **save_memory** - Save notes to AISSIA's persistent storage +5. **search_memories** - Search through saved memories + +### File System (8 tools) +6. **read_file** - Read file contents +7. **write_file** - Write content to files +8. **list_directory** - List files in a directory +9. **search_files** - Search for files by pattern +10. **file_exists** - Check if a file exists +11. **create_directory** - Create directories +12. **delete_file** - Delete files +13. **move_file** - Move or rename files + +## Configuration Options + +### Basic Configuration + +```json +{ + "mcpServers": { + "aissia": { + "command": "path/to/aissia.exe", + "args": ["--mcp-server"], + "disabled": false + } + } +} +``` + +### With Auto-Approval + +To skip confirmation prompts for specific tools: + +```json +{ + "mcpServers": { + "aissia": { + "command": "path/to/aissia.exe", + "args": ["--mcp-server"], + "disabled": false, + "alwaysAllow": ["chat_with_aissia", "read_file", "write_file"] + } + } +} +``` + +### Disable Server + +To temporarily disable AISSIA without removing the configuration: + +```json +{ + "mcpServers": { + "aissia": { + "command": "path/to/aissia.exe", + "args": ["--mcp-server"], + "disabled": true // <-- Set to true + } + } +} +``` + +## Prerequisites + +Before running AISSIA in MCP server mode, ensure these config files exist: + +### config/ai.json +```json +{ + "provider": "claude", + "api_key": "sk-ant-api03-...", + "model": "claude-sonnet-4-20250514", + "max_iterations": 10, + "system_prompt": "Tu es AISSIA, un assistant personnel intelligent..." +} +``` + +### config/storage.json +```json +{ + "database_path": "./data/aissia.db", + "journal_mode": "WAL", + "busy_timeout_ms": 5000 +} +``` + +### config/voice.json (optional) +```json +{ + "tts": { + "enabled": true, + "rate": 0, + "volume": 80 + }, + "stt": { + "active_mode": { + "enabled": false + } + } +} +``` + +## Testing MCP Server + +You can test the MCP server independently before integrating with Claude Code: + +```bash +# Test tools/list +echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | ./build/aissia.exe --mcp-server + +# Test chat_with_aissia tool +echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"chat_with_aissia","arguments":{"message":"What time is it?"}}}' | ./build/aissia.exe --mcp-server +``` + +## Troubleshooting + +### "Server not found" or "Connection failed" + +1. Verify the `command` path is correct and points to `aissia.exe` +2. Make sure AISSIA compiles successfully: `cmake --build build` +3. Test running `./build/aissia.exe --mcp-server` manually + +### "LLMService not initialized" + +AISSIA requires `config/ai.json` with a valid Claude API key. Check: +1. File exists: `config/ai.json` +2. API key is valid: `"api_key": "sk-ant-api03-..."` +3. Provider is set: `"provider": "claude"` + +### "Tool execution failed" + +Some tools have limited functionality in Phase 8 MVP: +- `transcribe_audio` - Not fully implemented yet (STT file support needed) +- `text_to_speech` - Not fully implemented yet (TTS file output needed) +- `save_memory` - Not fully implemented yet (Storage sync methods needed) +- `search_memories` - Not fully implemented yet (Storage sync methods needed) + +These will be completed in Phase 8.1 and 8.2. + +### Server starts but tools don't appear + +1. Check Claude Code logs: `Ctrl+Shift+P` → "Developer: Open Extension Logs" +2. Look for MCP server initialization errors +3. Verify JSON syntax in the MCP configuration file + +## Example Use Cases + +### 1. Ask AISSIA for Help + +``` +You: Use chat_with_aissia to ask "What are my top productivity patterns?" +Claude: [calls chat_with_aissia tool] +AISSIA: Based on your activity data, your most productive hours are 9-11 AM... +``` + +### 2. File Operations + AI + +``` +You: Read my TODO.md file and ask AISSIA to prioritize the tasks +Claude: [calls read_file("TODO.md")] +Claude: [calls chat_with_aissia with task list] +AISSIA: Here's a prioritized version based on urgency and dependencies... +``` + +### 3. Voice Transcription (future) + +``` +You: Transcribe meeting-notes.wav to text +Claude: [calls transcribe_audio("meeting-notes.wav")] +Result: "Welcome to the team meeting. Today we're discussing..." +``` + +## Advanced Configuration + +### Multiple MCP Servers + +You can configure multiple MCP servers alongside AISSIA: + +```json +{ + "mcpServers": { + "aissia": { + "command": "C:\\path\\to\\aissia\\build\\aissia.exe", + "args": ["--mcp-server"], + "disabled": false + }, + "filesystem": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "C:\\Users"], + "disabled": false + }, + "brave-search": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-brave-search"], + "disabled": false, + "env": { + "BRAVE_API_KEY": "your-brave-api-key" + } + } + } +} +``` + +### Environment Variables + +Pass environment variables to AISSIA: + +```json +{ + "mcpServers": { + "aissia": { + "command": "C:\\path\\to\\aissia\\build\\aissia.exe", + "args": ["--mcp-server"], + "disabled": false, + "env": { + "AISSIA_LOG_LEVEL": "debug", + "CLAUDE_API_KEY": "sk-ant-api03-..." + } + } + } +} +``` + +## References + +- **Full Documentation**: `docs/CLAUDE_CODE_INTEGRATION.md` +- **MCP Specification**: https://github.com/anthropics/mcp +- **Claude Code Extension**: https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev + +## Support + +For issues or questions: +1. Check the full documentation: `docs/CLAUDE_CODE_INTEGRATION.md` +2. Review logs: AISSIA writes to stderr in MCP mode +3. Test manually: `./build/aissia.exe --mcp-server` and send JSON-RPC requests diff --git a/config/claude_code_mcp_config.json b/config/claude_code_mcp_config.json new file mode 100644 index 0000000..885f84c --- /dev/null +++ b/config/claude_code_mcp_config.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "aissia": { + "command": "C:\\Users\\alexi\\Documents\\projects\\aissia\\build\\aissia.exe", + "args": ["--mcp-server"], + "disabled": false, + "alwaysAllow": [] + } + } +} diff --git a/docs/CLAUDE_CODE_INTEGRATION.md b/docs/CLAUDE_CODE_INTEGRATION.md new file mode 100644 index 0000000..2f11f03 --- /dev/null +++ b/docs/CLAUDE_CODE_INTEGRATION.md @@ -0,0 +1,449 @@ +# AISSIA - Claude Code Integration (Phase 8) + +## Overview + +AISSIA can now be exposed as an **MCP Server** (Model Context Protocol) to integrate with Claude Code and other MCP-compatible clients. This allows Claude to use AISSIA's capabilities as tools during conversations. + +**Mode MCP Server**: `./aissia --mcp-server` + +This mode exposes AISSIA's services via JSON-RPC 2.0 over stdio, following the MCP specification. + +## Available Tools + +AISSIA exposes **13 tools** total: + +### 1. AISSIA Core Tools (Priority) + +#### `chat_with_aissia` ⭐ **PRIORITY** +Dialogue with AISSIA's built-in AI assistant (Claude Sonnet 4). Send a message and get an intelligent response with access to AISSIA's knowledge and capabilities. + +**Input**: +```json +{ + "message": "string (required) - Message to send to AISSIA", + "conversation_id": "string (optional) - Conversation ID for continuity", + "system_prompt": "string (optional) - Custom system prompt" +} +``` + +**Output**: +```json +{ + "response": "AISSIA's response text", + "conversation_id": "conversation-id", + "tokens": 1234, + "iterations": 2 +} +``` + +**Example use case**: "Hey AISSIA, can you analyze my focus patterns this week?" + +#### `transcribe_audio` +Transcribe audio file to text using Speech-to-Text engines (Whisper.cpp, OpenAI Whisper API, Google Speech). + +**Input**: +```json +{ + "file_path": "string (required) - Path to audio file", + "language": "string (optional) - Language code (e.g., 'fr', 'en'). Default: 'fr'" +} +``` + +**Output**: +```json +{ + "text": "Transcribed text from audio", + "file": "/path/to/audio.wav", + "language": "fr" +} +``` + +**Status**: ⚠️ Not yet implemented - requires STT service file transcription support + +#### `text_to_speech` +Convert text to speech audio file using Text-to-Speech synthesis. Generates audio in WAV format. + +**Input**: +```json +{ + "text": "string (required) - Text to synthesize", + "output_file": "string (required) - Output audio file path (WAV)", + "voice": "string (optional) - Voice identifier (e.g., 'fr-fr', 'en-us'). Default: 'fr-fr'" +} +``` + +**Output**: +```json +{ + "success": true, + "file": "/path/to/output.wav", + "voice": "fr-fr" +} +``` + +**Status**: ⚠️ Not yet implemented - requires TTS engine file output support + +#### `save_memory` +Save a note or memory to AISSIA's persistent storage. Memories can be tagged and searched later. + +**Input**: +```json +{ + "title": "string (required) - Memory title", + "content": "string (required) - Memory content", + "tags": ["array of strings (optional) - Tags for categorization"] +} +``` + +**Output**: +```json +{ + "id": "memory-uuid", + "title": "Meeting notes", + "timestamp": "2025-01-30T10:00:00Z" +} +``` + +**Status**: ⚠️ Not yet implemented - requires StorageService sync methods + +#### `search_memories` +Search through saved memories and notes in AISSIA's storage. Returns matching memories with relevance scores. + +**Input**: +```json +{ + "query": "string (required) - Search query", + "limit": "integer (optional) - Maximum results to return. Default: 10" +} +``` + +**Output**: +```json +{ + "results": [ + { + "id": "memory-uuid", + "title": "Meeting notes", + "content": "...", + "score": 0.85, + "tags": ["work", "meeting"] + } + ], + "count": 5 +} +``` + +**Status**: ⚠️ Not yet implemented - requires StorageService sync methods + +### 2. File System Tools (8 tools) + +- `read_file` - Read a file from the filesystem +- `write_file` - Write content to a file +- `list_directory` - List files in a directory +- `search_files` - Search for files by pattern +- `file_exists` - Check if a file exists +- `create_directory` - Create a new directory +- `delete_file` - Delete a file +- `move_file` - Move or rename a file + +These tools provide Claude with direct filesystem access to work with files on your system. + +## Installation for Claude Code + +### 1. Configure Claude Code MCP + +Create or edit your Claude Code MCP configuration file: + +**Windows**: `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json` +**macOS/Linux**: `~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` + +Add AISSIA as an MCP server: + +```json +{ + "mcpServers": { + "aissia": { + "command": "C:\\path\\to\\aissia\\build\\aissia.exe", + "args": ["--mcp-server"], + "disabled": false + } + } +} +``` + +**Note**: Replace `C:\\path\\to\\aissia\\build\\aissia.exe` with the actual path to your compiled AISSIA executable. + +### 2. Verify Configuration + +Restart Claude Code (or VS Code) to reload the MCP configuration. + +Claude should now have access to all 13 AISSIA tools during conversations. + +### 3. Test Integration + +In Claude Code, try: + +``` +"Can you use the chat_with_aissia tool to ask AISSIA what time it is?" +``` + +Claude will call the `chat_with_aissia` tool, which internally uses AISSIA's LLM service to process the query. + +## Architecture + +### Synchronous Mode (MCP Server) + +When running as an MCP server, AISSIA uses **synchronous blocking calls** instead of the async pub/sub architecture used in normal mode: + +```cpp +// Normal mode (async) +io->publish("llm:request", data); +// ... wait for response on "llm:response" topic + +// MCP mode (sync) +auto response = llmService->sendMessageSync(message, conversationId); +// immediate result +``` + +This is necessary because: +1. MCP protocol expects immediate JSON-RPC responses +2. No event loop in MCP server mode (stdin/stdout blocking I/O) +3. Simplifies integration with external tools + +### Service Integration + +``` +MCPServer (stdio JSON-RPC) + ↓ +MCPServerTools (tool handlers) + ↓ +Services (sync methods) + ├── LLMService::sendMessageSync() + ├── VoiceService::transcribeFileSync() + ├── VoiceService::textToSpeechSync() + └── StorageService (stub implementations) +``` + +### Tool Registry + +All tools are registered in a central `ToolRegistry`: + +```cpp +ToolRegistry registry; + +// 1. Internal tools (get_current_time) +registry.registerTool("get_current_time", ...); + +// 2. FileSystem tools (8 tools) +for (auto& toolDef : FileSystemTools::getToolDefinitions()) { + registry.registerTool(toolDef); +} + +// 3. AISSIA tools (5 tools) +MCPServerTools aissiaTools(llmService, storageService, voiceService); +for (const auto& toolDef : aissiaTools.getToolDefinitions()) { + registry.registerTool(toolDef); +} +``` + +Total: **13 tools** + +## Configuration Files + +AISSIA MCP Server requires these config files (same as normal mode): + +- `config/ai.json` - LLM provider configuration (Claude API key) +- `config/storage.json` - Database path and settings +- `config/voice.json` - TTS/STT engine settings + +**Important**: Make sure these files are present before running `--mcp-server` mode. + +## Limitations (Phase 8 MVP) + +1. **STT/TTS file operations**: `transcribe_audio` and `text_to_speech` are not fully implemented yet + - STT service needs file transcription support (currently only streaming) + - TTS engine needs file output support (currently only direct playback) + +2. **Storage sync methods**: `save_memory` and `search_memories` return "not implemented" errors + - StorageService needs `saveMemorySync()` and `searchMemoriesSync()` methods + - Current storage only works via async pub/sub + +3. **No hot-reload**: MCP server mode doesn't load hot-reloadable modules + - Only services and tools are available + - No SchedulerModule, MonitoringModule, etc. + +4. **Single-threaded**: MCP server runs synchronously on main thread + - LLMService worker thread still runs for agentic loops + - But overall server is blocking on stdin + +## Roadmap + +### Phase 8.1 - Complete STT/TTS Sync Methods +- [ ] Implement `VoiceService::transcribeFileSync()` using STT engines +- [ ] Implement `VoiceService::textToSpeechSync()` with file output +- [ ] Test audio file transcription via MCP + +### Phase 8.2 - Storage Sync Methods +- [ ] Implement `StorageService::saveMemorySync()` +- [ ] Implement `StorageService::searchMemoriesSync()` +- [ ] Add vector embeddings for semantic search + +### Phase 8.3 - Advanced Tools +- [ ] `schedule_task` - Add tasks to AISSIA's scheduler +- [ ] `get_focus_stats` - Retrieve hyperfocus detection stats +- [ ] `list_active_apps` - Get current monitored applications +- [ ] `send_notification` - Trigger system notifications + +### Phase 8.4 - Multi-Modal Support +- [ ] Image input for LLM (Claude vision) +- [ ] PDF/document parsing tools +- [ ] Web scraping integration + +## Use Cases + +### 1. AI Assistant Collaboration + +Claude Code can delegate complex reasoning tasks to AISSIA: + +``` +Claude: "I need to analyze user behavior patterns. Let me ask AISSIA." +→ calls chat_with_aissia("Analyze recent focus patterns") +AISSIA: "Based on monitoring data, user has 3 hyperfocus sessions daily averaging 2.5 hours..." +``` + +### 2. Voice Transcription Workflow + +``` +Claude: "Transcribe meeting-2025-01-30.wav" +→ calls transcribe_audio(file_path="meeting-2025-01-30.wav", language="en") +→ calls write_file(path="transcript.txt", content=result) +``` + +### 3. Knowledge Management + +``` +Claude: "Save this important insight to AISSIA's memory" +→ calls save_memory( + title="Project architecture decision", + content="We decided to use hot-reload modules for business logic...", + tags=["architecture", "project"] +) +``` + +### 4. File + AI Operations + +``` +Claude: "Read todos.md, ask AISSIA to prioritize tasks, update file" +→ calls read_file("todos.md") +→ calls chat_with_aissia("Prioritize these tasks: ...") +→ calls write_file("todos-prioritized.md", content=...) +``` + +## Development + +### Adding New Tools + +1. **Declare tool in MCPServerTools.hpp**: +```cpp +json handleNewTool(const json& input); +``` + +2. **Implement in MCPServerTools.cpp**: +```cpp +json MCPServerTools::handleNewTool(const json& input) { + // Extract input parameters + std::string param = input["param"]; + + // Call service + auto result = m_someService->doSomethingSync(param); + + // Return JSON result + return { + {"output", result}, + {"status", "success"} + }; +} +``` + +3. **Register in getToolDefinitions()**: +```cpp +tools.push_back({ + "new_tool", + "Description of what this tool does", + { + {"type", "object"}, + {"properties", { + {"param", { + {"type", "string"}, + {"description", "Parameter description"} + }} + }}, + {"required", json::array({"param"})} + }, + [this](const json& input) { return handleNewTool(input); } +}); +``` + +4. **Add to execute() switch**: +```cpp +if (toolName == "new_tool") { + return handleNewTool(input); +} +``` + +### Testing MCP Server + +Test with `nc` or `socat`: + +```bash +# Send tools/list request +echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | ./build/aissia.exe --mcp-server + +# Send tool call +echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"chat_with_aissia","arguments":{"message":"Hello AISSIA"}}}' | ./build/aissia.exe --mcp-server +``` + +Expected output format: +```json +{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"chat_with_aissia","description":"...","inputSchema":{...}}]}} +``` + +## Troubleshooting + +### "LLMService not initialized" + +Make sure `config/ai.json` exists with valid API key: +```json +{ + "provider": "claude", + "api_key": "sk-ant-...", + "model": "claude-sonnet-4-20250514" +} +``` + +### "VoiceService not available" + +Voice tools are optional. If you don't need STT/TTS, this is normal. + +### "StorageService not available" + +Make sure `config/storage.json` exists: +```json +{ + "database_path": "./data/aissia.db", + "journal_mode": "WAL", + "busy_timeout_ms": 5000 +} +``` + +### "Tool not found" + +Check `tools/list` output to see which tools are actually registered. + +## References + +- **MCP Specification**: https://github.com/anthropics/mcp +- **AISSIA Architecture**: `docs/project-overview.md` +- **GroveEngine Guide**: `docs/GROVEENGINE_GUIDE.md` +- **LLM Service**: `src/services/LLMService.hpp` +- **MCPServer**: `src/shared/mcp/MCPServer.hpp` diff --git a/src/main.cpp b/src/main.cpp index e085483..e54e760 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "services/VoiceService.hpp" #include "shared/mcp/MCPServer.hpp" #include "shared/tools/FileSystemTools.hpp" +#include "shared/tools/MCPServerTools.hpp" #include "shared/llm/ToolRegistry.hpp" #include @@ -177,14 +178,32 @@ private: // Run AISSIA as MCP server (stdio mode) int runMCPServer() { // Log to stderr so stdout stays clean for JSON-RPC - // Use stderr_color_sink from stdout_color_sinks.h auto logger = spdlog::stderr_color_mt("MCPServer"); spdlog::set_default_logger(logger); spdlog::set_level(spdlog::level::info); spdlog::info("AISSIA MCP Server starting..."); - // Create tool registry with FileSystem tools + // === Initialize Services === + + // 1. LLMService (PRIORITY: for chat_with_aissia) + auto llmService = std::make_unique(); + if (!llmService->loadConfig("config/llm.json")) { + spdlog::warn("Failed to load LLM config, chat_with_aissia will be unavailable"); + } + llmService->initialize(nullptr); // No IIO in MCP mode + llmService->initializeTools(); // Load internal tools + MCP tools + + // 2. StorageService (for save_memory/search_memories) + auto storageService = std::make_unique(); + storageService->initialize(nullptr); + + // 3. VoiceService (for TTS/STT) + auto voiceService = std::make_unique(); + // Note: Voice config is optional + voiceService->initialize(nullptr); + + // === Create Tool Registry === aissia::ToolRegistry registry; // Register get_current_time tool @@ -214,7 +233,18 @@ int runMCPServer() { ); } - spdlog::info("Registered {} tools", registry.size()); + // === Register AISSIA Tools (Phase 8) === + aissia::tools::MCPServerTools aissiaTools( + llmService.get(), + storageService.get(), + voiceService.get() + ); + + for (const auto& toolDef : aissiaTools.getToolDefinitions()) { + registry.registerTool(toolDef); + } + + spdlog::info("Registered {} tools total", registry.size()); // Create and run MCP server aissia::mcp::MCPServer server(registry); diff --git a/src/services/LLMService.cpp b/src/services/LLMService.cpp index b531f1f..4543c35 100644 --- a/src/services/LLMService.cpp +++ b/src/services/LLMService.cpp @@ -340,4 +340,36 @@ void LLMService::shutdown() { m_logger->info("LLMService shutdown"); } +LLMService::SyncResponse LLMService::sendMessageSync( + const std::string& message, + const std::string& conversationId, + const std::string& systemPrompt +) { + SyncResponse syncResp; + + // Create request (same as async mode) + Request request; + request.query = message; + request.conversationId = conversationId.empty() ? "mcp-session" : conversationId; + request.systemPrompt = systemPrompt.empty() ? m_defaultSystemPrompt : systemPrompt; + request.maxIterations = m_maxIterations; + + // Process synchronously (blocking call) + auto response = processRequest(request); + + // Convert to SyncResponse + if (!response.isError) { + syncResp.text = response.text; + syncResp.tokens = response.tokens; + syncResp.iterations = response.iterations; + } else { + // On error, return error in text + syncResp.text = "Error: " + response.text; + syncResp.tokens = 0; + syncResp.iterations = 0; + } + + return syncResp; +} + } // namespace aissia diff --git a/src/services/LLMService.hpp b/src/services/LLMService.hpp index d255ad0..8dabf8a 100644 --- a/src/services/LLMService.hpp +++ b/src/services/LLMService.hpp @@ -60,6 +60,29 @@ public: /// Load MCP server configurations bool loadMCPConfig(const std::string& configPath); + /** + * @brief Synchronous response structure for MCP Server mode + */ + struct SyncResponse { + std::string text; + int tokens = 0; + int iterations = 0; + }; + + /** + * @brief Send message synchronously (blocking, for MCP Server mode) + * + * @param message User message + * @param conversationId Conversation ID (optional) + * @param systemPrompt Custom system prompt (optional) + * @return Sync response with text, tokens, iterations + */ + SyncResponse sendMessageSync( + const std::string& message, + const std::string& conversationId = "", + const std::string& systemPrompt = "" + ); + private: struct Request { std::string query; diff --git a/src/services/VoiceService.cpp b/src/services/VoiceService.cpp index e7bc585..f073b7e 100644 --- a/src/services/VoiceService.cpp +++ b/src/services/VoiceService.cpp @@ -4,6 +4,7 @@ #include #include #include +#include // Include VoiceService.hpp BEFORE spdlog to avoid logger macro conflicts #include "VoiceService.hpp" @@ -225,4 +226,69 @@ void VoiceService::shutdown() { m_logger->info("[VoiceService] Shutdown"); } +bool VoiceService::loadConfig(const std::string& configPath) { + try { + std::ifstream file(configPath); + if (!file.is_open()) { + m_logger->warn("[VoiceService] Config file not found: {}", configPath); + return false; + } + + nlohmann::json config; + file >> config; + + // Load TTS config + if (config.contains("tts")) { + const auto& ttsConfig = config["tts"]; + m_ttsEnabled = ttsConfig.value("enabled", true); + m_ttsRate = ttsConfig.value("rate", 0); + m_ttsVolume = ttsConfig.value("volume", 80); + } + + // Load STT config (Phase 7 format) + if (config.contains("stt")) { + configureSTT(config["stt"]); + } + + m_logger->info("[VoiceService] Config loaded from {}", configPath); + return true; + + } catch (const std::exception& e) { + m_logger->error("[VoiceService] Failed to load config: {}", e.what()); + return false; + } +} + +std::string VoiceService::transcribeFileSync( + const std::string& filePath, + const std::string& language +) { + m_logger->info("[VoiceService] transcribeFileSync: {}", filePath); + + if (!m_sttService) { + throw std::runtime_error("STT service not initialized"); + } + + // Use STT service to transcribe file synchronously + // Note: This requires STT service to support file transcription + // For MVP, we'll throw not implemented + throw std::runtime_error("transcribeFileSync not yet implemented - STT service needs file transcription support"); +} + +bool VoiceService::textToSpeechSync( + const std::string& text, + const std::string& outputFile, + const std::string& voice +) { + m_logger->info("[VoiceService] textToSpeechSync: {} -> {}", text.substr(0, 50), outputFile); + + if (!m_ttsEngine) { + throw std::runtime_error("TTS engine not initialized"); + } + + // For MVP, we don't support saving to file yet + // The TTS engine currently only speaks directly + throw std::runtime_error("textToSpeechSync file output not yet implemented - TTS engine needs file output support"); +} + } // namespace aissia diff --git a/src/services/VoiceService.hpp b/src/services/VoiceService.hpp index b3f1d0e..099e190 100644 --- a/src/services/VoiceService.hpp +++ b/src/services/VoiceService.hpp @@ -55,6 +55,35 @@ public: /// Configure STT with full config (Phase 7) void configureSTT(const nlohmann::json& sttConfig); + /// Load configuration from JSON file + bool loadConfig(const std::string& configPath); + + /** + * @brief Transcribe audio file synchronously (for MCP Server mode) + * + * @param filePath Path to audio file + * @param language Language code (e.g., "fr", "en") + * @return Transcribed text + */ + std::string transcribeFileSync( + const std::string& filePath, + const std::string& language = "fr" + ); + + /** + * @brief Convert text to speech synchronously (for MCP Server mode) + * + * @param text Text to synthesize + * @param outputFile Output audio file path + * @param voice Voice identifier (e.g., "fr-fr") + * @return true if successful + */ + bool textToSpeechSync( + const std::string& text, + const std::string& outputFile, + const std::string& voice = "fr-fr" + ); + private: // Configuration bool m_ttsEnabled = true; diff --git a/src/shared/tools/MCPServerTools.cpp b/src/shared/tools/MCPServerTools.cpp new file mode 100644 index 0000000..ceb4971 --- /dev/null +++ b/src/shared/tools/MCPServerTools.cpp @@ -0,0 +1,310 @@ +#include "MCPServerTools.hpp" +#include "../../services/LLMService.hpp" +#include "../../services/StorageService.hpp" +#include "../../services/VoiceService.hpp" + +#include + +namespace aissia::tools { + +MCPServerTools::MCPServerTools( + LLMService* llm, + StorageService* storage, + VoiceService* voice +) : m_llmService(llm), + m_storageService(storage), + m_voiceService(voice) +{ +} + +std::vector MCPServerTools::getToolDefinitions() { + std::vector tools; + + // Tool 1: chat_with_aissia (PRIORITÉ) + if (m_llmService) { + tools.push_back({ + "chat_with_aissia", + "Dialogue with AISSIA assistant (Claude Sonnet 4). Send a message and get an intelligent response with access to AISSIA's knowledge and capabilities.", + { + {"type", "object"}, + {"properties", { + {"message", { + {"type", "string"}, + {"description", "Message to send to AISSIA"} + }}, + {"conversation_id", { + {"type", "string"}, + {"description", "Conversation ID for continuity (optional)"} + }}, + {"system_prompt", { + {"type", "string"}, + {"description", "Custom system prompt (optional)"} + }} + }}, + {"required", json::array({"message"})} + }, + [this](const json& input) { return handleChatWithAissia(input); } + }); + } + + // Tool 2: transcribe_audio + if (m_voiceService) { + tools.push_back({ + "transcribe_audio", + "Transcribe audio file to text using Speech-to-Text (Whisper.cpp, OpenAI Whisper API, or Google Speech). Supports WAV, MP3, and other common audio formats.", + { + {"type", "object"}, + {"properties", { + {"file_path", { + {"type", "string"}, + {"description", "Path to audio file"} + }}, + {"language", { + {"type", "string"}, + {"description", "Language code (e.g., 'fr', 'en'). Default: 'fr'"} + }} + }}, + {"required", json::array({"file_path"})} + }, + [this](const json& input) { return handleTranscribeAudio(input); } + }); + + // Tool 3: text_to_speech + tools.push_back({ + "text_to_speech", + "Convert text to speech audio file using Text-to-Speech synthesis. Generates audio in WAV format.", + { + {"type", "object"}, + {"properties", { + {"text", { + {"type", "string"}, + {"description", "Text to synthesize"} + }}, + {"output_file", { + {"type", "string"}, + {"description", "Output audio file path (WAV)"} + }}, + {"voice", { + {"type", "string"}, + {"description", "Voice identifier (e.g., 'fr-fr', 'en-us'). Default: 'fr-fr'"} + }} + }}, + {"required", json::array({"text", "output_file"})} + }, + [this](const json& input) { return handleTextToSpeech(input); } + }); + } + + // Tool 4: save_memory + if (m_storageService) { + tools.push_back({ + "save_memory", + "Save a note or memory to AISSIA's persistent storage. Memories can be tagged and searched later.", + { + {"type", "object"}, + {"properties", { + {"title", { + {"type", "string"}, + {"description", "Memory title"} + }}, + {"content", { + {"type", "string"}, + {"description", "Memory content"} + }}, + {"tags", { + {"type", "array"}, + {"items", {{"type", "string"}}}, + {"description", "Tags for categorization (optional)"} + }} + }}, + {"required", json::array({"title", "content"})} + }, + [this](const json& input) { return handleSaveMemory(input); } + }); + + // Tool 5: search_memories + tools.push_back({ + "search_memories", + "Search through saved memories and notes in AISSIA's storage. Returns matching memories with relevance scores.", + { + {"type", "object"}, + {"properties", { + {"query", { + {"type", "string"}, + {"description", "Search query"} + }}, + {"limit", { + {"type", "integer"}, + {"description", "Maximum results to return. Default: 10"} + }} + }}, + {"required", json::array({"query"})} + }, + [this](const json& input) { return handleSearchMemories(input); } + }); + } + + return tools; +} + +json MCPServerTools::execute(const std::string& toolName, const json& input) { + if (toolName == "chat_with_aissia") { + return handleChatWithAissia(input); + } else if (toolName == "transcribe_audio") { + return handleTranscribeAudio(input); + } else if (toolName == "text_to_speech") { + return handleTextToSpeech(input); + } else if (toolName == "save_memory") { + return handleSaveMemory(input); + } else if (toolName == "search_memories") { + return handleSearchMemories(input); + } + + return { + {"error", "Unknown tool: " + toolName} + }; +} + +// ============================================================================ +// Tool Handlers +// ============================================================================ + +json MCPServerTools::handleChatWithAissia(const json& input) { + if (!m_llmService) { + return {{"error", "LLMService not available"}}; + } + + try { + std::string message = input["message"]; + std::string conversationId = input.value("conversation_id", ""); + std::string systemPrompt = input.value("system_prompt", ""); + + spdlog::info("[chat_with_aissia] Message: {}", message.substr(0, 100)); + + // Call synchronous LLM method + auto response = m_llmService->sendMessageSync(message, conversationId, systemPrompt); + + return { + {"response", response.text}, + {"conversation_id", conversationId}, + {"tokens", response.tokens}, + {"iterations", response.iterations} + }; + } catch (const std::exception& e) { + spdlog::error("[chat_with_aissia] Error: {}", e.what()); + return {{"error", e.what()}}; + } +} + +json MCPServerTools::handleTranscribeAudio(const json& input) { + if (!m_voiceService) { + return {{"error", "VoiceService not available"}}; + } + + try { + std::string filePath = input["file_path"]; + std::string language = input.value("language", "fr"); + + spdlog::info("[transcribe_audio] File: {}, Language: {}", filePath, language); + + // Call synchronous STT method + std::string text = m_voiceService->transcribeFileSync(filePath, language); + + return { + {"text", text}, + {"file", filePath}, + {"language", language} + }; + } catch (const std::exception& e) { + spdlog::error("[transcribe_audio] Error: {}", e.what()); + return {{"error", e.what()}}; + } +} + +json MCPServerTools::handleTextToSpeech(const json& input) { + if (!m_voiceService) { + return {{"error", "VoiceService not available"}}; + } + + try { + std::string text = input["text"]; + std::string outputFile = input["output_file"]; + std::string voice = input.value("voice", "fr-fr"); + + spdlog::info("[text_to_speech] Text: {}, Output: {}", text.substr(0, 50), outputFile); + + // Call synchronous TTS method + bool success = m_voiceService->textToSpeechSync(text, outputFile, voice); + + if (success) { + return { + {"success", true}, + {"file", outputFile}, + {"voice", voice} + }; + } else { + return {{"error", "TTS generation failed"}}; + } + } catch (const std::exception& e) { + spdlog::error("[text_to_speech] Error: {}", e.what()); + return {{"error", e.what()}}; + } +} + +json MCPServerTools::handleSaveMemory(const json& input) { + if (!m_storageService) { + return {{"error", "StorageService not available"}}; + } + + try { + std::string title = input["title"]; + std::string content = input["content"]; + std::vector tags; + + if (input.contains("tags") && input["tags"].is_array()) { + for (const auto& tag : input["tags"]) { + tags.push_back(tag.get()); + } + } + + spdlog::info("[save_memory] Title: {}", title); + + // TODO: Implement saveMemorySync in StorageService + // For now, return not implemented + return json({ + {"error", "save_memory not yet implemented"}, + {"note", "StorageService sync methods need to be added"}, + {"title", title} + }); + } catch (const std::exception& e) { + spdlog::error("[save_memory] Error: {}", e.what()); + return {{"error", e.what()}}; + } +} + +json MCPServerTools::handleSearchMemories(const json& input) { + if (!m_storageService) { + return {{"error", "StorageService not available"}}; + } + + try { + std::string query = input["query"]; + int limit = input.value("limit", 10); + + spdlog::info("[search_memories] Query: {}, Limit: {}", query, limit); + + // TODO: Implement searchMemoriesSync in StorageService + // For now, return not implemented + return json({ + {"error", "search_memories not yet implemented"}, + {"note", "StorageService sync methods need to be added"}, + {"query", query}, + {"limit", limit} + }); + } catch (const std::exception& e) { + spdlog::error("[search_memories] Error: {}", e.what()); + return {{"error", e.what()}}; + } +} + +} // namespace aissia::tools diff --git a/src/shared/tools/MCPServerTools.hpp b/src/shared/tools/MCPServerTools.hpp new file mode 100644 index 0000000..fbe8fe4 --- /dev/null +++ b/src/shared/tools/MCPServerTools.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "../llm/ToolRegistry.hpp" +#include +#include +#include + +// Forward declarations +namespace aissia { + class LLMService; + class StorageService; + class VoiceService; +} + +namespace aissia::tools { + +using json = nlohmann::json; + +/** + * @brief MCP Server Tools - Bridge between MCP Server and AISSIA services + * + * Provides tool definitions for AISSIA modules exposed via MCP Server: + * - chat_with_aissia: Dialogue with AISSIA (Claude Sonnet 4) + * - transcribe_audio: Speech-to-text (Whisper.cpp/OpenAI/Google) + * - text_to_speech: Text-to-speech synthesis + * - save_memory: Save note/memory to storage + * - search_memories: Search stored memories + * + * Note: These tools run in synchronous mode (no IIO pub/sub, no main loop) + */ +class MCPServerTools { +public: + /** + * @brief Construct MCP server tools with service dependencies + * + * @param llm LLMService for chat_with_aissia (can be nullptr) + * @param storage StorageService for save/search memories (can be nullptr) + * @param voice VoiceService for TTS/STT (can be nullptr) + */ + MCPServerTools( + LLMService* llm, + StorageService* storage, + VoiceService* voice + ); + + /** + * @brief Get all tool definitions for registration + * + * @return Vector of ToolDefinition structs + */ + std::vector getToolDefinitions(); + + /** + * @brief Execute a tool by name + * + * @param toolName Tool to execute + * @param input Tool arguments (JSON) + * @return Tool result (JSON) + */ + json execute(const std::string& toolName, const json& input); + +private: + // Tool handlers + json handleChatWithAissia(const json& input); + json handleTranscribeAudio(const json& input); + json handleTextToSpeech(const json& input); + json handleSaveMemory(const json& input); + json handleSearchMemories(const json& input); + + // Service references (nullable) + LLMService* m_llmService; + StorageService* m_storageService; + VoiceService* m_voiceService; +}; + +} // namespace aissia::tools