From 2c678873a94aea2838f058460aafc5bb43cf9e72 Mon Sep 17 00:00:00 2001 From: "jinli.yl" Date: Thu, 31 Jul 2025 17:10:48 +0800 Subject: [PATCH] add mcp --- README.md | 29 +- doc/mcp_quick_start.md | 570 ++++++++++++++++++ experiencemaker/app.py | 7 + experiencemaker/mcp_server.py | 111 ++++ experiencemaker/schema/app_config.py | 1 + .../service/experience_maker_service.py | 7 +- experiencemaker/service/mcp_client.py | 87 +++ pyproject.toml | 2 + 8 files changed, 811 insertions(+), 3 deletions(-) create mode 100644 doc/mcp_quick_start.md create mode 100644 experiencemaker/mcp_server.py create mode 100644 experiencemaker/service/mcp_client.py diff --git a/README.md b/README.md index 1127d157..ac54778c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ --- ## 📰 What's New -- **[2025-08]** 🎉 ExperienceMaker v0.1.0 is now available on [PyPI](https://pypi.org/project/experiencemaker/)! +- **[2025-08]** 🚀 MCP is now available! → [Quick Start Guide](./doc/mcp_quick_start.md) +- **[2025-07]** 🎉 ExperienceMaker v0.1.1 is now available on [PyPI](https://pypi.org/project/experiencemaker/)! - **[2025-07]** 📚 Complete documentation and quick start guides released - **[2025-06]** 🚀 Multi-backend vector store support (Elasticsearch & ChromaDB) @@ -134,6 +135,8 @@ EMBEDDING_MODEL_BASE_URL="https://xxx.com/v1" ## 🚀 Quick Start +### 🌐 HTTP Service + For testing and development, use the `local_file` backend: ```bash experiencemaker \ @@ -148,6 +151,30 @@ including custom pipelines, operation parameters, and advanced configuration met The service will start on `http://localhost:8001` +### 🔌 MCP Server + +ExperienceMaker now supports Model Context Protocol (MCP) for seamless integration with MCP-compatible clients like Claude Desktop: + +```bash +experiencemaker_mcp \ + mcp_transport=stdio \ + llm.default.model_name=qwen3-32b \ + embedding_model.default.model_name=text-embedding-v4 \ + vector_store.default.backend=local_file +``` + +For SSE transport (Server-Sent Events): +```bash +experiencemaker_mcp \ + mcp_transport=sse \ + http_service.port=8001 \ + llm.default.model_name=qwen3-32b \ + embedding_model.default.model_name=text-embedding-v4 \ + vector_store.default.backend=local_file +``` + +🔗 **For detailed MCP setup and usage examples**, see our [MCP Quick Start Guide](./doc/mcp_quick_start.md). + ### 🔍 Production Setup with Elasticsearch Backend ```bash experiencemaker \ diff --git a/doc/mcp_quick_start.md b/doc/mcp_quick_start.md new file mode 100644 index 00000000..d5acc7c8 --- /dev/null +++ b/doc/mcp_quick_start.md @@ -0,0 +1,570 @@ +# ExperienceMaker MCP Quick Start Guide + +This guide will help you get started with ExperienceMaker using the Model Context Protocol (MCP) interface for seamless +integration with MCP-compatible clients. + +## 🚀 What You'll Learn + +- How to set up ExperienceMaker MCP server +- Connect to the server using MCP clients +- Run an agent and generate experiences via MCP +- Retrieve and apply experiences through MCP tools +- Build experience-enhanced agents with MCP integration + +## 📋 Prerequisites + +- Python 3.12+ +- LLM API access (OpenAI or compatible) +- Embedding model API access +- MCP-compatible client (Claude Desktop, or custom MCP client) + +## 🛠️ Installation + +### Option 1: Install from PyPI (Recommended) + +```bash +pip install experiencemaker +``` + +### Option 2: Install from Source + +```bash +git clone https://github.com/modelscope/ExperienceMaker.git +cd ExperienceMaker +pip install . +``` + +## ⚙️ Environment Setup + +Create a `.env` file in your project directory: + +```bash +# Required: LLM API configuration +LLM_API_KEY="sk-xxx" +LLM_BASE_URL="https://xxx.com/v1" + +# Required: Embedding model configuration +EMBEDDING_MODEL_API_KEY="sk-xxx" +EMBEDDING_MODEL_BASE_URL="https://xxx.com/v1" + +# Optional: Elasticsearch configuration (if using Elasticsearch backend) +ES_HOSTS="http://localhost:9200" +``` + +## 🚀 Start the MCP Server + +### Option 1: STDIO Transport (Recommended for MCP clients) + +```bash +experiencemaker_mcp \ + mcp_transport=stdio \ + llm.default.model_name=qwen3-32b \ + embedding_model.default.model_name=text-embedding-v4 \ + vector_store.default.backend=local_file +``` + +### Option 2: SSE Transport (Server-Sent Events) + +```bash +experiencemaker_mcp \ + mcp_transport=sse \ + http_service.port=8001 \ + llm.default.model_name=qwen3-32b \ + embedding_model.default.model_name=text-embedding-v4 \ + vector_store.default.backend=local_file +``` + +The SSE server will start on `http://localhost:8001/sse` + +### Elasticsearch Backend + +```bash +experiencemaker_mcp \ + mcp_transport=stdio \ + llm.default.model_name=qwen3-32b \ + embedding_model.default.model_name=text-embedding-v4 \ + vector_store.default.backend=elasticsearch +``` + +**Setup Elasticsearch:** + +```bash +export ES_HOSTS="http://localhost:9200" +# Quick setup using Elastic's official script +curl -fsSL https://elastic.co/start-local | sh +``` + +📖 **Need Help?** Refer to [Vector Store Setup](vector_store_setup.md) for comprehensive deployment guidance. + +## 🔧 Configure MCP Client + +### Claude Desktop Configuration + +Add to your Claude Desktop `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "experiencemaker": { + "command": "experiencemaker_mcp", + "args": [ + "mcp_transport=stdio", + "llm.default.model_name=qwen3-32b", + "embedding_model.default.model_name=text-embedding-v4", + "vector_store.default.backend=local_file" + ] + } + } +} +``` + +### Custom MCP Client Configuration + +If using a custom MCP client, connect to: + +- **STDIO**: Use subprocess to communicate with the server +- **SSE**: Connect to `http://localhost:8001/sse` + +## 📝 Using ExperienceMaker MCP Tools + +The MCP server exposes three main tools: + +- `retriever`: Retrieve experiences from workspace +- `summarizer`: Transform trajectories into experiences +- `vector_store`: Manage vector store operations + +Note: The `workspace_id` serves as your experience storage namespace. Experiences in different workspaces remain +completely isolated. + +### 📊 Using the Summarizer Tool + +Transform conversation trajectories into valuable experiences using batch summarization. + +**Tool Parameters:** + +- `traj_list`: List of trajectories (each containing messages and score) +- `workspace_id`: Workspace identifier (default: "default") +- `config`: Additional configuration parameters (optional) + +
+Python MCP Client Example + +```python +import asyncio + +from experiencemaker.schema.message import Message, Trajectory, Role +from experiencemaker.schema.request import SummarizerRequest +from experiencemaker.service.mcp_client import MCPClient + + +async def example_summarizer(): + async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client: + # Create trajectory with conversation + trajectory = Trajectory( + messages=[ + Message(role=Role.USER, content="Hello, how can I solve a math problem?"), + Message(role=Role.ASSISTANT, content="I'd be happy to help! What math problem are you working on?"), + Message(role=Role.USER, content="What is 2+2?"), + Message(role=Role.ASSISTANT, content="2+2 equals 4.") + ], + score=1.0 # Success score + ) + + request = SummarizerRequest( + workspace_id="math_workspace", + traj_list=[trajectory] + ) + + response = await client.call_summarizer(request) + print("Generated experiences:") + for experience in response.experience_list: + print(f"- {experience.content}") + + +# Run the example +asyncio.run(example_summarizer()) +``` + +
+ +
+MCP Tool Call (JSON) + +```json +{ + "method": "tools/call", + "params": { + "name": "summarizer", + "arguments": { + "traj_list": [ + { + "messages": [ + { + "role": "user", + "content": "Hello, how can I solve a math problem?" + }, + { + "role": "assistant", + "content": "I'd be happy to help! What math problem are you working on?" + }, + { + "role": "user", + "content": "What is 2+2?" + }, + { + "role": "assistant", + "content": "2+2 equals 4." + } + ], + "score": 1.0 + } + ], + "workspace_id": "math_workspace" + } + } +} +``` + +
+ +### 🔍 Using the Retriever Tool + +Intelligently search and retrieve the most relevant experiences from your workspace. + +**Tool Parameters:** + +- `query`: Search query string +- `messages`: List of conversation messages (optional) +- `top_k`: Number of top experiences to retrieve (default: 1) +- `workspace_id`: Workspace identifier (default: "default") +- `config`: Additional configuration parameters (optional) + +
+Python MCP Client Example + +```python +import asyncio +from experiencemaker.service.mcp_client import MCPClient +from experiencemaker.schema.request import RetrieverRequest + + +async def example_retriever(): + async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client: + request = RetrieverRequest( + workspace_id="math_workspace", + query="How to solve basic arithmetic problems?", + top_k=3 + ) + + response = await client.call_retriever(request) + print(f"Retrieved experiences: {response.experience_merged}") + print(f"Experience list:") + for exp in response.experience_list: + print(f"- {exp.content}") + + +# Run the example +asyncio.run(example_retriever()) +``` + +
+ +
+MCP Tool Call (JSON) + +```json +{ + "method": "tools/call", + "params": { + "name": "retriever", + "arguments": { + "query": "How to solve basic arithmetic problems?", + "top_k": 3, + "workspace_id": "math_workspace" + } + } +} +``` + +
+ +### 💾 Using the Vector Store Tool + +Manage vector store operations for workspace data. + +**Tool Parameters:** + +- `action`: Action to perform ("dump", "load", "delete", "copy") +- `workspace_id`: Target workspace identifier +- `src_workspace_id`: Source workspace (for copy operation) +- `path`: File system path (for dump/load operations, default: "./") +- `config`: Additional configuration parameters (optional) + +#### Dump Experiences From Vector Store + +
+Python MCP Client Example + +```python +import asyncio +from experiencemaker.service.mcp_client import MCPClient +from experiencemaker.schema.request import VectorStoreRequest + + +async def example_dump(): + async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client: + request = VectorStoreRequest( + workspace_id="math_workspace", + action="dump", + path="./backups/" + ) + + response = await client.call_vector_store(request) + print(f"Dump result: {response}") + + +# Run the example +asyncio.run(example_dump()) +``` + +
+ +
+MCP Tool Call (JSON) + +```json +{ + "method": "tools/call", + "params": { + "name": "vector_store", + "arguments": { + "action": "dump", + "workspace_id": "math_workspace", + "path": "./backups/" + } + } +} +``` + +
+ +#### Load Experiences To Vector Store + +
+Python MCP Client Example + +```python +import asyncio +from experiencemaker.service.mcp_client import MCPClient +from experiencemaker.schema.request import VectorStoreRequest + + +async def example_load(): + async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client: + request = VectorStoreRequest( + workspace_id="math_workspace", + action="load", + path="./backups/" + ) + + response = await client.call_vector_store(request) + print(f"Load result: {response}") + + +# Run the example +asyncio.run(example_load()) +``` + +
+ +#### Delete Workspace + +
+Python MCP Client Example + +```python +import asyncio +from experiencemaker.service.mcp_client import MCPClient +from experiencemaker.schema.request import VectorStoreRequest + + +async def example_delete(): + async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client: + request = VectorStoreRequest( + workspace_id="math_workspace", + action="delete" + ) + + response = await client.call_vector_store(request) + print(f"Delete result: {response}") + + +# Run the example +asyncio.run(example_delete()) +``` + +
+ +#### Copy Workspace + +
+Python MCP Client Example + +```python +import asyncio +from experiencemaker.service.mcp_client import MCPClient +from experiencemaker.schema.request import VectorStoreRequest + + +async def example_copy(): + async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client: + request = VectorStoreRequest( + workspace_id="math_workspace_copy", + action="copy", + src_workspace_id="math_workspace" + ) + + response = await client.call_vector_store(request) + print(f"Copy result: {response}") + + +# Run the example +asyncio.run(example_copy()) +``` + +
+ +## 🔄 Complete MCP Workflow Example + +Here's a complete example showing the full workflow: + +```python +import asyncio +from experiencemaker.service.mcp_client import MCPClient +from experiencemaker.schema.request import SummarizerRequest, RetrieverRequest +from experiencemaker.schema.message import Message, Trajectory, Role + +async def complete_workflow(): + async with MCPClient(base_url="http://0.0.0.0:8001/sse") as client: + print("Available tools:", await client.list_tools()) + + # Step 1: Create experiences from trajectories + trajectory = Trajectory( + messages=[ + Message(role=Role.USER, content="How do I calculate compound interest?"), + Message(role=Role.ASSISTANT, + content="Compound interest is calculated using the formula A = P(1 + r/n)^(nt), where A is the final amount, P is the principal, r is the annual interest rate, n is the number of times interest is compounded per year, and t is the time in years."), + Message(role=Role.USER, content="Can you give me an example?"), + Message(role=Role.ASSISTANT, + content="Sure! If you invest $1000 at 5% annual interest compounded monthly for 2 years: A = 1000(1 + 0.05/12)^(12*2) = $1104.94") + ], + score=1.0 + ) + + summarizer_request = SummarizerRequest( + workspace_id="finance_workspace", + traj_list=[trajectory] + ) + + summarizer_response = await client.call_summarizer(summarizer_request) + print(f"Created {len(summarizer_response.experience_list)} experiences") + + # Step 2: Retrieve relevant experiences + retriever_request = RetrieverRequest( + workspace_id="finance_workspace", + query="How to calculate interest on investments?", + top_k=2 + ) + + retriever_response = await client.call_retriever(retriever_request) + print(f"Retrieved experiences: {retriever_response.experience_merged}") + + +# Run the complete workflow +asyncio.run(complete_workflow()) +``` + +## 🎭 Claude Desktop Integration + +Once configured with Claude Desktop, you can directly ask Claude to use ExperienceMaker tools: + +``` +Claude, please use the summarizer tool to create experiences from this conversation about solving math problems, then retrieve similar experiences when I ask about arithmetic. +``` + +Claude will automatically call the appropriate MCP tools and provide contextually relevant responses based on your +stored experiences. + +## 🐛 Common Issues + +### MCP Server Won't Start + +- Check if the required ports are available (for SSE transport) +- Verify your API keys in `.env` file +- Ensure Python version is 3.12+ +- Check MCP transport configuration + +### MCP Client Connection Issues + +- For STDIO: Ensure the command path is correct in your MCP client config +- For SSE: Verify the server URL and port accessibility +- Check firewall settings for SSE connections + +### No Experiences Retrieved + +- Make sure you've run the summarizer tool first to create experiences +- Check if workspace_id matches between operations +- Verify vector store backend is properly configured + +### API Connection Errors + +- Confirm LLM_BASE_URL and API keys are correct +- Test API access independently +- Check network connectivity + +## 🔧 Advanced Configuration + +### Custom MCP Client Setup + +```python +# For STDIO transport +async with MCPClient(enable_sse=False) as client: + # Your MCP operations here + pass + +# For SSE transport with custom URL +async with MCPClient(base_url="http://custom-host:8001/sse") as client: + # Your MCP operations here + pass +``` + +### Server Configuration Options + +```bash +# Full configuration example +experiencemaker_mcp \ + mcp_transport=stdio \ + http_service.host=0.0.0.0 \ + http_service.port=8001 \ + llm.default.model_name=qwen3-32b \ + llm.default.api_key=${LLM_API_KEY} \ + llm.default.base_url=${LLM_BASE_URL} \ + embedding_model.default.model_name=text-embedding-v4 \ + embedding_model.default.api_key=${EMBEDDING_MODEL_API_KEY} \ + embedding_model.default.base_url=${EMBEDDING_MODEL_BASE_URL} \ + vector_store.default.backend=elasticsearch \ + vector_store.default.host=localhost \ + vector_store.default.port=9200 +``` + +--- + +🎯 **You're all set!** You now have a working ExperienceMaker MCP setup that can seamlessly integrate with MCP-compatible +clients and learn from interactions to improve over time through the standardized MCP protocol. + +## 📚 Next Steps + +- Explore the [Configuration Guide](configuration_guide.md) for advanced customization +- Check out [cookbook examples](../cookbook/) for practical implementations +- Learn about [Vector Store Setup](vector_store_setup.md) for production deployments +- Review the [Operations Documentation](operations_documentation.md) for maintenance procedures \ No newline at end of file diff --git a/experiencemaker/app.py b/experiencemaker/app.py index aca7c9a4..f50f31e6 100644 --- a/experiencemaker/app.py +++ b/experiencemaker/app.py @@ -43,3 +43,10 @@ def main(): if __name__ == "__main__": main() + +# start with: +# experiencemaker \ +# http_service.port=8001 \ +# llm.default.model_name=qwen3-32b \ +# embedding_model.default.model_name=text-embedding-v4 \ +# vector_store.default.backend=local_file \ No newline at end of file diff --git a/experiencemaker/mcp_server.py b/experiencemaker/mcp_server.py new file mode 100644 index 00000000..33a33b5e --- /dev/null +++ b/experiencemaker/mcp_server.py @@ -0,0 +1,111 @@ +import sys +from typing import List + +from dotenv import load_dotenv +from fastmcp import FastMCP + +from experiencemaker.service.experience_maker_service import ExperienceMakerService + +load_dotenv() + +mcp = FastMCP("ExperienceMaker") +service = ExperienceMakerService(sys.argv[1:]) + + +@mcp.tool +def retriever(query: str, + messages: List[dict] = None, + top_k: int = 1, + workspace_id: str = "default", + config: dict = None) -> dict: + """ + Retrieve experiences from the workspace based on a query. + + Args: + query: Query string + messages: List of messages + top_k: Number of top experiences to retrieve + workspace_id: Workspace identifier + config: Additional configuration parameters + + Returns: + Dictionary containing retrieved experiences + """ + return service(api="retriever", request={ + "query": query, + "messages": messages if messages else [], + "top_k": top_k, + "workspace_id": workspace_id, + "config": config if config else {}, + }).model_dump() + + +@mcp.tool +def summarizer(traj_list: List[dict], workspace_id: str = "default", config: dict = None) -> dict: + """ + Summarize trajectories into experiences. + + Args: + traj_list: List of trajectories + workspace_id: Workspace identifier + config: Additional configuration parameters + + Returns: + experiences + """ + return service(api="summarizer", request={ + "traj_list": traj_list, + "workspace_id": workspace_id, + "config": config if config else {}, + }).model_dump() + + +@mcp.tool +def vector_store(action: str, + src_workspace_id: str = "", + workspace_id: str = "", + path: str = "./", + config: dict = None) -> dict: + """ + Perform vector store operations. + + Args: + action: Action to perform (e.g., "copy", "delete", "dump", "load") + src_workspace_id: Source workspace identifier + workspace_id: Workspace identifier + path: Path to the vector store + config: Additional configuration parameters + + Returns: + Dictionary containing the result of the vector store operation + """ + return service(api="vector_store", request={ + "action": action, + "src_workspace_id": src_workspace_id, + "workspace_id": workspace_id, + "path": path, + "config": config if config else {}, + }).model_dump() + + +def main(): + mcp_transport: str = service.init_app_config.mcp_transport + if mcp_transport == "sse": + mcp.run(transport="sse", host=service.http_service_config.host, port=service.http_service_config.port) + elif mcp_transport == "stdio": + mcp.run(transport="stdio") + else: + raise ValueError(f"Unsupported mcp transport: {mcp_transport}") + + +if __name__ == "__main__": + main() + + +# start with: +# experiencemaker_mcp \ +# mcp_transport=stdio \ +# http_service.port=8001 \ +# llm.default.model_name=qwen3-32b \ +# embedding_model.default.model_name=text-embedding-v4 \ +# vector_store.default.backend=local_file \ No newline at end of file diff --git a/experiencemaker/schema/app_config.py b/experiencemaker/schema/app_config.py index ce68c8fe..6524487f 100644 --- a/experiencemaker/schema/app_config.py +++ b/experiencemaker/schema/app_config.py @@ -59,6 +59,7 @@ class VectorStoreConfig: class AppConfig: pre_defined_config: str = field(default="default_config") config_path: str = field(default="") + mcp_transport: str = field(default="sse") http_service: HttpServiceConfig = field(default_factory=HttpServiceConfig) thread_pool: ThreadPoolConfig = field(default_factory=ThreadPoolConfig) api: APIConfig = field(default_factory=APIConfig) diff --git a/experiencemaker/service/experience_maker_service.py b/experiencemaker/service/experience_maker_service.py index edb86dba..8e97230b 100644 --- a/experiencemaker/service/experience_maker_service.py +++ b/experiencemaker/service/experience_maker_service.py @@ -46,8 +46,9 @@ class ExperienceMakerService: def __call__(self, api: str, request: dict | BaseRequest) -> BaseResponse: if isinstance(request, dict): - request = BaseRequest(**request) - app_config: AppConfig = self.config_parser.get_app_config(**request.config) + app_config: AppConfig = self.config_parser.get_app_config(**request["config"]) + else: + app_config: AppConfig = self.config_parser.get_app_config(**request.config) if api == "retriever": if isinstance(request, dict): @@ -76,6 +77,8 @@ class ExperienceMakerService: else: raise RuntimeError(f"Invalid service.api={api}") + logger.info(f"request={request.model_dump_json()}") + try: context = PipelineContext(app_config=app_config, thread_pool=self.thread_pool, diff --git a/experiencemaker/service/mcp_client.py b/experiencemaker/service/mcp_client.py new file mode 100644 index 00000000..9be64e3a --- /dev/null +++ b/experiencemaker/service/mcp_client.py @@ -0,0 +1,87 @@ +import asyncio +import json +from typing import List + +from fastmcp import Client +from pydantic import BaseModel, Field + +from experiencemaker.schema.request import RetrieverRequest, SummarizerRequest, VectorStoreRequest, AgentRequest +from experiencemaker.schema.response import RetrieverResponse, SummarizerResponse, VectorStoreResponse, AgentResponse + + +class MCPClient(BaseModel): + base_url: str = Field(default="http://0.0.0.0:8001/sse") + enable_sse: bool = Field(default=True) + timeout: int = Field(default=300) + + _client: Client | None = None + + async def __aenter__(self): + if self.enable_sse: + self._client = Client(self.base_url) + else: + self._client = Client("stdio") + + await self._client.__aenter__() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if self._client: + await self._client.__aexit__(exc_type, exc_val, exc_tb) + + async def list_tools(self) -> List[str]: + tools = await self._client.list_tools() + return [tool.name for tool in tools] + + async def call_retriever(self, request: RetrieverRequest) -> RetrieverResponse: + result = await self._client.call_tool("retriever", request.model_dump()) + return RetrieverResponse(**result.structured_content) + + async def call_summarizer(self, request: SummarizerRequest) -> SummarizerResponse: + result = await self._client.call_tool("summarizer", request.model_dump()) + return SummarizerResponse(**result.structured_content) + + async def call_vector_store(self, request: VectorStoreRequest) -> VectorStoreResponse: + result = await self._client.call_tool("vector_store", request.model_dump()) + return VectorStoreResponse(**result.structured_content) + + async def call_agent(self, request: AgentRequest) -> AgentResponse: + result = await self._client.call_tool("agent", request.model_dump()) + return AgentResponse(**result.structured_content) + + +async def main(): + """Example usage of MCPClient""" + async with MCPClient() as client: + # List available tools + tools = await client.list_tools() + print("Available tools:", json.dumps(tools, ensure_ascii=False, indent=2)) + + # Example retriever call + retriever_request = RetrieverRequest( + workspace_id="test_workspace", + query="hello world", + top_k=5) + + try: + response = await client.call_retriever(retriever_request) + print("Retriever response:", response.model_dump()) + except Exception as e: + print(f"Error calling retriever: {e}") + + # Example summarizer call + from experiencemaker.schema.message import Trajectory, Message + + summarizer_request = SummarizerRequest( + workspace_id="test_workspace", + traj_list=[Trajectory(messages=[Message(content="hello world!")])]) + + try: + response = await client.call_summarizer(summarizer_request) + print("Summarizer response:", response.model_dump()) + except Exception as e: + print(f"Error calling summarizer: {e}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml index 5ecc0b2f..1dadf1f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "dashscope>=1.19.1", "elasticsearch>=8.14.0", "fastapi>=0.115.13", + "fastmcp>=2.10.6", "loguru>=0.7.3", "mcp>=1.9.4", "numpy>=2.3.0", @@ -45,3 +46,4 @@ experiencemaker = [ [project.scripts] experiencemaker = "experiencemaker.app:main" +experiencemaker_mcp = "experiencemaker.mcp_server:main"