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.
@@ -0,0 +1,7 @@
1
+ """pgmnemo-mcp — MCP server exposing pgmnemo ingest and recall tools."""
2
+
3
+ __version__ = "0.5.0"
4
+
5
+ from .server import mcp, ingest, recall, main
6
+
7
+ __all__ = ["mcp", "ingest", "recall", "main", "__version__"]
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pgmnemo-mcp = pgmnemo_mcp.server:run
@@ -0,0 +1 @@
1
+ pgmnemo_mcp