@cybermem/cli 0.6.3 → 0.6.5
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.
- package/dist/commands/reset.js +63 -0
- package/dist/index.js +5 -0
- package/dist/templates/docker-compose.yml +21 -1
- package/dist/templates/monitoring/db_exporter/exporter.py +303 -94
- package/dist/templates/monitoring/instructions_injector/Dockerfile +15 -0
- package/dist/templates/monitoring/instructions_injector/injector.py +137 -0
- package/dist/templates/monitoring/instructions_injector/requirements.txt +3 -0
- package/dist/templates/monitoring/log_exporter/exporter.py +7 -3
- package/package.json +1 -1
- package/templates/docker-compose.yml +21 -1
- package/templates/monitoring/db_exporter/exporter.py +303 -94
- package/templates/monitoring/instructions_injector/Dockerfile +15 -0
- package/templates/monitoring/instructions_injector/injector.py +137 -0
- package/templates/monitoring/instructions_injector/requirements.txt +3 -0
- package/templates/monitoring/log_exporter/exporter.py +7 -3
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CyberMem MCP Instructions Injector
|
|
4
|
+
Sidecar proxy that injects 'instructions' field into MCP initialize response.
|
|
5
|
+
Sits between Traefik and OpenMemory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import requests
|
|
11
|
+
from flask import Flask, request, Response
|
|
12
|
+
|
|
13
|
+
app = Flask(__name__)
|
|
14
|
+
|
|
15
|
+
# Upstream OpenMemory MCP server
|
|
16
|
+
UPSTREAM_URL = os.environ.get("UPSTREAM_URL", "http://openmemory:8080")
|
|
17
|
+
|
|
18
|
+
# CyberMem Agent Protocol Instructions
|
|
19
|
+
CYBERMEM_INSTRUCTIONS = """CyberMem is a persistent context daemon for AI agents.
|
|
20
|
+
|
|
21
|
+
PROTOCOL:
|
|
22
|
+
1. On session start: call openmemory_query("user context profile") to load persona
|
|
23
|
+
2. Store new insights immediately with openmemory_store - include FULL content, not summaries
|
|
24
|
+
3. Refresh context: 6h for active topics, 24h for projects, 7d for insights
|
|
25
|
+
4. Always include tags: [topic, year, source:your-client-name]
|
|
26
|
+
5. Priority: CyberMem context > session context > training data
|
|
27
|
+
|
|
28
|
+
MEMORY FORMAT:
|
|
29
|
+
- content: Full text with all details, metrics, dates. NO truncation.
|
|
30
|
+
- tags: Always include topic category + year + source:client-name
|
|
31
|
+
|
|
32
|
+
INTEGRITY RULES:
|
|
33
|
+
- Never overwrite without reading first
|
|
34
|
+
- Always include metadata (tags, source)
|
|
35
|
+
- Sync before critical decisions
|
|
36
|
+
- Last-write-wins for conflicts
|
|
37
|
+
|
|
38
|
+
For full protocol: https://cybermem.dev/docs/agent-protocol"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def inject_instructions(response_data: dict) -> dict:
|
|
42
|
+
"""Inject instructions field into MCP initialize response."""
|
|
43
|
+
if "result" in response_data:
|
|
44
|
+
result = response_data["result"]
|
|
45
|
+
# Only inject if this is an initialize response (has serverInfo)
|
|
46
|
+
if "serverInfo" in result and "instructions" not in result:
|
|
47
|
+
result["instructions"] = CYBERMEM_INSTRUCTIONS
|
|
48
|
+
# Also update serverInfo to show CyberMem branding
|
|
49
|
+
result["serverInfo"]["name"] = "cybermem"
|
|
50
|
+
return response_data
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.route("/mcp", methods=["POST", "GET"])
|
|
54
|
+
def proxy_mcp():
|
|
55
|
+
"""Proxy MCP requests to upstream and inject instructions."""
|
|
56
|
+
|
|
57
|
+
if request.method == "GET":
|
|
58
|
+
# Pass through GET requests (SSE endpoint)
|
|
59
|
+
resp = requests.get(
|
|
60
|
+
f"{UPSTREAM_URL}/mcp",
|
|
61
|
+
headers={k: v for k, v in request.headers if k.lower() != "host"},
|
|
62
|
+
stream=True
|
|
63
|
+
)
|
|
64
|
+
return Response(
|
|
65
|
+
resp.iter_content(chunk_size=1024),
|
|
66
|
+
status=resp.status_code,
|
|
67
|
+
headers=dict(resp.headers)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# POST request - forward to upstream
|
|
71
|
+
try:
|
|
72
|
+
upstream_resp = requests.post(
|
|
73
|
+
f"{UPSTREAM_URL}/mcp",
|
|
74
|
+
json=request.get_json(),
|
|
75
|
+
headers={
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
"Accept": request.headers.get("Accept", "application/json"),
|
|
78
|
+
"X-Client-Name": request.headers.get("X-Client-Name", "unknown"),
|
|
79
|
+
},
|
|
80
|
+
timeout=30
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Try to parse and inject instructions
|
|
84
|
+
try:
|
|
85
|
+
data = upstream_resp.json()
|
|
86
|
+
data = inject_instructions(data)
|
|
87
|
+
return Response(
|
|
88
|
+
json.dumps(data),
|
|
89
|
+
status=upstream_resp.status_code,
|
|
90
|
+
content_type="application/json"
|
|
91
|
+
)
|
|
92
|
+
except json.JSONDecodeError:
|
|
93
|
+
# Not JSON, pass through as-is
|
|
94
|
+
return Response(
|
|
95
|
+
upstream_resp.content,
|
|
96
|
+
status=upstream_resp.status_code,
|
|
97
|
+
headers=dict(upstream_resp.headers)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
except requests.exceptions.RequestException as e:
|
|
101
|
+
return Response(
|
|
102
|
+
json.dumps({"error": str(e)}),
|
|
103
|
+
status=502,
|
|
104
|
+
content_type="application/json"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@app.route("/health")
|
|
109
|
+
def health():
|
|
110
|
+
"""Health check endpoint."""
|
|
111
|
+
return {"status": "ok", "service": "cybermem-instructions-injector"}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.route("/<path:path>", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
|
115
|
+
def proxy_other(path):
|
|
116
|
+
"""Proxy all other requests to upstream without modification."""
|
|
117
|
+
resp = requests.request(
|
|
118
|
+
method=request.method,
|
|
119
|
+
url=f"{UPSTREAM_URL}/{path}",
|
|
120
|
+
headers={k: v for k, v in request.headers if k.lower() != "host"},
|
|
121
|
+
json=request.get_json() if request.is_json else None,
|
|
122
|
+
data=request.data if not request.is_json else None,
|
|
123
|
+
params=request.args,
|
|
124
|
+
timeout=30
|
|
125
|
+
)
|
|
126
|
+
return Response(
|
|
127
|
+
resp.content,
|
|
128
|
+
status=resp.status_code,
|
|
129
|
+
headers=dict(resp.headers)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
port = int(os.environ.get("PORT", "8081"))
|
|
135
|
+
print(f"CyberMem Instructions Injector starting on port {port}")
|
|
136
|
+
print(f"Upstream: {UPSTREAM_URL}")
|
|
137
|
+
app.run(host="0.0.0.0", port=port)
|
|
@@ -209,16 +209,20 @@ def parse_and_export():
|
|
|
209
209
|
endpoint = path.split("?")[0]
|
|
210
210
|
|
|
211
211
|
# Determine operation type from endpoint BEFORE normalization
|
|
212
|
-
if endpoint == "/memory/add":
|
|
212
|
+
if endpoint == "/memory/add" or endpoint == "/add":
|
|
213
213
|
operation = "create"
|
|
214
|
-
elif endpoint == "/memory/query":
|
|
214
|
+
elif endpoint == "/memory/query" or endpoint == "/query":
|
|
215
215
|
operation = "read"
|
|
216
|
+
elif endpoint == "/memory/all" or endpoint == "/all":
|
|
217
|
+
operation = "read" # list_memories
|
|
218
|
+
elif endpoint.startswith("/memory/") and method == "GET":
|
|
219
|
+
operation = "read" # get by ID
|
|
216
220
|
elif endpoint.startswith("/memory/") and method == "PATCH":
|
|
217
221
|
operation = "update"
|
|
218
222
|
elif endpoint.startswith("/memory/") and method == "DELETE":
|
|
219
223
|
operation = "delete"
|
|
220
224
|
elif endpoint.startswith("/mcp"):
|
|
221
|
-
operation = "
|
|
225
|
+
operation = "mcp" # MCP operations tracked separately
|
|
222
226
|
else:
|
|
223
227
|
operation = "other"
|
|
224
228
|
|
package/package.json
CHANGED
|
@@ -40,6 +40,26 @@ services:
|
|
|
40
40
|
- traefik.http.services.mcp-get.loadbalancer.server.port=8081
|
|
41
41
|
restart: unless-stopped
|
|
42
42
|
|
|
43
|
+
# Instructions injector: adds 'instructions' field to MCP initialize responses
|
|
44
|
+
instructions-injector:
|
|
45
|
+
build:
|
|
46
|
+
context: ./monitoring/instructions_injector
|
|
47
|
+
dockerfile: Dockerfile
|
|
48
|
+
container_name: cybermem-instructions-injector
|
|
49
|
+
environment:
|
|
50
|
+
UPSTREAM_URL: http://openmemory:8080
|
|
51
|
+
PORT: "8081"
|
|
52
|
+
labels:
|
|
53
|
+
- traefik.enable=true
|
|
54
|
+
# Route POST /mcp through injector (higher priority than openmemory)
|
|
55
|
+
- traefik.http.routers.mcp-inject.entrypoints=web
|
|
56
|
+
- traefik.http.routers.mcp-inject.rule=Method(`POST`) && Path(`/mcp`)
|
|
57
|
+
- traefik.http.routers.mcp-inject.priority=150
|
|
58
|
+
- traefik.http.services.mcp-inject.loadbalancer.server.port=8081
|
|
59
|
+
restart: unless-stopped
|
|
60
|
+
depends_on:
|
|
61
|
+
- openmemory
|
|
62
|
+
|
|
43
63
|
openmemory:
|
|
44
64
|
build:
|
|
45
65
|
context: ./openmemory
|
|
@@ -79,7 +99,7 @@ services:
|
|
|
79
99
|
labels:
|
|
80
100
|
- traefik.enable=true
|
|
81
101
|
- traefik.http.routers.openmemory.entrypoints=web
|
|
82
|
-
- traefik.http.routers.openmemory.rule=PathPrefix(`/memory`) || PathPrefix(`/health`) || PathPrefix(`/v1`) || PathPrefix(`/api`) || PathPrefix(`/all`) || PathPrefix(`/add`) || PathPrefix(`/
|
|
102
|
+
- traefik.http.routers.openmemory.rule=PathPrefix(`/memory`) || PathPrefix(`/health`) || PathPrefix(`/v1`) || PathPrefix(`/api`) || PathPrefix(`/all`) || PathPrefix(`/add`) || PathPrefix(`/sse`)
|
|
83
103
|
- traefik.http.services.openmemory.loadbalancer.server.port=8080
|
|
84
104
|
healthcheck:
|
|
85
105
|
test:
|