vector-knowledge-graph-mcp 1.0.3__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.
Files changed (25) hide show
  1. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/PKG-INFO +1 -1
  2. vector_knowledge_graph_mcp-1.0.4/auth_middleware.py +223 -0
  3. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/pyproject.toml +2 -2
  4. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/server.py +1 -1
  5. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/.cursorrules +0 -0
  6. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/.github/FUNDING.yml +0 -0
  7. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/.github/workflows/mcp-smithery-publish.yml +0 -0
  8. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/.github/workflows/test.yml +0 -0
  9. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/.gitignore +0 -0
  10. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/.mcp.json +0 -0
  11. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/.well-known/mcp/server-card.json +0 -0
  12. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/CODE_OF_CONDUCT.md +0 -0
  13. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/CONTRIBUTING.md +0 -0
  14. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/Dockerfile +0 -0
  15. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/Dockerfile.glama +0 -0
  16. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/LICENSE +0 -0
  17. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/README.md +0 -0
  18. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/SECURITY.md +0 -0
  19. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/glama.json +0 -0
  20. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/llms.txt +0 -0
  21. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/mcp-wrapper.py +0 -0
  22. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/package.json +0 -0
  23. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/pytest.ini +0 -0
  24. {vector_knowledge_graph_mcp-1.0.3 → vector_knowledge_graph_mcp-1.0.4}/smithery.yaml +0 -0
  25. {vector_knowledge_graph_mcp-1.0.3 → 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
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
@@ -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.3"
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,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Buy Pro: https://www.csoai.org/checkout
4
- """Vector Knowledge Graph MCP Server — Neo4j-style graph + vector hybrid for compliance reasoning."""
4
+ Vector Knowledge Graph MCP Server — Neo4j-style graph + vector hybrid for compliance reasoning."""
5
5
 
6
6
  import sys, os
7
7
  from auth_middleware import check_access