Commit 1929f5b8 authored by dangdoan's avatar dangdoan

update stdio

parent 25bf8018
// {
// "servers": {
// "asisstant-memory": {
// "command": "python",
// "args": ["c:\\Work\\MeU Solutions\\Project\\meu-fastmcp\\memory_mcp_server.py"],
// "type": "stdio"
// }
// }
// }
//Dùng http server thay stdio để dễ debug hơn
{
"servers": {
"assistant-memory": {
"type": "http",
"url": "http://mcp-dev.meucorp.com"
"command": "python",
"args": ["c:\\Work\\MeU Solutions\\Project\\meu-fastmcp\\memory_mcp_server_stdio.py"],
"type": "stdio"
}
}
}
\ No newline at end of file
}
//Dùng http server thay stdio để dễ debug hơn
// {
// "servers": {
// "assistant-memory": {
// "type": "http",
// "url": "http://localhost:8090"
// }
// }
// }
\ No newline at end of file
import os
import pathlib
import uuid
from datetime import datetime
import argparse
from dotenv import load_dotenv
from fastmcp import FastMCP
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct, Filter, FieldCondition, MatchValue
from qdrant_client.models import (
Distance,
VectorParams,
PointStruct,
Filter,
FieldCondition,
MatchValue,
)
from sentence_transformers import SentenceTransformer
from gitlab import Gitlab
import uuid
from datetime import datetime
load_dotenv()
# Config
QDRANT_HOST = os.getenv("QDRANT_HOST", "192.168.0.29")
QDRANT_PORT = int(os.getenv("QDRANT_PORT", 7333))
COLLECTION_NAME = "multi_project_memory"
GITLAB = Gitlab(os.getenv("GITLAB_URL"), private_token=os.getenv("GITLAB_TOKEN"))
QDRANT_PORT = int(os.getenv("QDRANT_PORT", "7333"))
COLLECTION_NAME = os.getenv("QDRANT_COLLECTION", "multi_project_memory")
GITLAB_URL = os.getenv("GITLAB_URL", "")
GITLAB_TOKEN = os.getenv("GITLAB_TOKEN", "")
# MCP server
mcp = FastMCP(name="multi-project-memory")
......@@ -24,10 +37,14 @@ mcp = FastMCP(name="multi-project-memory")
qdrant = QdrantClient(host=QDRANT_HOST, port=QDRANT_PORT)
embedder = SentenceTransformer("all-MiniLM-L6-v2")
# Tạo collection nếu chưa có
gitlab_client = None
if GITLAB_URL and GITLAB_TOKEN:
gitlab_client = Gitlab(GITLAB_URL, private_token=GITLAB_TOKEN)
# Ensure collection exists
try:
qdrant.get_collection(COLLECTION_NAME)
except:
except Exception:
qdrant.create_collection(
collection_name=COLLECTION_NAME,
vectors_config=VectorParams(size=384, distance=Distance.COSINE),
......@@ -35,37 +52,40 @@ except:
@mcp.tool()
def load_agent_context():
"""Tự động đọc file AGENT.md từ root workspace và trả về nội dung."""
"""Read AGENT.md from workspace root and save to Qdrant."""
workspace_root = pathlib.Path.cwd()
agent_file = workspace_root / "AGENT.md"
if agent_file.exists():
content = agent_file.read_text(encoding="utf-8")
save_context(project_id="global", key="agent_context", content=content)
return content
else:
return "AGENT.md not found in workspace root. Please create it."
return "AGENT.md not found in workspace root. Please create it."
@mcp.tool()
def save_context(project_id: str, key: str, content: str):
"""Lưu context/code summary vào Qdrant cho một project cụ thể. Tự động update meta key list."""
"""Save a context entry to Qdrant (per project). Also maintains a meta keys list."""
unique_string = f"{project_id}_{key}"
unique_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, unique_string))
vector = embedder.encode(content).tolist()
qdrant.upsert(
collection_name=COLLECTION_NAME,
points=[PointStruct(
id=unique_id,
vector=vector,
payload={
"project_id": str(project_id),
"key": key,
"content": content,
"updated_at": datetime.now().isoformat(),
"category": key.split(":")[0] if ":" in key else "unknown"
}
)]
points=[
PointStruct(
id=unique_id,
vector=vector,
payload={
"project_id": str(project_id),
"key": key,
"content": content,
"updated_at": datetime.now().isoformat(),
"category": key.split(":")[0] if ":" in key else "unknown",
},
)
],
)
# Meta key update
# Update meta list
meta_key = f"{project_id}_meta:context-keys-list"
meta_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, meta_key))
meta_entry = (
......@@ -74,135 +94,130 @@ def save_context(project_id: str, key: str, content: str):
f"Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | "
f"ID: {unique_id[:8]}...\n"
)
meta_content = meta_entry
try:
retrieved = qdrant.retrieve(
collection_name=COLLECTION_NAME,
ids=[meta_id],
with_payload=True
)
retrieved = qdrant.retrieve(collection_name=COLLECTION_NAME, ids=[meta_id], with_payload=True)
if retrieved and retrieved[0].payload:
old_content = retrieved[0].payload.get("content", "")
if f"Key: {key} " not in old_content:
meta_content = old_content.rstrip() + "\n" + meta_entry
old = retrieved[0].payload.get("content", "")
if f"Key: {key} " not in old:
meta_content = old.rstrip() + "\n" + meta_entry
else:
meta_content = old_content
except Exception as e:
print(f"Meta retrieve error: {e}")
meta_content = old
except Exception:
meta_content = meta_entry
meta_vector = embedder.encode(meta_content).tolist()
qdrant.upsert(
collection_name=COLLECTION_NAME,
points=[PointStruct(
id=meta_id,
vector=meta_vector,
payload={
"project_id": str(project_id),
"key": meta_key,
"content": meta_content,
"type": "meta_keys_list",
"updated_at": datetime.now().isoformat()
}
)]
points=[
PointStruct(
id=meta_id,
vector=meta_vector,
payload={
"project_id": str(project_id),
"key": meta_key,
"content": meta_content,
"type": "meta_keys_list",
"updated_at": datetime.now().isoformat(),
},
)
],
)
return f"Saved context for project '{project_id}' with key: {key}. Meta list updated."
@mcp.tool()
def search_context(project_id: str, query: str, limit: int = 5):
"""Tìm kiếm semantic context từ Qdrant, chỉ trong project được chỉ định."""
"""Semantic search within a project."""
vector = embedder.encode(query).tolist()
hits = qdrant.query_points(
collection_name=COLLECTION_NAME,
query=vector,
query_filter=Filter(
must=[FieldCondition(
key="project_id",
match=MatchValue(value=str(project_id))
)]
must=[
FieldCondition(
key="project_id",
match=MatchValue(value=str(project_id)),
)
]
),
limit=limit
limit=limit,
).points
return [
{
"key": hit.payload.get("key", hit.id),
"content": hit.payload["content"],
"score": hit.score
"key": hit.payload.get("key", str(hit.id)),
"content": hit.payload.get("content", ""),
"score": hit.score,
}
for hit in hits
if hit.payload
]
@mcp.tool()
def index_gitlab_repo(project_id: str, gitlab_project_id: int):
"""Index repo GitLab vào Qdrant cho project cụ thể (lấy files và lưu summary)."""
project = GITLAB.projects.get(gitlab_project_id)
tree = project.repository_tree(recursive=True)
def index_gitlab_repo(project_id: str, gitlab_project_id: int, ref: str = "main"):
"""Index files from a GitLab repo into Qdrant (first 2000 chars per file)."""
if gitlab_client is None:
return "GitLab is not configured. Set GITLAB_URL and GITLAB_TOKEN."
project = gitlab_client.projects.get(gitlab_project_id)
tree = project.repository_tree(recursive=True, ref=ref)
indexed = 0
for item in tree:
if item["type"] == "blob":
try:
file = project.files.get(item["path"], ref="main")
content = file.decode().decode("utf-8")[:2000]
save_context(project_id, item["path"], content)
indexed += 1
except Exception as e:
print(f"Error indexing {item['path']}: {e}")
continue
return f"Indexed {indexed} files for project '{project_id}' (GitLab ID: {gitlab_project_id})"
if item.get("type") != "blob":
continue
path = item.get("path")
try:
f = project.files.get(path, ref=ref)
content = f.decode().decode("utf-8", errors="replace")[:2000]
save_context(project_id, path, content)
indexed += 1
except Exception:
continue
return f"Indexed {indexed} files for project '{project_id}' (GitLab ID: {gitlab_project_id}, ref={ref})"
@mcp.tool()
def list_all_keys(project_id: str):
"""Liệt kê tất cả context keys đã lưu cho project bằng cách search trực tiếp meta key."""
"""List saved keys for a project from meta record."""
meta_key = f"{project_id}_meta:context-keys-list"
meta_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, meta_key))
try:
retrieved = qdrant.retrieve(
collection_name=COLLECTION_NAME,
ids=[meta_id],
with_payload=True
)
if retrieved and len(retrieved) > 0 and retrieved[0].payload:
retrieved = qdrant.retrieve(collection_name=COLLECTION_NAME, ids=[meta_id], with_payload=True)
if retrieved and retrieved[0].payload:
meta_content = retrieved[0].payload.get("content", "Danh sách trống.")
lines = meta_content.strip().split("\n")
formatted = [f"• {line.strip()}" for line in lines if line.strip()]
total = len(formatted)
result = f"Danh sách context keys cho project '{project_id}' ({total} items):\n" + "\n".join(formatted)
return result if formatted else "Meta list tồn tại nhưng hiện tại trống."
else:
return "Không tìm thấy meta list cho project này. Hãy lưu ít nhất một context bằng save_context, sau đó thử lại."
lines = [ln.strip() for ln in meta_content.splitlines() if ln.strip()]
formatted = [f"• {ln}" for ln in lines]
return f"Danh sách context keys cho project '{project_id}' ({len(formatted)} items):\n" + "\n".join(formatted)
return "Không tìm thấy meta list cho project này. Hãy lưu ít nhất một context bằng save_context."
except Exception as e:
print(f"List all keys error: {str(e)}")
return f"Lỗi khi lấy meta list: {str(e)}. Kiểm tra log MCP server hoặc Qdrant dashboard."
return f"Lỗi khi lấy meta list: {e}"
@mcp.tool()
def delete_context(project_id: str, key: str):
"""Xóa một context cụ thể theo key trong project."""
"""Delete one context entry by key."""
unique_string = f"{project_id}_{key}"
unique_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, unique_string))
from qdrant_client.models import PointIdsList
qdrant.delete(
collection_name=COLLECTION_NAME,
points_selector=PointIdsList(points=[unique_id])
)
qdrant.delete(collection_name=COLLECTION_NAME, points_selector=PointIdsList(points=[unique_id]))
return f"Deleted context for project '{project_id}' with key: {key}"
@mcp.tool()
def delete_project_context(project_id: str):
"""Xóa toàn bộ context của một project trong Qdrant."""
from qdrant_client.models import FilterSelector, Filter, FieldCondition, MatchValue
"""Delete all context entries of a project."""
from qdrant_client.models import FilterSelector
qdrant.delete(
collection_name=COLLECTION_NAME,
points_selector=FilterSelector(
filter=Filter(
must=[
FieldCondition(
key="project_id",
match=MatchValue(value=project_id)
)
]
must=[FieldCondition(key="project_id", match=MatchValue(value=str(project_id)))]
)
)
),
)
return f"Deleted all contexts for project '{project_id}'"
if __name__ == "__main__":
mcp.run()
mcp.run()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment