@compr/opscontext-mcp 2.0.0
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/CHANGELOG.md +313 -0
- package/LICENSE +83 -0
- package/README.md +470 -0
- package/defaults/learnings.json +146 -0
- package/dist/activation.d.ts +48 -0
- package/dist/activation.js +377 -0
- package/dist/adapters.d.ts +101 -0
- package/dist/adapters.js +171 -0
- package/dist/agents.d.ts +137 -0
- package/dist/agents.js +1638 -0
- package/dist/audit.d.ts +23 -0
- package/dist/audit.js +163 -0
- package/dist/cache.d.ts +15 -0
- package/dist/cache.js +117 -0
- package/dist/claude-integration.d.ts +95 -0
- package/dist/claude-integration.js +247 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +1823 -0
- package/dist/code-chunker.d.ts +12 -0
- package/dist/code-chunker.js +270 -0
- package/dist/collectors.d.ts +63 -0
- package/dist/collectors.js +617 -0
- package/dist/config.d.ts +73 -0
- package/dist/config.js +239 -0
- package/dist/embeddings.d.ts +36 -0
- package/dist/embeddings.js +124 -0
- package/dist/firewall.d.ts +133 -0
- package/dist/firewall.js +631 -0
- package/dist/hooks.d.ts +76 -0
- package/dist/hooks.js +313 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1081 -0
- package/dist/ingest.d.ts +32 -0
- package/dist/ingest.js +162 -0
- package/dist/learnings.d.ts +108 -0
- package/dist/learnings.js +714 -0
- package/dist/license-sig.d.ts +47 -0
- package/dist/license-sig.js +104 -0
- package/dist/policy.d.ts +131 -0
- package/dist/policy.js +182 -0
- package/dist/search.d.ts +11 -0
- package/dist/search.js +99 -0
- package/dist/sessions.d.ts +46 -0
- package/dist/sessions.js +153 -0
- package/examples/adapters/notion-adapter.js +108 -0
- package/examples/adapters/rss-adapter.js +76 -0
- package/package.json +87 -0
- package/skills/opscontext/SKILL.md +260 -0
package/dist/sessions.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// LOCKED — verified March 3 2026 — session persistence: save/load/list/delete + auto-session inject
|
|
2
|
+
// DO NOT RE-AUDIT — 16 session tests passing, stable since v1.16.0
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { safeAppend } from "./audit.js";
|
|
7
|
+
/**
|
|
8
|
+
* Session Persistence — save/restore conversation context between sessions.
|
|
9
|
+
*
|
|
10
|
+
* Sessions are saved as JSON files in ~/.contextengine/sessions/.
|
|
11
|
+
* Each session has a name, timestamp, and key-value data store.
|
|
12
|
+
*
|
|
13
|
+
* Use cases:
|
|
14
|
+
* - AI agent saves decisions/context at end of session → picks up next time
|
|
15
|
+
* - Track what was discussed, changed, or planned across sessions
|
|
16
|
+
* - Store project-specific notes that persist between agent restarts
|
|
17
|
+
*/
|
|
18
|
+
const SESSIONS_DIR = join(homedir(), ".contextengine", "sessions");
|
|
19
|
+
function ensureDir() {
|
|
20
|
+
if (!existsSync(SESSIONS_DIR)) {
|
|
21
|
+
mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function sessionPath(name) {
|
|
25
|
+
// Sanitize name for filesystem
|
|
26
|
+
const safe = name.replace(/[^a-zA-Z0-9_\-\.]/g, "_").substring(0, 100);
|
|
27
|
+
return join(SESSIONS_DIR, `${safe}.json`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Save or update a key-value pair in a named session.
|
|
31
|
+
*/
|
|
32
|
+
export function saveSession(name, key, value) {
|
|
33
|
+
ensureDir();
|
|
34
|
+
const path = sessionPath(name);
|
|
35
|
+
const now = new Date().toISOString();
|
|
36
|
+
let session;
|
|
37
|
+
if (existsSync(path)) {
|
|
38
|
+
session = JSON.parse(readFileSync(path, "utf-8"));
|
|
39
|
+
session.updated = now;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
session = { name, created: now, updated: now, entries: [] };
|
|
43
|
+
}
|
|
44
|
+
// Update existing key or add new one
|
|
45
|
+
const existing = session.entries.find((e) => e.key === key);
|
|
46
|
+
if (existing) {
|
|
47
|
+
existing.value = value;
|
|
48
|
+
existing.timestamp = now;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
session.entries.push({ key, value, timestamp: now });
|
|
52
|
+
}
|
|
53
|
+
writeFileSync(path, JSON.stringify(session, null, 2));
|
|
54
|
+
safeAppend("session.save", {
|
|
55
|
+
name: session.name,
|
|
56
|
+
key,
|
|
57
|
+
value_length: value.length,
|
|
58
|
+
entries: session.entries.length,
|
|
59
|
+
});
|
|
60
|
+
return session;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Load a session by name.
|
|
64
|
+
*/
|
|
65
|
+
export function loadSession(name) {
|
|
66
|
+
const path = sessionPath(name);
|
|
67
|
+
if (!existsSync(path))
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* List all saved sessions.
|
|
78
|
+
*/
|
|
79
|
+
export function listSessions() {
|
|
80
|
+
ensureDir();
|
|
81
|
+
try {
|
|
82
|
+
return readdirSync(SESSIONS_DIR)
|
|
83
|
+
.filter((f) => f.endsWith(".json"))
|
|
84
|
+
.map((f) => {
|
|
85
|
+
try {
|
|
86
|
+
const session = JSON.parse(readFileSync(join(SESSIONS_DIR, f), "utf-8"));
|
|
87
|
+
return {
|
|
88
|
+
name: session.name,
|
|
89
|
+
entries: session.entries.length,
|
|
90
|
+
created: session.created,
|
|
91
|
+
updated: session.updated,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
.filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Delete a session.
|
|
106
|
+
*/
|
|
107
|
+
export function deleteSession(name) {
|
|
108
|
+
const path = sessionPath(name);
|
|
109
|
+
if (existsSync(path)) {
|
|
110
|
+
unlinkSync(path);
|
|
111
|
+
safeAppend("session.delete", { name });
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Format a session for display.
|
|
118
|
+
*/
|
|
119
|
+
export function formatSession(session) {
|
|
120
|
+
const lines = [];
|
|
121
|
+
lines.push(`# Session: ${session.name}`);
|
|
122
|
+
lines.push(`Created: ${session.created}`);
|
|
123
|
+
lines.push(`Updated: ${session.updated}`);
|
|
124
|
+
lines.push(`Entries: ${session.entries.length}`);
|
|
125
|
+
lines.push("");
|
|
126
|
+
for (const entry of session.entries) {
|
|
127
|
+
lines.push(`## ${entry.key}`);
|
|
128
|
+
lines.push(`_Updated: ${entry.timestamp}_`);
|
|
129
|
+
lines.push("");
|
|
130
|
+
lines.push(entry.value);
|
|
131
|
+
lines.push("");
|
|
132
|
+
}
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Format session list for display.
|
|
137
|
+
*/
|
|
138
|
+
export function formatSessionList(sessions) {
|
|
139
|
+
if (sessions.length === 0) {
|
|
140
|
+
return "No saved sessions. Use `save_session` to create one.";
|
|
141
|
+
}
|
|
142
|
+
const lines = [];
|
|
143
|
+
lines.push("# 📋 Saved Sessions\n");
|
|
144
|
+
lines.push("| Session | Entries | Created | Last Updated |");
|
|
145
|
+
lines.push("|---------|---------|---------|-------------|");
|
|
146
|
+
for (const s of sessions) {
|
|
147
|
+
const created = s.created.split("T")[0];
|
|
148
|
+
const updated = s.updated.split("T")[0];
|
|
149
|
+
lines.push(`| ${s.name} | ${s.entries} | ${created} | ${updated} |`);
|
|
150
|
+
}
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example ContextEngine Adapter — Notion Integration
|
|
3
|
+
*
|
|
4
|
+
* This is a SKELETON adapter showing how to build a custom data source
|
|
5
|
+
* connector for ContextEngine. It demonstrates the full Adapter interface.
|
|
6
|
+
*
|
|
7
|
+
* To use this adapter:
|
|
8
|
+
* 1. Copy this file to your project
|
|
9
|
+
* 2. Install the Notion SDK: npm install @notionhq/client
|
|
10
|
+
* 3. Add to contextengine.json:
|
|
11
|
+
* {
|
|
12
|
+
* "adapters": [{
|
|
13
|
+
* "name": "notion",
|
|
14
|
+
* "module": "./adapters/notion-adapter.js",
|
|
15
|
+
* "config": { "token": "$NOTION_API_TOKEN", "databases": ["db-id-1", "db-id-2"] }
|
|
16
|
+
* }]
|
|
17
|
+
* }
|
|
18
|
+
* 4. Set NOTION_API_TOKEN in your environment
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// Import the Adapter type from ContextEngine
|
|
24
|
+
// import type { Adapter } from "@compr/contextengine-mcp/adapters";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Notion adapter — fetches pages and databases from Notion
|
|
28
|
+
* and converts them into searchable ContextEngine chunks.
|
|
29
|
+
*/
|
|
30
|
+
const notionAdapter = {
|
|
31
|
+
name: "notion",
|
|
32
|
+
description: "Fetches pages and databases from Notion workspace",
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate adapter configuration.
|
|
36
|
+
* Return null if valid, error message string if invalid.
|
|
37
|
+
*/
|
|
38
|
+
validate(config) {
|
|
39
|
+
if (!config?.token) {
|
|
40
|
+
return "Missing 'token' in config. Set NOTION_API_TOKEN env var and use \"$NOTION_API_TOKEN\" in config.";
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Optional initialization — called once when adapter loads.
|
|
47
|
+
* Use for auth handshake, API client setup, etc.
|
|
48
|
+
*/
|
|
49
|
+
async init(config) {
|
|
50
|
+
// Example: Initialize Notion client
|
|
51
|
+
// const { Client } = await import("@notionhq/client");
|
|
52
|
+
// this._client = new Client({ auth: config.token });
|
|
53
|
+
console.error(`[Notion Adapter] Initialized with token: ${config?.token ? "✓" : "✗"}`);
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Collect data — the core method.
|
|
58
|
+
* Called during every reindex. Must be safe (read-only, no side effects).
|
|
59
|
+
* Returns Chunk[] compatible with ContextEngine's search index.
|
|
60
|
+
*/
|
|
61
|
+
async collect(config) {
|
|
62
|
+
const chunks = [];
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Example: Fetch pages from configured databases
|
|
66
|
+
const databases = config?.databases || [];
|
|
67
|
+
|
|
68
|
+
for (const dbId of databases) {
|
|
69
|
+
// In a real adapter, you'd call the Notion API here:
|
|
70
|
+
// const response = await this._client.databases.query({ database_id: dbId });
|
|
71
|
+
// for (const page of response.results) { ... }
|
|
72
|
+
|
|
73
|
+
// Example chunk structure:
|
|
74
|
+
chunks.push({
|
|
75
|
+
source: `Notion DB ${dbId}`,
|
|
76
|
+
section: "## Page Title",
|
|
77
|
+
content: "Page content extracted from Notion blocks...",
|
|
78
|
+
lineStart: 1,
|
|
79
|
+
lineEnd: 1,
|
|
80
|
+
// Optional: add indexedAt for temporal decay
|
|
81
|
+
indexedAt: new Date().toISOString(),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
// Never throw — return empty array on failure
|
|
86
|
+
console.error(`[Notion Adapter] Error: ${err?.message || err}`);
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return chunks;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Optional cleanup — called on server shutdown.
|
|
95
|
+
*/
|
|
96
|
+
async destroy() {
|
|
97
|
+
// Close connections, flush buffers, etc.
|
|
98
|
+
console.error("[Notion Adapter] Destroyed");
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Export as default (ContextEngine auto-detects)
|
|
103
|
+
export default notionAdapter;
|
|
104
|
+
|
|
105
|
+
// Alternative: export a factory function for per-instance config
|
|
106
|
+
// export function createAdapter(config) {
|
|
107
|
+
// return { ...notionAdapter, _config: config };
|
|
108
|
+
// }
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example ContextEngine Adapter — RSS/Atom Feed
|
|
3
|
+
*
|
|
4
|
+
* A minimal adapter that fetches RSS/Atom feeds and indexes them
|
|
5
|
+
* as searchable chunks. Good starting point for simple adapters.
|
|
6
|
+
*
|
|
7
|
+
* Usage in contextengine.json:
|
|
8
|
+
* {
|
|
9
|
+
* "adapters": [{
|
|
10
|
+
* "name": "feeds",
|
|
11
|
+
* "module": "./adapters/rss-adapter.js",
|
|
12
|
+
* "config": {
|
|
13
|
+
* "feeds": [
|
|
14
|
+
* "https://blog.example.com/rss.xml",
|
|
15
|
+
* "https://changelog.example.com/feed.atom"
|
|
16
|
+
* ],
|
|
17
|
+
* "maxItems": 20
|
|
18
|
+
* }
|
|
19
|
+
* }]
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export function createAdapter(config) {
|
|
26
|
+
const maxItems = config?.maxItems || 10;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
name: "rss-feed",
|
|
30
|
+
description: `RSS/Atom feed indexer (max ${maxItems} items per feed)`,
|
|
31
|
+
|
|
32
|
+
validate(cfg) {
|
|
33
|
+
if (!cfg?.feeds || !Array.isArray(cfg.feeds) || cfg.feeds.length === 0) {
|
|
34
|
+
return "Missing 'feeds' array in config. Provide at least one RSS/Atom URL.";
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async collect(cfg) {
|
|
40
|
+
const feeds = cfg?.feeds || [];
|
|
41
|
+
const chunks = [];
|
|
42
|
+
|
|
43
|
+
for (const feedUrl of feeds) {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(feedUrl);
|
|
46
|
+
if (!response.ok) continue;
|
|
47
|
+
const text = await response.text();
|
|
48
|
+
|
|
49
|
+
// Simple XML parsing — extract <item> or <entry> elements
|
|
50
|
+
const items = text.match(/<(?:item|entry)[\s>][\s\S]*?<\/(?:item|entry)>/gi) || [];
|
|
51
|
+
|
|
52
|
+
for (const item of items.slice(0, maxItems)) {
|
|
53
|
+
const title = item.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")?.trim() || "Untitled";
|
|
54
|
+
const description = item.match(/<(?:description|summary|content)[^>]*>([\s\S]*?)<\/(?:description|summary|content)>/i)?.[1]?.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")?.replace(/<[^>]+>/g, "")?.trim() || "";
|
|
55
|
+
const pubDate = item.match(/<(?:pubDate|published|updated)[^>]*>([\s\S]*?)<\/(?:pubDate|published|updated)>/i)?.[1]?.trim();
|
|
56
|
+
|
|
57
|
+
if (title || description) {
|
|
58
|
+
chunks.push({
|
|
59
|
+
source: new URL(feedUrl).hostname,
|
|
60
|
+
section: `## ${title}`,
|
|
61
|
+
content: description.slice(0, 2000),
|
|
62
|
+
lineStart: 1,
|
|
63
|
+
lineEnd: 1,
|
|
64
|
+
indexedAt: pubDate ? new Date(pubDate).toISOString() : new Date().toISOString(),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Skip failed feeds silently
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return chunks;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@compr/opscontext-mcp",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "OpsContext for AI Agents — read-only fleet visibility (PM2/nginx/Docker/git/cron) + tamper-evident audit log + policy-as-code hooks. The ops + compliance layer Claude Code can't grow natively.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"opscontext": "dist/cli.js",
|
|
9
|
+
"opscontext-mcp": "dist/cli.js",
|
|
10
|
+
"contextengine": "dist/cli.js",
|
|
11
|
+
"contextengine-mcp": "dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"lint": "eslint src/",
|
|
20
|
+
"prepublishOnly": "node scripts/check-npm-token-expiry.mjs && npm run build",
|
|
21
|
+
"check-token": "node scripts/check-npm-token-expiry.mjs"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"claude",
|
|
28
|
+
"cursor",
|
|
29
|
+
"copilot",
|
|
30
|
+
"ai-agents",
|
|
31
|
+
"ops",
|
|
32
|
+
"observability",
|
|
33
|
+
"audit-log",
|
|
34
|
+
"compliance",
|
|
35
|
+
"soc2",
|
|
36
|
+
"iso27001",
|
|
37
|
+
"policy-as-code",
|
|
38
|
+
"git-hooks",
|
|
39
|
+
"pre-commit",
|
|
40
|
+
"secret-scanning",
|
|
41
|
+
"fleet",
|
|
42
|
+
"context",
|
|
43
|
+
"knowledge-base",
|
|
44
|
+
"developer-tools"
|
|
45
|
+
],
|
|
46
|
+
"author": {
|
|
47
|
+
"name": "FASTPROD (PROD LLC)",
|
|
48
|
+
"url": "https://compr.fr"
|
|
49
|
+
},
|
|
50
|
+
"license": "BSL-1.1",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/FASTPROD/ContextEngine.git"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://compr.fr",
|
|
56
|
+
"bugs": {
|
|
57
|
+
"email": "yannick@compr.ch"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18.0.0"
|
|
61
|
+
},
|
|
62
|
+
"files": [
|
|
63
|
+
"dist/",
|
|
64
|
+
"!dist/test*",
|
|
65
|
+
"!dist/**/*.map",
|
|
66
|
+
"defaults/",
|
|
67
|
+
"skills/",
|
|
68
|
+
"examples/",
|
|
69
|
+
"LICENSE",
|
|
70
|
+
"README.md",
|
|
71
|
+
"CHANGELOG.md"
|
|
72
|
+
],
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
75
|
+
"zod": "^4.3.6"
|
|
76
|
+
},
|
|
77
|
+
"optionalDependencies": {
|
|
78
|
+
"@huggingface/transformers": "^3.8.1"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@types/node": "^25.2.3",
|
|
82
|
+
"eslint": "^10.0.1",
|
|
83
|
+
"typescript": "^5.9.3",
|
|
84
|
+
"typescript-eslint": "^8.56.0",
|
|
85
|
+
"vitest": "^4.0.18"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: opscontext
|
|
3
|
+
description: "OpsContext for AI Agents — the ops + compliance layer Claude Code can't grow natively. Read-only fleet visibility (PM2/nginx/Docker/git/cron) + tamper-evident hash-chained audit log (SOC2 CC7.2 / ISO 27001 A.12.4.1) + declarative policy-as-code git hooks (.contextengine/policy.json with secret_patterns, doc_coverage, deploy_verify_hosts, bypass_tokens) + persistent learnings + hybrid BM25/semantic search. Use when: (1) searching project documentation or context files, (2) collecting operational insights from workspaces, (3) storing and retrieving persistent learnings across sessions, (4) auditing project compliance or AI-readiness, (5) managing session data, (6) verifying the audit log chain, (7) authoring/validating policy.json. Zero API keys — runs 100% locally with CPU embeddings."
|
|
4
|
+
homepage: https://www.npmjs.com/package/@compr/opscontext-mcp
|
|
5
|
+
metadata: { "openclaw": { "emoji": "🧭", "requires": { "bins": ["npx"] }, "homepage": "https://www.npmjs.com/package/@compr/opscontext-mcp" } }
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# OpsContext for AI Agents — Knowledge Base + Ops + Compliance
|
|
9
|
+
|
|
10
|
+
ContextEngine turns your project documentation into a **queryable knowledge base** with hybrid BM25 keyword + semantic vector search. Zero API keys required — embeddings run locally on CPU.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
### 1. Initialize (one-time per project)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @compr/contextengine-mcp init
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Creates `contextengine.json` config + `.github/copilot-instructions.md` template in the current directory.
|
|
21
|
+
|
|
22
|
+
### 2. Search your knowledge base
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Ask the agent to search for context
|
|
26
|
+
search_context "deployment docker nginx setup"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
ContextEngine auto-discovers documentation files from 7 common patterns:
|
|
30
|
+
- `.github/copilot-instructions.md`
|
|
31
|
+
- `.github/SKILLS.md`
|
|
32
|
+
- `CLAUDE.md`
|
|
33
|
+
- `.cursorrules`
|
|
34
|
+
- `.cursor/rules`
|
|
35
|
+
- `AGENTS.md`
|
|
36
|
+
|
|
37
|
+
## CLI Usage (no MCP required)
|
|
38
|
+
|
|
39
|
+
ContextEngine also works as a standalone CLI tool — no MCP client needed:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx @compr/contextengine-mcp search "docker nginx" # Search knowledge base
|
|
43
|
+
npx @compr/contextengine-mcp list-sources # Show indexed sources
|
|
44
|
+
npx @compr/contextengine-mcp list-projects # Discover all projects
|
|
45
|
+
npx @compr/contextengine-mcp score # AI-readiness score
|
|
46
|
+
npx @compr/contextengine-mcp score --html # Visual HTML report
|
|
47
|
+
npx @compr/contextengine-mcp list-learnings security # List learnings by category
|
|
48
|
+
npx @compr/contextengine-mcp audit # Compliance audit
|
|
49
|
+
npx @compr/contextengine-mcp help # Show all commands
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## MCP Server Setup
|
|
53
|
+
|
|
54
|
+
ContextEngine runs as an **MCP server** via stdio transport. Configure it in your MCP client:
|
|
55
|
+
|
|
56
|
+
### VS Code (Per-Workspace)
|
|
57
|
+
|
|
58
|
+
Add to `.vscode/mcp.json` in your project root:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"servers": {
|
|
63
|
+
"contextengine": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["-y", "@compr/contextengine-mcp"],
|
|
66
|
+
"env": {
|
|
67
|
+
"CONTEXTENGINE_WORKSPACES": "/path/to/your/projects"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Claude Desktop
|
|
75
|
+
|
|
76
|
+
Add to Claude Desktop MCP config:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"contextengine": {
|
|
82
|
+
"command": "npx",
|
|
83
|
+
"args": ["-y", "@compr/contextengine-mcp"],
|
|
84
|
+
"env": {
|
|
85
|
+
"CONTEXTENGINE_WORKSPACES": "/path/to/your/projects"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### OpenClaw MCP Config
|
|
93
|
+
|
|
94
|
+
Add to your OpenClaw `openclaw.json` MCP servers section:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"contextengine": {
|
|
100
|
+
"command": "npx",
|
|
101
|
+
"args": ["-y", "@compr/contextengine-mcp"],
|
|
102
|
+
"env": {
|
|
103
|
+
"CONTEXTENGINE_WORKSPACES": "/path/to/your/projects"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Available Tools (17)
|
|
111
|
+
|
|
112
|
+
| Tool | Description |
|
|
113
|
+
|------|-------------|
|
|
114
|
+
| `search_context` | Hybrid BM25+semantic search with temporal decay. Modes: hybrid, keyword, semantic |
|
|
115
|
+
| `list_sources` | Show all indexed sources with chunk counts and embedding status |
|
|
116
|
+
| `read_source` | Read full content of a knowledge source by name |
|
|
117
|
+
| `reindex` | Force full re-index of all sources |
|
|
118
|
+
| `list_projects` | Discover and analyze all projects (tech stack, git, docker, pm2) |
|
|
119
|
+
| `check_ports` | Scan all projects for port conflicts |
|
|
120
|
+
| `run_audit` | Compliance agent — git remotes, hooks, .env, Docker, PM2, versions |
|
|
121
|
+
| `score_project` | AI-readiness scoring 0-100% with anti-gaming v2 (symlink/ghost config detection) |
|
|
122
|
+
| `save_session` | Save key-value entry to a named session for cross-session persistence |
|
|
123
|
+
| `load_session` | Load all entries from a named session |
|
|
124
|
+
| `list_sessions` | List all saved sessions with entry counts and timestamps |
|
|
125
|
+
| `end_session` | Pre-flight checklist — checks uncommitted git changes + doc freshness |
|
|
126
|
+
| `save_learning` | Save a permanent operational rule — auto-surfaces in search results |
|
|
127
|
+
| `list_learnings` | List all permanent learnings, optionally filtered by category |
|
|
128
|
+
| `import_learnings` | Bulk-import learnings from Markdown or JSON files |
|
|
129
|
+
| `activate` | Activate a Pro license on this machine |
|
|
130
|
+
| `activation_status` | Check current license activation status |
|
|
131
|
+
|
|
132
|
+
## Core Capabilities
|
|
133
|
+
|
|
134
|
+
### Hybrid Search
|
|
135
|
+
|
|
136
|
+
Combines three signals for optimal relevance:
|
|
137
|
+
- **40% BM25 keyword search** — IDF-weighted, rare terms rank higher
|
|
138
|
+
- **60% semantic similarity** — cosine distance via MiniLM-L6-v2 (22MB, local CPU)
|
|
139
|
+
- **Temporal decay** — 90-day half-life boosts recent content
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Search with mode selection
|
|
143
|
+
search_context "docker nginx proxy" --mode hybrid
|
|
144
|
+
search_context "deployment steps" --mode keyword
|
|
145
|
+
search_context "how to configure SSL" --mode semantic
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Operational Data Collection
|
|
149
|
+
|
|
150
|
+
Auto-collects from your projects (no setup needed):
|
|
151
|
+
- **Git**: branches, remotes, recent commits, hooks
|
|
152
|
+
- **package.json / composer.json**: dependencies, scripts
|
|
153
|
+
- **Docker**: Dockerfile, docker-compose services
|
|
154
|
+
- **PM2**: process list, ecosystem config
|
|
155
|
+
- **Nginx**: server blocks, proxy configs
|
|
156
|
+
- **.env**: variable names (never values)
|
|
157
|
+
- **Cron**: scheduled tasks
|
|
158
|
+
|
|
159
|
+
### Persistent Learnings
|
|
160
|
+
|
|
161
|
+
Save reusable patterns, bug fixes, and operational rules that auto-surface in search results:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Save a learning
|
|
165
|
+
save_learning --category "deployment" --rule "Always use --platform linux/amd64 for cross-arch Docker builds" --context "Apple Silicon to AMD64 server"
|
|
166
|
+
|
|
167
|
+
# List by category
|
|
168
|
+
list_learnings --category "security"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
16 categories: architecture, security, bug-patterns, deployment, testing, api, frontend, backend, infrastructure, tooling, devops, git, data, dependencies, performance, accessibility.
|
|
172
|
+
|
|
173
|
+
### Session Persistence
|
|
174
|
+
|
|
175
|
+
Save and restore key-value data across sessions via MCP tools (NOT files in the project):
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# Sessions are stored centrally in ~/.contextengine/sessions/ — NOT in the project directory
|
|
179
|
+
# Use the MCP tool or CLI command — do NOT create session files manually
|
|
180
|
+
|
|
181
|
+
save_session --name "project-x" --key "current_task" --value "Implementing auth flow"
|
|
182
|
+
load_session --name "project-x"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Important**: "Save session" means calling the `save_session` MCP tool or CLI command. It does NOT mean creating a markdown file. Sessions are stored in `~/.contextengine/sessions/` and auto-loaded on MCP startup.
|
|
186
|
+
|
|
187
|
+
## Configuration
|
|
188
|
+
|
|
189
|
+
Create `contextengine.json` in your project root (or run `npx @compr/contextengine-mcp init`):
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"sources": ["docs/architecture.md", "RUNBOOK.md"],
|
|
194
|
+
"workspaces": ["/home/user/projects"],
|
|
195
|
+
"patterns": [".github/copilot-instructions.md", "CLAUDE.md"],
|
|
196
|
+
"codeDirs": ["src", "lib"]
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Environment Variables
|
|
201
|
+
|
|
202
|
+
| Variable | Purpose |
|
|
203
|
+
|----------|---------|
|
|
204
|
+
| `CONTEXTENGINE_CONFIG` | Path to contextengine.json config file |
|
|
205
|
+
| `CONTEXTENGINE_WORKSPACES` | Colon-separated list of workspace directories |
|
|
206
|
+
|
|
207
|
+
## Architecture
|
|
208
|
+
|
|
209
|
+
- **Embeddings**: all-MiniLM-L6-v2 via @huggingface/transformers (22MB, local CPU, no API key)
|
|
210
|
+
- **Transport**: MCP SDK v1.26 via stdio
|
|
211
|
+
- **Chunking**: Markdown heading-based with 4-line overlap + code chunker (TS/JS/Python)
|
|
212
|
+
- **Dedup**: SHA-256 content hashing prevents duplicate chunks
|
|
213
|
+
- **Caching**: Embedding cache to disk (~/.contextengine/embedding-cache.json)
|
|
214
|
+
- **File watching**: fs.watch with 500ms debounce → auto re-index on changes
|
|
215
|
+
- **Non-blocking**: Keyword search available instantly, embeddings load in background
|
|
216
|
+
|
|
217
|
+
## Notes
|
|
218
|
+
|
|
219
|
+
- Requires **Node.js 18+**
|
|
220
|
+
- First run downloads the embedding model (~22MB) — subsequent runs use cache
|
|
221
|
+
- Keyword search is available instantly at startup; semantic search becomes available once the model loads
|
|
222
|
+
- License: BSL-1.1 (Business Source License)
|
|
223
|
+
- npm: `@compr/contextengine-mcp`
|
|
224
|
+
|
|
225
|
+
### Post-Commit Verification
|
|
226
|
+
|
|
227
|
+
After every `git push`, call `end_session` to verify nothing was missed:
|
|
228
|
+
|
|
229
|
+
- **MCP tool**: `end_session` — use when ContextEngine MCP server is connected
|
|
230
|
+
- **CLI fallback**: `npx @compr/contextengine-mcp end-session` — use when MCP is not connected (Cursor, Copilot, terminal sessions)
|
|
231
|
+
|
|
232
|
+
Resolve any FAIL items before finishing. Workflow: `commit → push → end_session → verify → fix anything it catches → re-commit if needed`.
|
|
233
|
+
|
|
234
|
+
## Lock Markers — Do Not Touch Verified Code
|
|
235
|
+
|
|
236
|
+
ContextEngine detects **lock markers** in source files and documentation. When a chunk contains a lock marker, search results display a `🔒 LOCKED` prefix — this means the code has been verified and **must not be modified, deleted, or re-implemented**.
|
|
237
|
+
|
|
238
|
+
### Recognized patterns
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
// LOCKED // in JS/TS/C/Go
|
|
242
|
+
/* LOCKED */ // block comment variant
|
|
243
|
+
# LOCKED // Python/Shell/YAML
|
|
244
|
+
<!-- LOCKED --> // HTML/Markdown
|
|
245
|
+
LOCKED — verified <date> // prose
|
|
246
|
+
DO NOT RE-AUDIT // audit skip signal
|
|
247
|
+
ALREADY IMPLEMENTED // status marker
|
|
248
|
+
VERIFIED — DO NOT // verified block
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Rules for agents
|
|
252
|
+
|
|
253
|
+
1. **Never delete or rewrite** code that contains a lock marker or sits within a locked section
|
|
254
|
+
2. **Never re-implement** functionality described in an `ALREADY IMPLEMENTED` block
|
|
255
|
+
3. **When you finish implementing a feature**, add a lock marker to signal future sessions:
|
|
256
|
+
```ts
|
|
257
|
+
// LOCKED — verified March 3 2026 — GA4 tag injection
|
|
258
|
+
```
|
|
259
|
+
4. **When you see `🔒 LOCKED` in search results**, treat it as read-only context — do not open the file to "fix" or "improve" it
|
|
260
|
+
5. **If you must modify locked code** (user explicitly requests it), remove the lock marker first and add a new one after verification
|