Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
meu-fastmcp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Đặng Trần Nguyên Khang
meu-fastmcp
Commits
1929f5b8
Commit
1929f5b8
authored
Mar 02, 2026
by
dangdoan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update stdio
parent
25bf8018
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
128 additions
and
113 deletions
+128
-113
mcp.json
.vscode/mcp.json
+13
-13
memory_mcp_server_stdio.py
memory_mcp_server_stdio.py
+115
-100
No files found.
.vscode/mcp.json
View file @
1929f5b8
//
{
//
"servers"
:
{
//
"asisstant-memory"
:
{
//
"command"
:
"python"
,
//
"args"
:
[
"c:
\\
Work
\\
MeU Solutions
\\
Project
\\
meu-fastmcp
\\
memory_mcp_server.py"
],
//
"type"
:
"stdio"
//
}
//
}
//
}
//Dùng
http
server
thay
vì
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"
}
}
}
//Dùng
http
server
thay
vì
stdio
để
dễ
debug
hơn
//
{
//
"servers"
:
{
//
"assistant-memory"
:
{
//
"type"
:
"http"
,
//
"url"
:
"http://localhost:8090"
//
}
//
}
//
}
\ No newline at end of file
memory_mcp_server_stdio.py
View file @
1929f5b8
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,25 +52,26 @@ 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."
@
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
(
points
=
[
PointStruct
(
id
=
unique_id
,
vector
=
vector
,
payload
=
{
...
...
@@ -61,11 +79,13 @@ def save_context(project_id: str, key: str, content: str):
"key"
:
key
,
"content"
:
content
,
"updated_at"
:
datetime
.
now
()
.
isoformat
(),
"category"
:
key
.
split
(
":"
)[
0
]
if
":"
in
key
else
"unknown"
}
)]
"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,26 +94,24 @@ 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
(
points
=
[
PointStruct
(
id
=
meta_id
,
vector
=
meta_vector
,
payload
=
{
...
...
@@ -101,106 +119,103 @@ def save_context(project_id: str, key: str, content: str):
"key"
:
meta_key
,
"content"
:
meta_content
,
"type"
:
"meta_keys_list"
,
"updated_at"
:
datetime
.
now
()
.
isoformat
()
}
)]
"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
(
must
=
[
FieldCondition
(
key
=
"project_id"
,
match
=
MatchValue
(
value
=
str
(
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"
:
if
item
.
get
(
"type"
)
!=
"blob"
:
continue
path
=
item
.
get
(
"path"
)
try
:
file
=
project
.
files
.
get
(
item
[
"path"
],
ref
=
"main"
)
content
=
file
.
decode
()
.
decode
(
"utf-8
"
)[:
2000
]
save_context
(
project_id
,
item
[
"path"
]
,
content
)
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
as
e
:
print
(
f
"Error indexing {item['path']}: {e}"
)
except
Exception
:
continue
return
f
"Indexed {indexed} files for project '{project_id}' (GitLab ID: {gitlab_project_id})"
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
."""
"""Li
st 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}'"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment