Merge branch mcp_v2 into dev

Title: add mcp
Link: https://code.alibaba-inc.com/OpenRepo/ExperienceMaker/codereview/22801767
This commit is contained in:
jinli.yl 2025-07-31 17:16:09 +08:00
commit 2328f94b5e
8 changed files with 811 additions and 3 deletions

View file

@ -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 \

570
doc/mcp_quick_start.md Normal file
View file

@ -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)
<details open>
<summary><b>Python MCP Client Example</b></summary>
```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())
```
</details>
<details>
<summary><b>MCP Tool Call (JSON)</b></summary>
```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"
}
}
}
```
</details>
### 🔍 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)
<details open>
<summary><b>Python MCP Client Example</b></summary>
```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())
```
</details>
<details>
<summary><b>MCP Tool Call (JSON)</b></summary>
```json
{
"method": "tools/call",
"params": {
"name": "retriever",
"arguments": {
"query": "How to solve basic arithmetic problems?",
"top_k": 3,
"workspace_id": "math_workspace"
}
}
}
```
</details>
### 💾 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
<details open>
<summary><b>Python MCP Client Example</b></summary>
```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())
```
</details>
<details>
<summary><b>MCP Tool Call (JSON)</b></summary>
```json
{
"method": "tools/call",
"params": {
"name": "vector_store",
"arguments": {
"action": "dump",
"workspace_id": "math_workspace",
"path": "./backups/"
}
}
}
```
</details>
#### Load Experiences To Vector Store
<details open>
<summary><b>Python MCP Client Example</b></summary>
```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())
```
</details>
#### Delete Workspace
<details open>
<summary><b>Python MCP Client Example</b></summary>
```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())
```
</details>
#### Copy Workspace
<details open>
<summary><b>Python MCP Client Example</b></summary>
```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())
```
</details>
## 🔄 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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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())

View file

@ -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"