vector-knowledge-graph-mcp 1.0.2__tar.gz → 1.0.4__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.
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/.github/FUNDING.yml +1 -1
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/PKG-INFO +1 -1
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/README.md +16 -0
- vector_knowledge_graph_mcp-1.0.4/auth_middleware.py +223 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/pyproject.toml +2 -2
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/server.py +8 -6
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/.cursorrules +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/.github/workflows/mcp-smithery-publish.yml +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/.github/workflows/test.yml +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/.gitignore +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/.mcp.json +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/.well-known/mcp/server-card.json +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/CODE_OF_CONDUCT.md +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/CONTRIBUTING.md +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/Dockerfile +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/Dockerfile.glama +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/LICENSE +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/SECURITY.md +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/glama.json +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/llms.txt +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/mcp-wrapper.py +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/package.json +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/pytest.ini +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/smithery.yaml +0 -0
- {vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vector-knowledge-graph-mcp
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: AI-powered vector knowledge graph MCP server for agents. Supports add node, add edge, semantic node search. By MEOK AI Labs.
|
|
5
5
|
Project-URL: Homepage, https://meok.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/CSOAI-ORG/vector-knowledge-graph-mcp
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
mcp-name: io.github.CSOAI-ORG/vector-knowledge-graph-mcp
|
|
2
|
+
|
|
1
3
|
# Vector Knowledge Graph
|
|
2
4
|
|
|
3
5
|
[](https://pypi.org/project/vector-knowledge-graph-mcp/) [](https://pypi.org/project/vector-knowledge-graph-mcp/)
|
|
@@ -94,3 +96,17 @@ Free tier: 10 calls/day per MCP. Pro tier (£79/mo): unlimited + cryptographical
|
|
|
94
96
|
→ Full catalogue: [councilof.ai/catalogue](https://councilof.ai/catalogue)
|
|
95
97
|
→ MEOK AI Labs: [meok.ai](https://meok.ai)
|
|
96
98
|
|
|
99
|
+
<!-- BUY-LADDER:START -->
|
|
100
|
+
|
|
101
|
+
## 💸 Try MEOK in 30 seconds — instant buy ladder
|
|
102
|
+
|
|
103
|
+
| Tier | Price | What you get | Stripe |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| Smoke test | **£1** | Signed sample MCP-Hardening report + Article 50 PDF | <https://buy.stripe.com/dRmcN75ScdQS7oh1Uc8k90U> |
|
|
106
|
+
| Quick Kit | **£9** | EU AI Act Article 50 implementation guide (C2PA + EU-Icon) | <https://buy.stripe.com/cNi00la8s1460ZT0Q88k90V> |
|
|
107
|
+
| Founder Call | **£29** | 30-min 1-on-1 with the founder | <https://buy.stripe.com/8x228ta8s6oqbExaqI8k90W> |
|
|
108
|
+
|
|
109
|
+
> Refundable. UK Stripe — VAT-clean. Builds on the 81-MCP MEOK fleet.
|
|
110
|
+
> Verify any signed report at <https://meok.ai/verify>.
|
|
111
|
+
|
|
112
|
+
<!-- BUY-LADDER:END -->
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MEOK Labs — Shared Authentication Middleware for MCP Servers
|
|
3
|
+
Deploy to: ~/clawd/meok-labs-engine/shared/auth_middleware.py
|
|
4
|
+
Every compliance MCP server imports this.
|
|
5
|
+
|
|
6
|
+
Usage in any server.py:
|
|
7
|
+
import sys, os
|
|
8
|
+
sys.path.insert(0, os.path.expanduser("~/clawd/meok-labs-engine/shared"))
|
|
9
|
+
from auth_middleware import check_access, require_tier, audit_log, Tier
|
|
10
|
+
|
|
11
|
+
@mcp.tool(name="my_tool")
|
|
12
|
+
async def my_tool(query: str, api_key: str = "") -> str:
|
|
13
|
+
allowed, msg, tier = check_access(api_key)
|
|
14
|
+
if not allowed:
|
|
15
|
+
return json.dumps({"error": msg, "upgrade_url": "https://buy.stripe.com/00wfZjcgAeUW4c5cyQ8k90K"})
|
|
16
|
+
# ... tool logic ...
|
|
17
|
+
audit_log(api_key, "my_tool", "eu_ai_act", "result_summary", tier)
|
|
18
|
+
return json.dumps(result)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import hashlib
|
|
23
|
+
import time
|
|
24
|
+
import json
|
|
25
|
+
from typing import Optional, Tuple
|
|
26
|
+
from enum import Enum
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Tier(str, Enum):
|
|
30
|
+
FREE = "free"
|
|
31
|
+
STARTER = "starter"
|
|
32
|
+
PROFESSIONAL = "professional"
|
|
33
|
+
ENTERPRISE = "enterprise"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
TIER_LIMITS = {
|
|
37
|
+
Tier.FREE: {"calls_per_day": 10, "frameworks": 1, "audit_trail": False},
|
|
38
|
+
Tier.STARTER: {"calls_per_day": 100, "frameworks": 1, "audit_trail": False},
|
|
39
|
+
Tier.PROFESSIONAL: {"calls_per_day": 1000, "frameworks": 5, "audit_trail": True},
|
|
40
|
+
Tier.ENTERPRISE: {"calls_per_day": -1, "frameworks": -1, "audit_trail": True},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
TIER_ORDER = [Tier.FREE, Tier.STARTER, Tier.PROFESSIONAL, Tier.ENTERPRISE]
|
|
44
|
+
|
|
45
|
+
MEOK_DIR = os.path.expanduser("~/.meok")
|
|
46
|
+
USAGE_FILE = os.path.join(MEOK_DIR, "usage.json")
|
|
47
|
+
KEYS_FILE = os.path.join(MEOK_DIR, "api_keys.json")
|
|
48
|
+
AUDIT_FILE = os.path.join(MEOK_DIR, "audit_trail.jsonl")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _ensure_dir():
|
|
52
|
+
os.makedirs(MEOK_DIR, exist_ok=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _load_json(path: str) -> dict:
|
|
56
|
+
_ensure_dir()
|
|
57
|
+
if os.path.exists(path):
|
|
58
|
+
try:
|
|
59
|
+
with open(path) as f:
|
|
60
|
+
return json.load(f)
|
|
61
|
+
except (json.JSONDecodeError, IOError):
|
|
62
|
+
return {}
|
|
63
|
+
return {}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _save_json(path: str, data: dict):
|
|
67
|
+
_ensure_dir()
|
|
68
|
+
with open(path, "w") as f:
|
|
69
|
+
json.dump(data, f, indent=2)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def generate_api_key(tier: Tier, customer_name: str) -> str:
|
|
73
|
+
"""Generate a new API key for a customer. Run manually to onboard customers."""
|
|
74
|
+
raw = f"meok_{tier.value}_{customer_name}_{time.time()}"
|
|
75
|
+
key = f"meok_{hashlib.sha256(raw.encode()).hexdigest()[:32]}"
|
|
76
|
+
|
|
77
|
+
keys = _load_json(KEYS_FILE)
|
|
78
|
+
keys[key] = {
|
|
79
|
+
"tier": tier.value,
|
|
80
|
+
"customer": customer_name,
|
|
81
|
+
"created": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
82
|
+
"active": True,
|
|
83
|
+
}
|
|
84
|
+
_save_json(KEYS_FILE, keys)
|
|
85
|
+
return key
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_tier_from_api_key(api_key: str) -> Tier:
|
|
89
|
+
"""Look up tier for an API key."""
|
|
90
|
+
if not api_key:
|
|
91
|
+
return Tier.FREE
|
|
92
|
+
|
|
93
|
+
keys = _load_json(KEYS_FILE)
|
|
94
|
+
if api_key in keys and keys[api_key].get("active", True):
|
|
95
|
+
try:
|
|
96
|
+
return Tier(keys[api_key]["tier"])
|
|
97
|
+
except ValueError:
|
|
98
|
+
return Tier.FREE
|
|
99
|
+
|
|
100
|
+
return Tier.FREE
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def check_access(api_key: str = "", framework: str = None) -> Tuple[bool, str, Tier]:
|
|
104
|
+
"""
|
|
105
|
+
Main access control function. Returns (allowed, message, tier).
|
|
106
|
+
Call at the start of every tool.
|
|
107
|
+
"""
|
|
108
|
+
tier = get_tier_from_api_key(api_key)
|
|
109
|
+
limits = TIER_LIMITS[tier]
|
|
110
|
+
|
|
111
|
+
# Rate limit check
|
|
112
|
+
usage = _load_json(USAGE_FILE)
|
|
113
|
+
today = time.strftime("%Y-%m-%d")
|
|
114
|
+
key_hash = hashlib.sha256((api_key or "anon").encode()).hexdigest()[:12]
|
|
115
|
+
day_key = f"{key_hash}:{today}"
|
|
116
|
+
|
|
117
|
+
current = usage.get(day_key, 0)
|
|
118
|
+
max_calls = limits["calls_per_day"]
|
|
119
|
+
|
|
120
|
+
if max_calls != -1 and current >= max_calls:
|
|
121
|
+
return (
|
|
122
|
+
False,
|
|
123
|
+
f"Rate limit reached ({max_calls}/day on {tier.value} tier). "
|
|
124
|
+
f"Upgrade at https://buy.stripe.com/00wfZjcgAeUW4c5cyQ8k90K",
|
|
125
|
+
tier,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Record usage
|
|
129
|
+
usage[day_key] = current + 1
|
|
130
|
+
# Clean old entries (keep last 7 days)
|
|
131
|
+
cutoff = time.strftime("%Y-%m-%d", time.localtime(time.time() - 7 * 86400))
|
|
132
|
+
usage = {k: v for k, v in usage.items() if k.split(":")[1] >= cutoff}
|
|
133
|
+
_save_json(USAGE_FILE, usage)
|
|
134
|
+
|
|
135
|
+
return True, "OK", tier
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def require_tier(minimum: Tier, current: Tier) -> Tuple[bool, str]:
|
|
139
|
+
"""Check if current tier meets the minimum requirement for a tool."""
|
|
140
|
+
if TIER_ORDER.index(current) < TIER_ORDER.index(minimum):
|
|
141
|
+
return (
|
|
142
|
+
False,
|
|
143
|
+
f"Requires {minimum.value} tier. Current: {current.value}. "
|
|
144
|
+
f"Upgrade at https://buy.stripe.com/00wfZjcgAeUW4c5cyQ8k90K",
|
|
145
|
+
)
|
|
146
|
+
return True, "OK"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def audit_log(
|
|
150
|
+
api_key: str,
|
|
151
|
+
tool_name: str,
|
|
152
|
+
framework: str,
|
|
153
|
+
result_summary: str,
|
|
154
|
+
tier: Tier,
|
|
155
|
+
):
|
|
156
|
+
"""Append to audit trail. Only Professional and Enterprise tiers generate audit logs."""
|
|
157
|
+
if not TIER_LIMITS[tier]["audit_trail"]:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
_ensure_dir()
|
|
161
|
+
entry = {
|
|
162
|
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
163
|
+
"tool": tool_name,
|
|
164
|
+
"framework": framework,
|
|
165
|
+
"result": result_summary[:200],
|
|
166
|
+
"tier": tier.value,
|
|
167
|
+
"key_prefix": (api_key or "")[:8] + "...",
|
|
168
|
+
}
|
|
169
|
+
with open(AUDIT_FILE, "a") as f:
|
|
170
|
+
f.write(json.dumps(entry) + "\n")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def get_usage_stats(api_key: str = "") -> dict:
|
|
174
|
+
"""Get usage statistics for an API key."""
|
|
175
|
+
usage = _load_json(USAGE_FILE)
|
|
176
|
+
tier = get_tier_from_api_key(api_key)
|
|
177
|
+
limits = TIER_LIMITS[tier]
|
|
178
|
+
|
|
179
|
+
key_hash = hashlib.sha256((api_key or "anon").encode()).hexdigest()[:12]
|
|
180
|
+
today = time.strftime("%Y-%m-%d")
|
|
181
|
+
day_key = f"{key_hash}:{today}"
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
"tier": tier.value,
|
|
185
|
+
"calls_today": usage.get(day_key, 0),
|
|
186
|
+
"limit": limits["calls_per_day"],
|
|
187
|
+
"remaining": max(0, limits["calls_per_day"] - usage.get(day_key, 0))
|
|
188
|
+
if limits["calls_per_day"] != -1 else "unlimited",
|
|
189
|
+
"audit_trail": limits["audit_trail"],
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# CLI for key management
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
import sys
|
|
196
|
+
if len(sys.argv) < 2:
|
|
197
|
+
print("Usage:")
|
|
198
|
+
print(" python auth_middleware.py generate <tier> <customer_name>")
|
|
199
|
+
print(" python auth_middleware.py list")
|
|
200
|
+
print(" python auth_middleware.py stats <api_key>")
|
|
201
|
+
print(f"\nTiers: {', '.join(t.value for t in Tier)}")
|
|
202
|
+
sys.exit(0)
|
|
203
|
+
|
|
204
|
+
cmd = sys.argv[1]
|
|
205
|
+
|
|
206
|
+
if cmd == "generate":
|
|
207
|
+
tier = Tier(sys.argv[2])
|
|
208
|
+
name = sys.argv[3]
|
|
209
|
+
key = generate_api_key(tier, name)
|
|
210
|
+
print(f"Generated key: {key}")
|
|
211
|
+
print(f"Tier: {tier.value}")
|
|
212
|
+
print(f"Customer: {name}")
|
|
213
|
+
|
|
214
|
+
elif cmd == "list":
|
|
215
|
+
keys = _load_json(KEYS_FILE)
|
|
216
|
+
for k, v in keys.items():
|
|
217
|
+
status = "active" if v.get("active", True) else "disabled"
|
|
218
|
+
print(f" {k[:20]}... | {v['tier']:15} | {v['customer']:20} | {status}")
|
|
219
|
+
|
|
220
|
+
elif cmd == "stats":
|
|
221
|
+
key = sys.argv[2]
|
|
222
|
+
stats = get_usage_stats(key)
|
|
223
|
+
print(json.dumps(stats, indent=2))
|
|
@@ -3,7 +3,7 @@ requires = ["hatchling"]
|
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
[project]
|
|
5
5
|
name = "vector-knowledge-graph-mcp"
|
|
6
|
-
version = "1.0.
|
|
6
|
+
version = "1.0.4"
|
|
7
7
|
description = "AI-powered vector knowledge graph MCP server for agents. Supports add node, add edge, semantic node search. By MEOK AI Labs."
|
|
8
8
|
license = {file = "LICENSE"}
|
|
9
9
|
requires-python = ">=3.10"
|
|
@@ -21,7 +21,7 @@ Homepage = "https://meok.ai"
|
|
|
21
21
|
Repository = "https://github.com/CSOAI-ORG/vector-knowledge-graph-mcp"
|
|
22
22
|
[tool.hatch.build.targets.wheel]
|
|
23
23
|
packages = ["."]
|
|
24
|
-
only-include = ["server.py"]
|
|
24
|
+
only-include = ["server.py", "auth_middleware.py"]
|
|
25
25
|
|
|
26
26
|
[project.scripts]
|
|
27
27
|
vector_knowledge_graph_mcp = "server:main"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
|
+
Buy Pro: https://www.csoai.org/checkout
|
|
4
|
+
Vector Knowledge Graph MCP Server — Neo4j-style graph + vector hybrid for compliance reasoning."""
|
|
3
5
|
|
|
4
6
|
import sys, os
|
|
5
7
|
from auth_middleware import check_access
|
|
@@ -36,7 +38,7 @@ def add_node(label: str, properties: dict, node_id: Optional[str] = None, api_ke
|
|
|
36
38
|
"""Add a node to the knowledge graph with properties, embeddings, and metadata."""
|
|
37
39
|
allowed, msg, tier = check_access(api_key)
|
|
38
40
|
if not allowed:
|
|
39
|
-
return {"error": msg, "upgrade_url": "https://
|
|
41
|
+
return {"error": msg, "upgrade_url": "https://councilof.ai"}
|
|
40
42
|
if err := _rl(): return err
|
|
41
43
|
|
|
42
44
|
nid = node_id or hashlib.md5(label.encode()).hexdigest()[:12]
|
|
@@ -48,7 +50,7 @@ def add_edge(from_id: str, to_id: str, relation: str, weight: float = 1.0, api_k
|
|
|
48
50
|
"""Create a directed edge between two nodes with relationship type and weight."""
|
|
49
51
|
allowed, msg, tier = check_access(api_key)
|
|
50
52
|
if not allowed:
|
|
51
|
-
return {"error": msg, "upgrade_url": "https://
|
|
53
|
+
return {"error": msg, "upgrade_url": "https://councilof.ai"}
|
|
52
54
|
if err := _rl(): return err
|
|
53
55
|
|
|
54
56
|
_EDGES.append({"from": from_id, "to": to_id, "relation": relation, "weight": weight})
|
|
@@ -59,7 +61,7 @@ def semantic_node_search(query: str, top_k: int = 5, api_key: str = "") -> str:
|
|
|
59
61
|
"""Search for nodes using semantic similarity matching against stored embeddings."""
|
|
60
62
|
allowed, msg, tier = check_access(api_key)
|
|
61
63
|
if not allowed:
|
|
62
|
-
return {"error": msg, "upgrade_url": "https://
|
|
64
|
+
return {"error": msg, "upgrade_url": "https://councilof.ai"}
|
|
63
65
|
if err := _rl(): return err
|
|
64
66
|
|
|
65
67
|
q_vec = _embed(query)
|
|
@@ -75,7 +77,7 @@ def trace_compliance_chain(start_node_id: str, max_depth: int = 3, api_key: str
|
|
|
75
77
|
"""Trace the compliance chain from a requirement through controls to evidence."""
|
|
76
78
|
allowed, msg, tier = check_access(api_key)
|
|
77
79
|
if not allowed:
|
|
78
|
-
return {"error": msg, "upgrade_url": "https://
|
|
80
|
+
return {"error": msg, "upgrade_url": "https://councilof.ai"}
|
|
79
81
|
if err := _rl(): return err
|
|
80
82
|
|
|
81
83
|
visited = set()
|
|
@@ -97,7 +99,7 @@ def find_gaps(required_frameworks: list, api_key: str = "") -> str:
|
|
|
97
99
|
"""Find gaps in the knowledge graph where expected relationships or nodes are missing."""
|
|
98
100
|
allowed, msg, tier = check_access(api_key)
|
|
99
101
|
if not allowed:
|
|
100
|
-
return {"error": msg, "upgrade_url": "https://
|
|
102
|
+
return {"error": msg, "upgrade_url": "https://councilof.ai"}
|
|
101
103
|
if err := _rl(): return err
|
|
102
104
|
|
|
103
105
|
present = set()
|
|
File without changes
|
|
File without changes
|
{vector_knowledge_graph_mcp-1.0.2 → vector_knowledge_graph_mcp-1.0.4}/.github/workflows/test.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|