memgraph-ingester-mcp 0.1.0__tar.gz

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,43 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ environment:
13
+ name: pypi
14
+ permissions:
15
+ contents: read
16
+ id-token: write
17
+
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@v6
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v7
24
+ with:
25
+ enable-cache: true
26
+
27
+ - name: Install Python
28
+ run: uv python install 3.13
29
+
30
+ - name: Install dependencies
31
+ run: uv sync --locked --dev
32
+
33
+ - name: Lint
34
+ run: uv run ruff check .
35
+
36
+ - name: Test
37
+ run: uv run pytest
38
+
39
+ - name: Build
40
+ run: uv build --no-sources
41
+
42
+ - name: Publish
43
+ run: uv publish
@@ -0,0 +1,11 @@
1
+ .DS_Store
2
+ .ruff_cache/
3
+ .pytest_cache/
4
+ .uv-cache/
5
+ .venv/
6
+ __pycache__/
7
+ *.py[cod]
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+ .idea
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oleksii Usatov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: memgraph-ingester-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP tools for Memgraph graphs created by memgraph-ingester
5
+ Project-URL: Homepage, https://github.com/ousatov-ua/memgraph-ingester-mcp
6
+ Project-URL: Repository, https://github.com/ousatov-ua/memgraph-ingester-mcp
7
+ Project-URL: Issues, https://github.com/ousatov-ua/memgraph-ingester-mcp/issues
8
+ Author: Oleksii Usatov
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: codex,knowledge-graph,mcp,memgraph,rag
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Database
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: mcp>=1.16.0
23
+ Requires-Dist: neo4j>=5.28.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ # memgraph-ingester-mcp
27
+
28
+ `memgraph-ingester-mcp` exposes safe, high-level MCP tools for Memgraph graphs produced by
29
+ [`memgraph-ingester`](https://github.com/ousatov-ua/memgraph-ingester). It keeps agents out of long
30
+ Cypher instruction blocks for normal work while still offering a project-scoped read-only Cypher
31
+ escape hatch for unusual lookups.
32
+
33
+ The package is intended to be published to PyPI as `memgraph-ingester-mcp` under the
34
+ `ousatov-ua` account.
35
+
36
+ ## Tools
37
+
38
+ Code graph tools:
39
+
40
+ - `server_status`: graph inventory, memory counts, and vector index status.
41
+ - `code_orientation`: compact package/type/call overview.
42
+ - `code_search`: CodeChunk vector search for broad discovery.
43
+ - `code_lookup_type`: exact class/interface/annotation lookup with members.
44
+ - `code_lookup_methods`: exact method lookup with source ranges.
45
+ - `code_callers`, `code_callees`: call graph lookups.
46
+ - `code_hierarchy`: class ancestry, children, interfaces, and interface implementors.
47
+
48
+ Memory tools:
49
+
50
+ - `memory_orientation`: rules plus open findings, tasks, questions, and risks.
51
+ - `memory_search`: MemoryChunk vector search with index-only hit metadata.
52
+ - `memory_get`: canonical memory node plus resolved CodeRefs.
53
+ - `memory_upsert`: create/update Decision, ADR, Rule, Context, Finding, Task, Risk, Question, or Idea.
54
+ - `memory_update_status`: lifecycle status update.
55
+ - `memory_link_code_ref`: link memory to a resolved CodeRef target.
56
+ - `memory_refresh_chunk`, `memory_refresh_embeddings`: maintain derived memory RAG data.
57
+
58
+ Fallback:
59
+
60
+ - `raw_read_cypher`: read-only, project-scoped Cypher for edge cases.
61
+
62
+ ## Configuration
63
+
64
+ Use namespaced environment variables so this server does not collide with generic shell settings:
65
+
66
+ | Variable | Default | Purpose |
67
+ | --- | --- | --- |
68
+ | `MEMGRAPH_INGESTER_MCP_BOLT_URI` | `bolt://127.0.0.1:7687` | Memgraph Bolt URI |
69
+ | `MEMGRAPH_INGESTER_MCP_USERNAME` | unset | Optional username |
70
+ | `MEMGRAPH_INGESTER_MCP_PASSWORD` | unset | Optional password |
71
+ | `MEMGRAPH_INGESTER_MCP_DATABASE` | unset | Optional database name |
72
+ | `MEMGRAPH_INGESTER_MCP_PROJECT` | unset | Default project name |
73
+ | `MEMGRAPH_INGESTER_MCP_QUERY_TIMEOUT_SECONDS` | `30` | Query timeout |
74
+ | `MEMGRAPH_INGESTER_MCP_READ_ONLY` | `false` | Disable write tools when `true` |
75
+ | `MEMGRAPH_INGESTER_MCP_EMBEDDING_MODEL` | `default` | Metadata stamped on refreshed chunks |
76
+ | `MEMGRAPH_INGESTER_MCP_EMBEDDING_DIMENSIONS` | `384` | Expected memory embedding dimension |
77
+
78
+ ## Run Locally
79
+
80
+ ```bash
81
+ uv sync --dev
82
+ uv run memgraph-ingester-mcp
83
+ ```
84
+
85
+ Run tests:
86
+
87
+ ```bash
88
+ uv run ruff check .
89
+ uv run pytest
90
+ ```
91
+
92
+ Build the package:
93
+
94
+ ```bash
95
+ uv build --no-sources
96
+ ```
97
+
98
+ ## Client Setup
99
+
100
+ Replace `my-project` with the project name used when running `memgraph-ingester`.
101
+
102
+ ### Codex
103
+
104
+ Add this to `~/.codex/config.toml`:
105
+
106
+ ```toml
107
+ [mcp_servers.memgraphIngester]
108
+ command = "uvx"
109
+ args = ["memgraph-ingester-mcp"]
110
+ startup_timeout_ms = 20_000
111
+
112
+ [mcp_servers.memgraphIngester.env]
113
+ MEMGRAPH_INGESTER_MCP_BOLT_URI = "bolt://127.0.0.1:7687"
114
+ MEMGRAPH_INGESTER_MCP_PROJECT = "my-project"
115
+ ```
116
+
117
+ ### Claude Desktop
118
+
119
+ Add this to `claude_desktop_config.json`:
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "memgraphIngester": {
125
+ "type": "stdio",
126
+ "command": "uvx",
127
+ "args": ["memgraph-ingester-mcp"],
128
+ "env": {
129
+ "MEMGRAPH_INGESTER_MCP_BOLT_URI": "bolt://127.0.0.1:7687",
130
+ "MEMGRAPH_INGESTER_MCP_PROJECT": "my-project"
131
+ }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ Claude Code can also add the same stdio server directly:
138
+
139
+ ```bash
140
+ claude mcp add-json memgraphIngester \
141
+ '{"type":"stdio","command":"uvx","args":["memgraph-ingester-mcp"],"env":{"MEMGRAPH_INGESTER_MCP_BOLT_URI":"bolt://127.0.0.1:7687","MEMGRAPH_INGESTER_MCP_PROJECT":"my-project"}}'
142
+ ```
143
+
144
+ ### GitHub Copilot in VS Code
145
+
146
+ Create `.vscode/mcp.json` in the workspace or add the server to your user MCP configuration:
147
+
148
+ ```json
149
+ {
150
+ "servers": {
151
+ "memgraphIngester": {
152
+ "type": "stdio",
153
+ "command": "uvx",
154
+ "args": ["memgraph-ingester-mcp"],
155
+ "env": {
156
+ "MEMGRAPH_INGESTER_MCP_BOLT_URI": "bolt://127.0.0.1:7687",
157
+ "MEMGRAPH_INGESTER_MCP_PROJECT": "my-project"
158
+ }
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### Gemini CLI
165
+
166
+ Add this to Gemini CLI `settings.json`:
167
+
168
+ ```json
169
+ {
170
+ "mcpServers": {
171
+ "memgraphIngester": {
172
+ "command": "uvx",
173
+ "args": ["memgraph-ingester-mcp"],
174
+ "env": {
175
+ "MEMGRAPH_INGESTER_MCP_BOLT_URI": "bolt://127.0.0.1:7687",
176
+ "MEMGRAPH_INGESTER_MCP_PROJECT": "my-project"
177
+ },
178
+ "trust": false
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ ## Agent Guidance
185
+
186
+ Prefer the dedicated tools over `raw_read_cypher`. Use RAG search tools only for broad discovery,
187
+ then follow up with exact lookup tools before making claims or edits. Use memory write tools for
188
+ task lifecycle changes and CodeRef links so derived MemoryChunks stay refreshable.
@@ -0,0 +1,163 @@
1
+ # memgraph-ingester-mcp
2
+
3
+ `memgraph-ingester-mcp` exposes safe, high-level MCP tools for Memgraph graphs produced by
4
+ [`memgraph-ingester`](https://github.com/ousatov-ua/memgraph-ingester). It keeps agents out of long
5
+ Cypher instruction blocks for normal work while still offering a project-scoped read-only Cypher
6
+ escape hatch for unusual lookups.
7
+
8
+ The package is intended to be published to PyPI as `memgraph-ingester-mcp` under the
9
+ `ousatov-ua` account.
10
+
11
+ ## Tools
12
+
13
+ Code graph tools:
14
+
15
+ - `server_status`: graph inventory, memory counts, and vector index status.
16
+ - `code_orientation`: compact package/type/call overview.
17
+ - `code_search`: CodeChunk vector search for broad discovery.
18
+ - `code_lookup_type`: exact class/interface/annotation lookup with members.
19
+ - `code_lookup_methods`: exact method lookup with source ranges.
20
+ - `code_callers`, `code_callees`: call graph lookups.
21
+ - `code_hierarchy`: class ancestry, children, interfaces, and interface implementors.
22
+
23
+ Memory tools:
24
+
25
+ - `memory_orientation`: rules plus open findings, tasks, questions, and risks.
26
+ - `memory_search`: MemoryChunk vector search with index-only hit metadata.
27
+ - `memory_get`: canonical memory node plus resolved CodeRefs.
28
+ - `memory_upsert`: create/update Decision, ADR, Rule, Context, Finding, Task, Risk, Question, or Idea.
29
+ - `memory_update_status`: lifecycle status update.
30
+ - `memory_link_code_ref`: link memory to a resolved CodeRef target.
31
+ - `memory_refresh_chunk`, `memory_refresh_embeddings`: maintain derived memory RAG data.
32
+
33
+ Fallback:
34
+
35
+ - `raw_read_cypher`: read-only, project-scoped Cypher for edge cases.
36
+
37
+ ## Configuration
38
+
39
+ Use namespaced environment variables so this server does not collide with generic shell settings:
40
+
41
+ | Variable | Default | Purpose |
42
+ | --- | --- | --- |
43
+ | `MEMGRAPH_INGESTER_MCP_BOLT_URI` | `bolt://127.0.0.1:7687` | Memgraph Bolt URI |
44
+ | `MEMGRAPH_INGESTER_MCP_USERNAME` | unset | Optional username |
45
+ | `MEMGRAPH_INGESTER_MCP_PASSWORD` | unset | Optional password |
46
+ | `MEMGRAPH_INGESTER_MCP_DATABASE` | unset | Optional database name |
47
+ | `MEMGRAPH_INGESTER_MCP_PROJECT` | unset | Default project name |
48
+ | `MEMGRAPH_INGESTER_MCP_QUERY_TIMEOUT_SECONDS` | `30` | Query timeout |
49
+ | `MEMGRAPH_INGESTER_MCP_READ_ONLY` | `false` | Disable write tools when `true` |
50
+ | `MEMGRAPH_INGESTER_MCP_EMBEDDING_MODEL` | `default` | Metadata stamped on refreshed chunks |
51
+ | `MEMGRAPH_INGESTER_MCP_EMBEDDING_DIMENSIONS` | `384` | Expected memory embedding dimension |
52
+
53
+ ## Run Locally
54
+
55
+ ```bash
56
+ uv sync --dev
57
+ uv run memgraph-ingester-mcp
58
+ ```
59
+
60
+ Run tests:
61
+
62
+ ```bash
63
+ uv run ruff check .
64
+ uv run pytest
65
+ ```
66
+
67
+ Build the package:
68
+
69
+ ```bash
70
+ uv build --no-sources
71
+ ```
72
+
73
+ ## Client Setup
74
+
75
+ Replace `my-project` with the project name used when running `memgraph-ingester`.
76
+
77
+ ### Codex
78
+
79
+ Add this to `~/.codex/config.toml`:
80
+
81
+ ```toml
82
+ [mcp_servers.memgraphIngester]
83
+ command = "uvx"
84
+ args = ["memgraph-ingester-mcp"]
85
+ startup_timeout_ms = 20_000
86
+
87
+ [mcp_servers.memgraphIngester.env]
88
+ MEMGRAPH_INGESTER_MCP_BOLT_URI = "bolt://127.0.0.1:7687"
89
+ MEMGRAPH_INGESTER_MCP_PROJECT = "my-project"
90
+ ```
91
+
92
+ ### Claude Desktop
93
+
94
+ Add this to `claude_desktop_config.json`:
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "memgraphIngester": {
100
+ "type": "stdio",
101
+ "command": "uvx",
102
+ "args": ["memgraph-ingester-mcp"],
103
+ "env": {
104
+ "MEMGRAPH_INGESTER_MCP_BOLT_URI": "bolt://127.0.0.1:7687",
105
+ "MEMGRAPH_INGESTER_MCP_PROJECT": "my-project"
106
+ }
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
112
+ Claude Code can also add the same stdio server directly:
113
+
114
+ ```bash
115
+ claude mcp add-json memgraphIngester \
116
+ '{"type":"stdio","command":"uvx","args":["memgraph-ingester-mcp"],"env":{"MEMGRAPH_INGESTER_MCP_BOLT_URI":"bolt://127.0.0.1:7687","MEMGRAPH_INGESTER_MCP_PROJECT":"my-project"}}'
117
+ ```
118
+
119
+ ### GitHub Copilot in VS Code
120
+
121
+ Create `.vscode/mcp.json` in the workspace or add the server to your user MCP configuration:
122
+
123
+ ```json
124
+ {
125
+ "servers": {
126
+ "memgraphIngester": {
127
+ "type": "stdio",
128
+ "command": "uvx",
129
+ "args": ["memgraph-ingester-mcp"],
130
+ "env": {
131
+ "MEMGRAPH_INGESTER_MCP_BOLT_URI": "bolt://127.0.0.1:7687",
132
+ "MEMGRAPH_INGESTER_MCP_PROJECT": "my-project"
133
+ }
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Gemini CLI
140
+
141
+ Add this to Gemini CLI `settings.json`:
142
+
143
+ ```json
144
+ {
145
+ "mcpServers": {
146
+ "memgraphIngester": {
147
+ "command": "uvx",
148
+ "args": ["memgraph-ingester-mcp"],
149
+ "env": {
150
+ "MEMGRAPH_INGESTER_MCP_BOLT_URI": "bolt://127.0.0.1:7687",
151
+ "MEMGRAPH_INGESTER_MCP_PROJECT": "my-project"
152
+ },
153
+ "trust": false
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ ## Agent Guidance
160
+
161
+ Prefer the dedicated tools over `raw_read_cypher`. Use RAG search tools only for broad discovery,
162
+ then follow up with exact lookup tools before making claims or edits. Use memory write tools for
163
+ task lifecycle changes and CodeRef links so derived MemoryChunks stay refreshable.
@@ -0,0 +1,56 @@
1
+ [project]
2
+ name = "memgraph-ingester-mcp"
3
+ version = "0.1.0"
4
+ description = "MCP tools for Memgraph graphs created by memgraph-ingester"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ authors = [{ name = "Oleksii Usatov" }]
9
+ keywords = ["mcp", "memgraph", "knowledge-graph", "rag", "codex"]
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Database",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ ]
21
+ dependencies = [
22
+ "mcp>=1.16.0",
23
+ "neo4j>=5.28.0",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/ousatov-ua/memgraph-ingester-mcp"
28
+ Repository = "https://github.com/ousatov-ua/memgraph-ingester-mcp"
29
+ Issues = "https://github.com/ousatov-ua/memgraph-ingester-mcp/issues"
30
+
31
+ [project.scripts]
32
+ memgraph-ingester-mcp = "memgraph_ingester_mcp.server:main"
33
+
34
+ [dependency-groups]
35
+ dev = [
36
+ "pytest>=8.3.0",
37
+ "ruff>=0.8.0",
38
+ ]
39
+
40
+ [build-system]
41
+ requires = ["hatchling"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/memgraph_ingester_mcp"]
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+ target-version = "py311"
50
+
51
+ [tool.ruff.lint]
52
+ select = ["E", "F", "I", "UP", "B", "SIM", "C4", "RUF"]
53
+
54
+ [tool.pytest.ini_options]
55
+ addopts = "-q"
56
+ testpaths = ["tests"]
@@ -0,0 +1,5 @@
1
+ """MCP server for Memgraph Ingester graphs."""
2
+
3
+ from memgraph_ingester_mcp.config import MemgraphConfig
4
+
5
+ __all__ = ["MemgraphConfig"]
@@ -0,0 +1,6 @@
1
+ """Run the Memgraph Ingester MCP server."""
2
+
3
+ from memgraph_ingester_mcp.server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,74 @@
1
+ """Runtime configuration for the MCP server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from os import getenv
7
+
8
+
9
+ def _optional_env(name: str) -> str | None:
10
+ value = getenv(name)
11
+ if value is None or value == "":
12
+ return None
13
+ return value
14
+
15
+
16
+ def _bool_env(name: str, default: bool) -> bool:
17
+ value = getenv(name)
18
+ if value is None or value == "":
19
+ return default
20
+ return value.strip().lower() in {"1", "true", "yes", "on"}
21
+
22
+
23
+ def _float_env(name: str, default: float) -> float:
24
+ value = getenv(name)
25
+ if value is None or value == "":
26
+ return default
27
+ return float(value)
28
+
29
+
30
+ def _int_env(name: str, default: int) -> int:
31
+ value = getenv(name)
32
+ if value is None or value == "":
33
+ return default
34
+ return int(value)
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class MemgraphConfig:
39
+ """Namespaced Memgraph settings used by the stdio MCP process."""
40
+
41
+ bolt_uri: str = "bolt://127.0.0.1:7687"
42
+ username: str | None = None
43
+ password: str | None = None
44
+ database: str | None = None
45
+ default_project: str | None = None
46
+ query_timeout_seconds: float = 30.0
47
+ read_only: bool = False
48
+ embedding_model_name: str = "default"
49
+ embedding_dimensions: int = 384
50
+
51
+ @classmethod
52
+ def from_environment(cls) -> MemgraphConfig:
53
+ """Load only MCP-specific environment variables to avoid generic name collisions."""
54
+
55
+ return cls(
56
+ bolt_uri=getenv("MEMGRAPH_INGESTER_MCP_BOLT_URI", cls.bolt_uri),
57
+ username=_optional_env("MEMGRAPH_INGESTER_MCP_USERNAME"),
58
+ password=_optional_env("MEMGRAPH_INGESTER_MCP_PASSWORD"),
59
+ database=_optional_env("MEMGRAPH_INGESTER_MCP_DATABASE"),
60
+ default_project=_optional_env("MEMGRAPH_INGESTER_MCP_PROJECT"),
61
+ query_timeout_seconds=_float_env(
62
+ "MEMGRAPH_INGESTER_MCP_QUERY_TIMEOUT_SECONDS",
63
+ cls.query_timeout_seconds,
64
+ ),
65
+ read_only=_bool_env("MEMGRAPH_INGESTER_MCP_READ_ONLY", cls.read_only),
66
+ embedding_model_name=getenv(
67
+ "MEMGRAPH_INGESTER_MCP_EMBEDDING_MODEL",
68
+ cls.embedding_model_name,
69
+ ),
70
+ embedding_dimensions=_int_env(
71
+ "MEMGRAPH_INGESTER_MCP_EMBEDDING_DIMENSIONS",
72
+ cls.embedding_dimensions,
73
+ ),
74
+ )
@@ -0,0 +1,65 @@
1
+ """Thin Memgraph Bolt client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from datetime import date, datetime
7
+ from typing import Any
8
+
9
+ from neo4j import GraphDatabase, Query
10
+ from neo4j.time import Date, DateTime, Duration, Time
11
+
12
+ from memgraph_ingester_mcp.config import MemgraphConfig
13
+
14
+
15
+ class MemgraphError(RuntimeError):
16
+ """Raised when an MCP operation cannot be executed safely."""
17
+
18
+
19
+ def _to_jsonable(value: Any) -> Any:
20
+ if isinstance(value, Mapping):
21
+ return {str(key): _to_jsonable(item) for key, item in value.items()}
22
+ if isinstance(value, list | tuple):
23
+ return [_to_jsonable(item) for item in value]
24
+ if isinstance(value, DateTime | Date | Time | Duration | datetime | date):
25
+ return str(value)
26
+ return value
27
+
28
+
29
+ class MemgraphClient:
30
+ """Execute parameterized Cypher against Memgraph over Bolt."""
31
+
32
+ def __init__(self, config: MemgraphConfig, driver: Any | None = None) -> None:
33
+ self.config = config
34
+ if driver is None:
35
+ auth = None
36
+ if config.username is not None:
37
+ auth = (config.username, config.password or "")
38
+ driver = GraphDatabase.driver(config.bolt_uri, auth=auth)
39
+ self._driver = driver
40
+
41
+ def close(self) -> None:
42
+ close = getattr(self._driver, "close", None)
43
+ if close is not None:
44
+ close()
45
+
46
+ def run(
47
+ self,
48
+ query: str,
49
+ parameters: Mapping[str, Any] | None = None,
50
+ *,
51
+ write: bool = False,
52
+ ) -> list[dict[str, Any]]:
53
+ if write and self.config.read_only:
54
+ raise MemgraphError("Write tools are disabled by MEMGRAPH_INGESTER_MCP_READ_ONLY.")
55
+
56
+ session_kwargs: dict[str, Any] = {}
57
+ if self.config.database is not None:
58
+ session_kwargs["database"] = self.config.database
59
+
60
+ with self._driver.session(**session_kwargs) as session:
61
+ result = session.run(
62
+ Query(query, timeout=self.config.query_timeout_seconds),
63
+ dict(parameters or {}),
64
+ )
65
+ return [_to_jsonable(dict(record)) for record in result]