pgmnemo-mcp 0.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pgmnemo_mcp/__init__.py +7 -0
- pgmnemo_mcp/__main__.py +56 -0
- pgmnemo_mcp/config.py +16 -0
- pgmnemo_mcp/server.py +109 -0
- pgmnemo_mcp-0.5.2.dist-info/METADATA +10 -0
- pgmnemo_mcp-0.5.2.dist-info/RECORD +9 -0
- pgmnemo_mcp-0.5.2.dist-info/WHEEL +5 -0
- pgmnemo_mcp-0.5.2.dist-info/entry_points.txt +2 -0
- pgmnemo_mcp-0.5.2.dist-info/top_level.txt +1 -0
pgmnemo_mcp/__init__.py
ADDED
pgmnemo_mcp/__main__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""python -m pgmnemo_mcp — CLI entry point with --smoke flag."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main() -> None:
|
|
8
|
+
parser = argparse.ArgumentParser(prog="pgmnemo-mcp")
|
|
9
|
+
parser.add_argument(
|
|
10
|
+
"--smoke",
|
|
11
|
+
action="store_true",
|
|
12
|
+
help="Run a connectivity smoke test: connect to DB and call recall_lessons().",
|
|
13
|
+
)
|
|
14
|
+
args = parser.parse_args()
|
|
15
|
+
|
|
16
|
+
if args.smoke:
|
|
17
|
+
_run_smoke()
|
|
18
|
+
else:
|
|
19
|
+
from .server import run
|
|
20
|
+
run()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _run_smoke() -> None:
|
|
24
|
+
from .config import DATABASE_URL, get_pool
|
|
25
|
+
|
|
26
|
+
print(f"pgmnemo-mcp smoke: connecting to {_redact(DATABASE_URL)} …")
|
|
27
|
+
try:
|
|
28
|
+
pool = get_pool()
|
|
29
|
+
conn = pool.getconn()
|
|
30
|
+
try:
|
|
31
|
+
with conn.cursor() as cur:
|
|
32
|
+
cur.execute(
|
|
33
|
+
"SELECT count(*) FROM pgmnemo.recall_lessons("
|
|
34
|
+
"NULL::vector(1024), 5, NULL, NULL, 'test')"
|
|
35
|
+
)
|
|
36
|
+
row = cur.fetchone()
|
|
37
|
+
pool.putconn(conn)
|
|
38
|
+
except Exception:
|
|
39
|
+
pool.putconn(conn)
|
|
40
|
+
raise
|
|
41
|
+
except Exception as exc:
|
|
42
|
+
print(f"pgmnemo-mcp smoke: FAIL — {exc}", file=sys.stderr)
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
print(f"pgmnemo-mcp smoke: OK (recall_lessons returned {row[0]} rows)")
|
|
46
|
+
sys.exit(0)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _redact(url: str) -> str:
|
|
50
|
+
"""Hide password in DATABASE_URL for safe printing."""
|
|
51
|
+
import re
|
|
52
|
+
return re.sub(r"://([^:@]+):([^@]+)@", r"://\1:***@", url)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
main()
|
pgmnemo_mcp/config.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from psycopg2 import pool
|
|
3
|
+
|
|
4
|
+
__all__ = ["DATABASE_URL", "MCP_PORT", "get_pool"]
|
|
5
|
+
|
|
6
|
+
DATABASE_URL: str = os.environ.get("DATABASE_URL", "postgresql://localhost/pgmnemo")
|
|
7
|
+
MCP_PORT: int = int(os.environ.get("MCP_PORT", "8765"))
|
|
8
|
+
|
|
9
|
+
_pool: pool.SimpleConnectionPool | None = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_pool() -> pool.SimpleConnectionPool:
|
|
13
|
+
global _pool
|
|
14
|
+
if _pool is None:
|
|
15
|
+
_pool = pool.SimpleConnectionPool(1, 5, dsn=DATABASE_URL)
|
|
16
|
+
return _pool
|
pgmnemo_mcp/server.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""pgmnemo MCP server — exposes ingest and recall as MCP tools.
|
|
2
|
+
|
|
3
|
+
Transport note (BUG-3 resolution):
|
|
4
|
+
FastMCP uses MCP protocol transport (stdio by default, SSE/streamable-http
|
|
5
|
+
optionally). It does NOT expose REST endpoints at /ingest or /recall.
|
|
6
|
+
Clients must use the MCP JSON-RPC protocol (stdio pipe or SSE at /sse).
|
|
7
|
+
The --smoke command in __main__.py exercises the DB layer directly
|
|
8
|
+
without going through the MCP transport.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from mcp.server.fastmcp import FastMCP
|
|
17
|
+
|
|
18
|
+
from .config import get_pool
|
|
19
|
+
|
|
20
|
+
mcp = FastMCP("pgmnemo", port=8765)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@mcp.tool(name="pgmnemo.ingest", description="Ingest a lesson into pgmnemo agent memory.")
|
|
24
|
+
def ingest(
|
|
25
|
+
text: str,
|
|
26
|
+
role: str = "mcp_agent",
|
|
27
|
+
topic: str = "general",
|
|
28
|
+
importance: int = 3,
|
|
29
|
+
project_id: int = 1,
|
|
30
|
+
commit_sha: str | None = None,
|
|
31
|
+
artifact_hash: str | None = None,
|
|
32
|
+
metadata: dict[str, Any] | None = None,
|
|
33
|
+
) -> dict[str, Any]:
|
|
34
|
+
"""Store a lesson via pgmnemo.ingest() SP and return its id.
|
|
35
|
+
|
|
36
|
+
Uses the pgmnemo.ingest() stored procedure instead of raw INSERT so that:
|
|
37
|
+
- Gate enforcement (provenance checks) runs inside the SP.
|
|
38
|
+
- verified_at is stamped automatically when commit_sha/artifact_hash are present.
|
|
39
|
+
- Embedding dimension validation fires before the INSERT.
|
|
40
|
+
"""
|
|
41
|
+
p = get_pool()
|
|
42
|
+
conn = p.getconn()
|
|
43
|
+
try:
|
|
44
|
+
with conn.cursor() as cur:
|
|
45
|
+
cur.execute(
|
|
46
|
+
"""
|
|
47
|
+
SELECT pgmnemo.ingest(
|
|
48
|
+
%s, %s, %s, %s, %s::smallint,
|
|
49
|
+
NULL::vector(1024), %s, %s, %s::jsonb
|
|
50
|
+
)
|
|
51
|
+
""",
|
|
52
|
+
(
|
|
53
|
+
role,
|
|
54
|
+
project_id,
|
|
55
|
+
topic,
|
|
56
|
+
text,
|
|
57
|
+
importance,
|
|
58
|
+
commit_sha,
|
|
59
|
+
artifact_hash,
|
|
60
|
+
json.dumps(metadata) if metadata is not None else "{}",
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
new_id = cur.fetchone()[0]
|
|
64
|
+
conn.commit()
|
|
65
|
+
return {"id": new_id}
|
|
66
|
+
finally:
|
|
67
|
+
p.putconn(conn)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@mcp.tool(name="pgmnemo.recall", description="Recall lessons from pgmnemo agent memory.")
|
|
71
|
+
def recall(query: str, top_k: int = 5) -> list[dict[str, Any]]:
|
|
72
|
+
"""Return up to top_k lessons whose text matches query via pgmnemo.recall_lessons.
|
|
73
|
+
|
|
74
|
+
recall_lessons() RETURNS TABLE (lesson_id bigint, score, role, ...) — the output
|
|
75
|
+
column is 'lesson_id' (an alias), not 'id' (the physical table column).
|
|
76
|
+
"""
|
|
77
|
+
p = get_pool()
|
|
78
|
+
conn = p.getconn()
|
|
79
|
+
try:
|
|
80
|
+
with conn.cursor() as cur:
|
|
81
|
+
# recall_lessons(query_vec, top_k, role_filter, project_id_filter, query_text)
|
|
82
|
+
# Pass NULL vector — rely on query_text for BM25/hybrid keyword match.
|
|
83
|
+
cur.execute(
|
|
84
|
+
"""
|
|
85
|
+
SELECT lesson_id, role, topic, lesson_text, importance, created_at
|
|
86
|
+
FROM pgmnemo.recall_lessons(
|
|
87
|
+
NULL::vector(1024), %s, NULL, NULL, %s
|
|
88
|
+
)
|
|
89
|
+
""",
|
|
90
|
+
(top_k, query),
|
|
91
|
+
)
|
|
92
|
+
cols = [d[0] for d in cur.description]
|
|
93
|
+
return [dict(zip(cols, row)) for row in cur.fetchall()]
|
|
94
|
+
finally:
|
|
95
|
+
p.putconn(conn)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def run() -> None:
|
|
99
|
+
"""Entry point for `python -m pgmnemo_mcp.server`."""
|
|
100
|
+
mcp.run()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main() -> None:
|
|
104
|
+
"""Console script entry point (pgmnemo-mcp = pgmnemo_mcp.server:main)."""
|
|
105
|
+
run()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgmnemo-mcp
|
|
3
|
+
Version: 0.5.2
|
|
4
|
+
Summary: MCP server wrapping pgmnemo ingest and recall for AI agent memory
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: mcp>=1.0
|
|
9
|
+
Requires-Dist: psycopg2-binary>=2.9
|
|
10
|
+
Requires-Dist: pydantic>=2.0
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pgmnemo_mcp/__init__.py,sha256=fdbWQ7cWSPFZWnGHZcgsPsID8_6jrLzwDiMq9eSrar8,207
|
|
2
|
+
pgmnemo_mcp/__main__.py,sha256=C70Hy0oI3NAOWlp2KImAvqZ1WkU6NHkSZjtv5J56s5I,1478
|
|
3
|
+
pgmnemo_mcp/config.py,sha256=W-oonbahlggYdvrTW9HxY6KWxovANy2wSAdOLg7CAVE,447
|
|
4
|
+
pgmnemo_mcp/server.py,sha256=1CJ-8OfrocRPKFfqWa_KQRm3oiOER-13pYpYv9OhDMc,3537
|
|
5
|
+
pgmnemo_mcp-0.5.2.dist-info/METADATA,sha256=z6Y6xV6LvbbcO0hIWOzjvv2p3SCTB95mYG3qnXyHzvg,303
|
|
6
|
+
pgmnemo_mcp-0.5.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
pgmnemo_mcp-0.5.2.dist-info/entry_points.txt,sha256=Pit6nwvR6ZCXd6yu9MYiycaQIKcXYzq80GbESAD8vIc,55
|
|
8
|
+
pgmnemo_mcp-0.5.2.dist-info/top_level.txt,sha256=dzfHsNhBFdEcp1eCqIdY6kpX_PCevAQsS2Kz3lBcADc,12
|
|
9
|
+
pgmnemo_mcp-0.5.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pgmnemo_mcp
|