kg-mcp 0.1.8__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,112 @@
1
+ """
2
+ Origin validation middleware for MCP server.
3
+ Prevents DNS rebinding attacks by validating Origin headers.
4
+ """
5
+
6
+ import fnmatch
7
+ import logging
8
+ from typing import Callable, List, Optional
9
+
10
+ from kg_mcp.config import get_settings
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class OriginValidationError(Exception):
16
+ """Raised when origin validation fails."""
17
+
18
+ pass
19
+
20
+
21
+ def validate_origin(origin: Optional[str], allowed_origins: List[str]) -> bool:
22
+ """
23
+ Validate if an origin is allowed.
24
+
25
+ Args:
26
+ origin: The Origin header value
27
+ allowed_origins: List of allowed origin patterns (supports wildcards)
28
+
29
+ Returns:
30
+ True if the origin is allowed
31
+ """
32
+ if not origin:
33
+ # No origin header - might be a same-origin request or non-browser client
34
+ # For MCP servers, we typically allow this
35
+ return True
36
+
37
+ if not allowed_origins:
38
+ # No allowlist configured - only allow localhost by default
39
+ allowed_origins = ["http://localhost:*", "http://127.0.0.1:*"]
40
+
41
+ for pattern in allowed_origins:
42
+ if fnmatch.fnmatch(origin, pattern):
43
+ return True
44
+
45
+ return False
46
+
47
+
48
+ def create_origin_middleware() -> Callable:
49
+ """
50
+ Create an origin validation middleware function.
51
+
52
+ Returns:
53
+ A middleware function that validates Origin headers
54
+ """
55
+
56
+ async def origin_middleware(request, call_next):
57
+ """Middleware to validate Origin headers."""
58
+ settings = get_settings()
59
+ allowed_origins = settings.allowed_origins_list
60
+
61
+ origin = request.headers.get("origin")
62
+
63
+ if not validate_origin(origin, allowed_origins):
64
+ logger.warning(
65
+ f"Rejected request with disallowed origin: {origin} "
66
+ f"(allowed: {allowed_origins})"
67
+ )
68
+ from starlette.responses import JSONResponse
69
+
70
+ return JSONResponse(
71
+ status_code=403,
72
+ content={"error": f"Origin '{origin}' is not allowed"},
73
+ )
74
+
75
+ # Add CORS headers for allowed origins
76
+ response = await call_next(request)
77
+
78
+ if origin and validate_origin(origin, allowed_origins):
79
+ response.headers["Access-Control-Allow-Origin"] = origin
80
+ response.headers["Access-Control-Allow-Credentials"] = "true"
81
+ response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
82
+ response.headers["Access-Control-Allow-Headers"] = "Authorization, Content-Type"
83
+
84
+ return response
85
+
86
+ return origin_middleware
87
+
88
+
89
+ def is_localhost(host: str) -> bool:
90
+ """
91
+ Check if a host is localhost.
92
+
93
+ Args:
94
+ host: The host to check
95
+
96
+ Returns:
97
+ True if localhost
98
+ """
99
+ localhost_patterns = [
100
+ "localhost",
101
+ "127.0.0.1",
102
+ "::1",
103
+ "[::1]",
104
+ ]
105
+
106
+ # Strip port if present
107
+ if ":" in host and not host.startswith("["):
108
+ host = host.split(":")[0]
109
+ elif host.startswith("[") and "]:" in host:
110
+ host = host.split("]:")[0] + "]"
111
+
112
+ return host.lower() in localhost_patterns
kg_mcp/utils.py ADDED
@@ -0,0 +1,100 @@
1
+ """
2
+ Utility functions for the KG-MCP server.
3
+ """
4
+
5
+ import json
6
+ from datetime import datetime, date, time
7
+ from typing import Any, Dict, List, Union
8
+
9
+
10
+ def serialize_neo4j_value(value: Any) -> Any:
11
+ """
12
+ Recursively serialize Neo4j values to JSON-compatible types.
13
+
14
+ Handles:
15
+ - neo4j.time.DateTime -> ISO string
16
+ - neo4j.time.Date -> ISO string
17
+ - neo4j.time.Time -> ISO string
18
+ - neo4j.time.Duration -> dict
19
+ - neo4j.spatial.Point -> dict
20
+ - nested dicts and lists
21
+ """
22
+ if value is None:
23
+ return None
24
+
25
+ # Handle Neo4j DateTime types
26
+ type_name = type(value).__name__
27
+ module_name = type(value).__module__
28
+
29
+ if module_name.startswith('neo4j'):
30
+ # Neo4j DateTime
31
+ if type_name == 'DateTime':
32
+ return value.isoformat()
33
+ # Neo4j Date
34
+ elif type_name == 'Date':
35
+ return value.isoformat()
36
+ # Neo4j Time
37
+ elif type_name == 'Time':
38
+ return value.isoformat()
39
+ # Neo4j Duration
40
+ elif type_name == 'Duration':
41
+ return {
42
+ 'months': value.months,
43
+ 'days': value.days,
44
+ 'seconds': value.seconds,
45
+ 'nanoseconds': value.nanoseconds,
46
+ }
47
+ # Neo4j Point
48
+ elif type_name == 'Point':
49
+ return {
50
+ 'srid': value.srid,
51
+ 'x': value.x,
52
+ 'y': value.y,
53
+ 'z': getattr(value, 'z', None),
54
+ }
55
+ # Other Neo4j types - convert to string
56
+ else:
57
+ return str(value)
58
+
59
+ # Handle Python datetime types
60
+ if isinstance(value, datetime):
61
+ return value.isoformat()
62
+ if isinstance(value, date):
63
+ return value.isoformat()
64
+ if isinstance(value, time):
65
+ return value.isoformat()
66
+
67
+ # Handle dict - recursively serialize
68
+ if isinstance(value, dict):
69
+ return {k: serialize_neo4j_value(v) for k, v in value.items()}
70
+
71
+ # Handle list - recursively serialize
72
+ if isinstance(value, (list, tuple)):
73
+ return [serialize_neo4j_value(v) for v in value]
74
+
75
+ # Handle sets
76
+ if isinstance(value, set):
77
+ return [serialize_neo4j_value(v) for v in value]
78
+
79
+ # Handle bytes
80
+ if isinstance(value, bytes):
81
+ return value.decode('utf-8', errors='replace')
82
+
83
+ # Return primitives as-is
84
+ return value
85
+
86
+
87
+ def serialize_response(data: Dict[str, Any]) -> Dict[str, Any]:
88
+ """
89
+ Serialize a complete response dictionary for JSON output.
90
+
91
+ Use this to wrap tool responses before returning.
92
+ """
93
+ return serialize_neo4j_value(data)
94
+
95
+
96
+ class Neo4jJSONEncoder(json.JSONEncoder):
97
+ """Custom JSON encoder that handles Neo4j types."""
98
+
99
+ def default(self, obj):
100
+ return serialize_neo4j_value(obj)
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: kg-mcp
3
+ Version: 0.1.8
4
+ Summary: Memory/Knowledge Graph MCP Server for IDE Assistants - Persistent context and knowledge for AI coding agents
5
+ Project-URL: Homepage, https://github.com/Hexecu/mcp-neuralmemory
6
+ Project-URL: Documentation, https://github.com/Hexecu/mcp-neuralmemory#readme
7
+ Project-URL: Repository, https://github.com/Hexecu/mcp-neuralmemory
8
+ Project-URL: Issues, https://github.com/Hexecu/mcp-neuralmemory/issues
9
+ Author: Davide Leopardi
10
+ License: MIT
11
+ Keywords: ai-assistant,gemini,knowledge-graph,llm,mcp,model-context-protocol,neo4j
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: google-auth>=2.0.0
21
+ Requires-Dist: httpx>=0.27.0
22
+ Requires-Dist: litellm>=1.40.0
23
+ Requires-Dist: mcp>=1.0.0
24
+ Requires-Dist: neo4j>=5.0.0
25
+ Requires-Dist: pydantic-settings>=2.0.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: python-dotenv>=1.0.0
28
+ Requires-Dist: rich>=13.0.0
29
+ Requires-Dist: uvicorn>=0.30.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: black>=24.0.0; extra == 'dev'
32
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
33
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
34
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
35
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # MCP-KG-Memory Server
40
+
41
+ Python MCP server implementation with Neo4j backend.
42
+
43
+ ## Development Setup
44
+
45
+ ```bash
46
+ # Create virtual environment
47
+ python -m venv .venv
48
+ source .venv/bin/activate
49
+
50
+ # Install dependencies (including dev)
51
+ pip install -e ".[dev]"
52
+
53
+ # Run tests
54
+ pytest tests/ -v
55
+
56
+ # Run server
57
+ python -m kg_mcp.main
58
+ ```
59
+
60
+ ## Project Structure
61
+
62
+ ```
63
+ src/kg_mcp/
64
+ ├── main.py # Entry point
65
+ ├── config.py # Settings management
66
+ ├── llm/ # LLM integration
67
+ │ ├── client.py # LiteLLM wrapper
68
+ │ ├── schemas.py # Pydantic models
69
+ │ └── prompts/ # Prompt templates
70
+ ├── kg/ # Knowledge graph
71
+ │ ├── neo4j.py # Driver/client
72
+ │ ├── schema.cypher # DB schema
73
+ │ ├── repo.py # Query repository
74
+ │ ├── ingest.py # Ingestion pipeline
75
+ │ └── retrieval.py # Context builder
76
+ ├── mcp/ # MCP components
77
+ │ ├── tools.py # Tool definitions
78
+ │ ├── resources.py # Resource handlers
79
+ │ └── prompts.py # Prompt templates
80
+ ├── codegraph/ # Code indexing (V1)
81
+ │ ├── model.py # Data models
82
+ │ └── indexer.py # File indexer
83
+ └── security/ # Auth/Origin
84
+ ├── auth.py # Token validation
85
+ └── origin.py # Origin checking
86
+ ```
@@ -0,0 +1,36 @@
1
+ kg_mcp/__init__.py,sha256=f35x5yllOVTBCMOsJcgesmoze7bM5UTiOlTSMZU61-U,80
2
+ kg_mcp/__main__.py,sha256=3djAWBOOTYfXvFPnAfwe2UWkKkYHBUVHlPJjaMXNWrY,135
3
+ kg_mcp/config.py,sha256=9ryFQLG28B_v-PGv-D_2ojm5oU2EhNFxbq4qAv_hOyM,3479
4
+ kg_mcp/main.py,sha256=oprRN_yyZ5QIHOivYr923xl-_BWzizKvXEAOgtin-hg,5614
5
+ kg_mcp/utils.py,sha256=_4DlV2PtftJjR5Ak4lFzxdNuIrbyczIVenWhqKkX5zk,2810
6
+ kg_mcp/cli/__init__.py,sha256=-i85AHO1gqjeqEYqIkSjfW9Ik0LWbhrTPuhUCIk7eD4,60
7
+ kg_mcp/cli/setup.py,sha256=1d4jmR6Tuaf9kGNQWOcmrCoIMqeH3rMYZGDdBLdH39s,44376
8
+ kg_mcp/cli/status.py,sha256=0-_CiISA5maBgliQXl6S7B5WfpEc8nUuQI6S1QE_mLk,12651
9
+ kg_mcp/codegraph/__init__.py,sha256=Erh3mMg5FlbN0kzMJAzCpizcrjrNX0LuZHawKOECskE,68
10
+ kg_mcp/codegraph/indexer.py,sha256=H-QTMmrLsgzL4jXzhgLUagFGUyDfkm9cX-B-9ip_7TA,9130
11
+ kg_mcp/codegraph/model.py,sha256=qupXdrkHC4FQZGYE0Haw93AWdDWPsyuTND2jlMOFjWM,3853
12
+ kg_mcp/kg/__init__.py,sha256=2lueX_bq3H6sjh5DgRzSVWOAuzftzq7vz8NlCLsY1BE,56
13
+ kg_mcp/kg/apply_schema.py,sha256=iJNiLmSqmhOXVeS5bgNO_tSFIZi38fZfhSchmDtc_Gs,2715
14
+ kg_mcp/kg/ingest.py,sha256=KdLUlweATYiwBVOuyxMjGmLh04fXUky9QSO1fkgtTr8,9239
15
+ kg_mcp/kg/neo4j.py,sha256=ZvD45aChmuFH3Ns7i8OmTz0mrnmbscvIaZlTRa_-7hw,4761
16
+ kg_mcp/kg/repo.py,sha256=ASuKwsBLTel3toshScsNFC0F-0_8wvkV2PJ9p7s_dac,27164
17
+ kg_mcp/kg/retrieval.py,sha256=oFxVvGFCCC2jto6EBDMcXenkJq_LIln_5P9q3Lk6X-Y,9032
18
+ kg_mcp/kg/schema.cypher,sha256=r6BMg-fKhJxHREqaeHnxp2X3MknbTY8vJ96e2UUVPn4,5887
19
+ kg_mcp/llm/__init__.py,sha256=ewfvU-0Mxw8P2s774DsI8t9PkFNh78WeO58FqwsTOwY,88
20
+ kg_mcp/llm/client.py,sha256=OEwig8uE_MZeUvsZPUKZUEvZFVetA5mPIffbx42PUQM,10535
21
+ kg_mcp/llm/schemas.py,sha256=F4LQM7a5PMBoL3LQEP80LlPgcB7bfHoDV3nK2mbBJbw,8554
22
+ kg_mcp/llm/prompts/__init__.py,sha256=rMw7FCXH-pK_X759NlDpghKLvIt5hGk6p_1bOIw8klg,221
23
+ kg_mcp/llm/prompts/extractor.py,sha256=3gBq0f3YLKtEHwjOLjMxQYDu_yUi1SEoleiYiZ_79Uc,3692
24
+ kg_mcp/llm/prompts/linker.py,sha256=Cv0sR2hFbOf58_C8bEbVOmZPDV8vOVUBwa01dU5mpiU,3931
25
+ kg_mcp/mcp/__init__.py,sha256=HIdA5ozqK0A6JDcQpItt4M73_nk52PkdqYAorlgaiUE,57
26
+ kg_mcp/mcp/change_schemas.py,sha256=mWso9c26556O-8VaFfHLc1lvibouyT6IxRSVLTuzUE8,4891
27
+ kg_mcp/mcp/prompts.py,sha256=qbRwyM834gKFxwJlcsXH9eSdCrGXVmlyPDaJPb6VR6c,5546
28
+ kg_mcp/mcp/resources.py,sha256=H7hVC8bMqYUKB7e8T-kaSVoVI4JlA5nKhmlQuxC3j2w,8502
29
+ kg_mcp/mcp/tools.py,sha256=8CpBsO-_y7rW6wRb00NQJ73ENqgj7rUwzi76QGmKqZA,20172
30
+ kg_mcp/security/__init__.py,sha256=zvxT3XvZQLqMaj1IiKOYXlQDmQrolXpEErn8-LSe-a8,65
31
+ kg_mcp/security/auth.py,sha256=4GLUguNnlvwA8G1cAjbQlMvndyJYxecEv1REQFnhk4s,3218
32
+ kg_mcp/security/origin.py,sha256=fRm-w_URkT7sF0D8aRnt3xXXXkfYREBAydHKdrTrbtg,3068
33
+ kg_mcp-0.1.8.dist-info/METADATA,sha256=Wkbq5TbXdVmHxZn81I0FfK3ddpxminN0RMMxZAY3E7o,3026
34
+ kg_mcp-0.1.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
35
+ kg_mcp-0.1.8.dist-info/entry_points.txt,sha256=rWKJ-LdGRIRnTAALNI0ZJ7H1Nclnn8C76OLwt3QjUiE,120
36
+ kg_mcp-0.1.8.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ kg-mcp = kg_mcp.main:main
3
+ kg-mcp-setup = kg_mcp.cli.setup:main
4
+ kg-mcp-status = kg_mcp.cli.status:main