cuemap-mcp 0.7.0 → 0.7.2
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/README.md +61 -10
- package/build/index.js +579 -85
- package/build/job-status.js +44 -0
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# CueMap MCP Server
|
|
1
|
+
# CueMap MCP Server v0.7.2
|
|
2
2
|
|
|
3
3
|
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for CueMap, allowing AI coding assistants (like Claude Desktop, Cursor, Windsurf, and Antigravity) to instantly recall codebase context using the CueMap engine.
|
|
4
4
|
|
|
5
5
|
## Zero-Config Deployment
|
|
6
6
|
|
|
7
|
-
The CueMap MCP Server is designed to work completely out-of-the-box. When started, it automatically manages a high-performance Rust instance of the CueMap Server in the background.
|
|
7
|
+
The CueMap MCP Server is designed to work completely out-of-the-box. When started, it automatically manages a high-performance Rust instance of the CueMap Server in the background. The v0.7.2 engine bundles qint8 MiniLM-L3 by default and q4 MiniLM-L3 for the edge profile; no model download occurs at runtime.
|
|
8
8
|
|
|
9
9
|
You do **not** need to install or run the CueMap CLI manually. The correct pre-compiled binary for your operating system is automatically downloaded via optional NPM dependencies.
|
|
10
10
|
|
|
@@ -24,6 +24,12 @@ By default, the embedded engine runs on port `8080`. You can customize the serve
|
|
|
24
24
|
- `CUEMAP_PORT`: Override the port the embedded engine binds to (default: `8080`).
|
|
25
25
|
- `CUEMAP_CONFIG_PATH`: Absolute path to a custom `server_config.toml` to configure advanced Engine tuning, background jobs, and RAG search parameters.
|
|
26
26
|
- `CUEMAP_URL`: If you prefer to bypass the embedded engine and connect to a remotely hosted or separately running CueMap server, specify its URL here (e.g. `http://localhost:8080`).
|
|
27
|
+
- `CUEMAP_PROJECT`: Override the default project. Without this setting, CueMap derives a stable repository-scoped project from the current Git remote or working directory.
|
|
28
|
+
- `CUEMAP_LOG_PATH`: Override the embedded engine log path. It defaults to `~/.cuemap/server.log`, which is the file read by `cuemap logs`.
|
|
29
|
+
|
|
30
|
+
### Server lifecycle
|
|
31
|
+
|
|
32
|
+
The MCP server calls `EmbeddedCueMap.start()` as soon as Codex launches the plugin process, before any CueMap tool is invoked. It attaches to a compatible engine already listening at `CUEMAP_URL` or the configured port; otherwise it starts and owns a local engine automatically. `cuemap_init` does not start the server—it previews or applies a confirmed repository scope after the engine is ready. An engine owned by the MCP process is stopped gracefully when that process exits.
|
|
27
33
|
|
|
28
34
|
## Using with AI Agents
|
|
29
35
|
|
|
@@ -50,20 +56,64 @@ To use this MCP server with your AI assistant, add it to your assistant's MCP co
|
|
|
50
56
|
|
|
51
57
|
## Available Tools
|
|
52
58
|
|
|
53
|
-
- **`
|
|
54
|
-
- `path` (string): Absolute path to the
|
|
55
|
-
- `projectName` (string, optional):
|
|
56
|
-
|
|
57
|
-
-
|
|
59
|
+
- **`cuemap_init_preview`**: Performs a read-only repository metadata scan and returns supported top-level folders/root files, counts, the stable repository project ID, and any existing saved scope. Present this preview to the user before ingestion.
|
|
60
|
+
- `path` (string): Absolute path to the repository root.
|
|
61
|
+
- `projectName` (string, optional): Project ID override. Defaults to the stable repository-scoped ID.
|
|
62
|
+
- `includedPaths` (string[], optional): Repository-relative files or folders to preview. Empty means all supported files allowed by ignore rules.
|
|
63
|
+
- `ignoredPatterns` (string[], optional): Additional gitignore-style exclusions.
|
|
64
|
+
- `ignoredExtensions` (string[], optional): Additional excluded extensions.
|
|
65
|
+
|
|
66
|
+
- **`cuemap_init`**: Applies a user-confirmed repository ingestion scope, starts the initial scan, and keeps watching that scope for new, changed, deleted, newly ignored, or newly unignored supported files. It only reports verified completion after observing ingestion activity, reaching a terminal job phase, and completing intent annotation; an initial `idle` status with `0/0` writes is not considered complete.
|
|
67
|
+
- `path` (string): Absolute path to the repository root.
|
|
68
|
+
- `projectName` (string, optional): Project ID override. Defaults to the stable repository-scoped ID.
|
|
69
|
+
- `includedPaths` (string[], optional): User-approved repository-relative files or folders. Empty means all supported files allowed by ignore rules.
|
|
70
|
+
- `ignoredPatterns` (string[], optional): Additional gitignore-style exclusions.
|
|
71
|
+
- `ignoredExtensions` (string[], optional): Additional excluded extensions.
|
|
72
|
+
- `confirmed` (boolean): Must be true only after explicit user confirmation of the previewed scope.
|
|
73
|
+
|
|
74
|
+
- **`cuemap_status`**: Returns project-scoped background ingestion progress plus derived monitoring fields. Poll it after `cuemap_init` until `verified_complete` is `true`.
|
|
75
|
+
- `project` (string, optional): Project ID override. Defaults to the stable repository-scoped ID.
|
|
76
|
+
- `active`: Whether the current phase is non-terminal or writes/intent annotations remain pending.
|
|
77
|
+
- `observed_activity`: Whether this MCP process has observed an active phase or positive write/intent counts for the project.
|
|
78
|
+
- `pending_writes`: Remaining writes based on the latest totals.
|
|
79
|
+
- `pending_intents`: Remaining memory intent annotations based on the latest totals.
|
|
80
|
+
- `verified_complete`: True only after activity has been observed, the project reaches `done` or `idle`, no work failed or remains pending, and `intent_ready` is not false.
|
|
81
|
+
|
|
82
|
+
### Project inspection and memory lifecycle
|
|
83
|
+
|
|
84
|
+
- **`cuemap_projects`**: List projects and their summary metadata.
|
|
85
|
+
- **`cuemap_stats`**: Read repository-project statistics, or global engine statistics with `global: true`.
|
|
86
|
+
- **`cuemap_memory_get`**: Read one memory by numeric `memory_id`.
|
|
87
|
+
- **`cuemap_memory_reinforce`**: Reinforce one memory, optionally on explicit `cues`.
|
|
88
|
+
- **`cuemap_memory_delete`**: Permanently delete one memory. Requires `confirmed: true` after explicit user confirmation.
|
|
89
|
+
- **`cuemap_project_export`**: Export a cursor-paginated project page with configurable content, cue, and metadata inclusion.
|
|
90
|
+
- **`cuemap_project_artifacts`**: Inspect CueBridge artifact metadata without reloading it.
|
|
91
|
+
|
|
92
|
+
### Explicit ingestion
|
|
93
|
+
|
|
94
|
+
- **`cuemap_ingest_url`**: Ingest one URL or recursively crawl it with `depth` and `same_domain_only` controls.
|
|
95
|
+
- **`cuemap_ingest_content`**: Ingest supplied text with filename, source-key, metadata, structural-cue, segmentation, and optional one-vector-per-produced-chunk `embeddings`.
|
|
96
|
+
- **`cuemap_ingest_file`**: Ingest one user-approved local file path.
|
|
97
|
+
|
|
98
|
+
These tools are for explicit ingestion requests. Repository initialization continues to use the preview, confirmation, and watcher workflow above.
|
|
99
|
+
|
|
100
|
+
### Alias and Lexicon administration
|
|
101
|
+
|
|
102
|
+
- **`cuemap_alias_list`**, **`cuemap_alias_add`**, **`cuemap_alias_merge`**: Inspect and manage manual cue aliases. Merging requires `confirmed: true`.
|
|
103
|
+
- **`cuemap_lexicon_inspect`**, **`cuemap_lexicon_graph`**, **`cuemap_lexicon_wire`**, **`cuemap_lexicon_delete`**: Inspect and administer Lexicon relationships. Deletion requires `confirmed: true`.
|
|
104
|
+
- **`cuemap_add`**: Stores one natural-language memory. The project is created automatically if it does not exist.
|
|
58
105
|
- `content` (string): Memory content to store.
|
|
59
|
-
- `project` (string): Project ID that will own the memory.
|
|
106
|
+
- `project` (string, optional): Project ID that will own the memory. Defaults to the current repository-scoped project.
|
|
60
107
|
- `cues` (string[], optional): Explicit cues/tags to associate with the memory.
|
|
61
108
|
- `metadata` (object, optional): JSON metadata to store with the memory.
|
|
62
109
|
- `source_key` (string, optional): Stable source key for deterministic upsert/deduplication.
|
|
63
|
-
- `
|
|
110
|
+
- `event_time` (number, optional): Original event timestamp as Unix seconds. Defaults to ingestion time.
|
|
111
|
+
- `embedding` (number[], optional): Precomputed memory embedding.
|
|
64
112
|
- `disable_temporal_chunking` (boolean, optional): Disable temporal chunking for this memory.
|
|
65
113
|
- `async_ingest` (boolean, optional): Process ingestion in the background and return immediately.
|
|
66
114
|
|
|
115
|
+
- **`cuemap_intent_classify`**: Classifies text with the engine's local intent model. Accepts `target: "query"` or `"memory"` and returns relative intent scores, recall/memory eligibility, confidence weight, and model/taxonomy versions. Scores are ranking signals, not calibrated probabilities.
|
|
116
|
+
|
|
67
117
|
- **`cuemap_recall`**: Recalls context about a codebase from your CueMap integrated brain. Uses natural language and semantic search to find relevant information.
|
|
68
118
|
- `query` (string): The natural language query to search for.
|
|
69
119
|
- `limit` (number, optional): Maximum results to return (default: 10).
|
|
@@ -78,12 +128,13 @@ To use this MCP server with your AI assistant, add it to your assistant's MCP co
|
|
|
78
128
|
- `trace_timing` (boolean, optional): Include v0.7 timing diagnostics.
|
|
79
129
|
- `disable_salience_bias` (boolean, optional): Disable salience scoring bias.
|
|
80
130
|
- `disable_alias_expansion` (boolean, optional): Disable lexicon synonym injection during querying (default: true).
|
|
81
|
-
- `cuepacks` (string[], optional): Cuepack names to apply during query-intent expansion.
|
|
82
131
|
- `parent_fusion` (`off` | `auto` | `force`, optional): Chunk-parent fusion mode.
|
|
83
132
|
- `ordered_reconstruction` (`off` | `auto` | `force`, optional): Ordered session reconstruction mode.
|
|
84
133
|
- `evidence_coverage` (`off` | `auto` | `force`, optional): Multi-evidence coverage mode.
|
|
85
134
|
- `disable_cuebridge_artifacts` (boolean, optional): Disable CueBridge artifact expansion.
|
|
86
135
|
- `cuebridge_gap_limit` (number, optional): Maximum CueBridge gap expansions.
|
|
136
|
+
- `semantic_mode` (`lexical`, `semantic`, or `hybrid`, optional): Choose cue-only recall, vector candidate discovery, or local semantic reranking of lexical candidates. The engine default is `hybrid`.
|
|
137
|
+
- `query_embedding` (number[], optional): Supply a precomputed query vector when the calling application owns the embedding provider.
|
|
87
138
|
|
|
88
139
|
## License
|
|
89
140
|
|
package/build/index.js
CHANGED
|
@@ -8,106 +8,233 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
|
8
8
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
10
|
const cuemap_1 = __importDefault(require("cuemap"));
|
|
11
|
-
const
|
|
11
|
+
const embedded_1 = require("cuemap/embedded");
|
|
12
|
+
const node_buffer_1 = require("node:buffer");
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
12
14
|
const path_1 = __importDefault(require("path"));
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const child_process_1 = require("child_process");
|
|
17
|
+
const job_status_js_1 = require("./job-status.js");
|
|
18
|
+
let embeddedEngine = null;
|
|
16
19
|
let CUEMAP_URL = process.env.CUEMAP_URL;
|
|
17
20
|
let client;
|
|
21
|
+
const observedJobActivity = new Map();
|
|
22
|
+
function git(cwd, args) {
|
|
23
|
+
const result = (0, child_process_1.spawnSync)("git", args, { cwd, encoding: "utf8", timeout: 1_500 });
|
|
24
|
+
return result.status === 0 ? result.stdout.trim() : "";
|
|
25
|
+
}
|
|
26
|
+
function canonicalRemote(remote) {
|
|
27
|
+
return remote
|
|
28
|
+
.replace(/^[^@\s]+@([^:]+):/, "ssh://$1/")
|
|
29
|
+
.replace(/:\/\/[^/@]+@/, "://")
|
|
30
|
+
.replace(/\.git$/, "")
|
|
31
|
+
.replace(/\/+$/, "")
|
|
32
|
+
.toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
function defaultProjectId(cwd) {
|
|
35
|
+
if (process.env.CUEMAP_PROJECT)
|
|
36
|
+
return process.env.CUEMAP_PROJECT;
|
|
37
|
+
const root = git(cwd, ["rev-parse", "--show-toplevel"]) || cwd;
|
|
38
|
+
const remote = canonicalRemote(git(root, ["remote", "get-url", "origin"]));
|
|
39
|
+
const identity = remote || root;
|
|
40
|
+
const slug = path_1.default.basename(root)
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
43
|
+
.replace(/^-+|-+$/g, "") || "workspace";
|
|
44
|
+
const digest = (0, crypto_1.createHash)("sha256").update(identity).digest("hex").slice(0, 10);
|
|
45
|
+
return `repo-${slug.slice(0, 40)}-${digest}`.slice(0, 64);
|
|
46
|
+
}
|
|
47
|
+
const DEFAULT_PROJECT = defaultProjectId(process.cwd());
|
|
18
48
|
const server = new mcp_js_1.McpServer({
|
|
19
49
|
name: "cuemap-mcp",
|
|
20
|
-
version: "
|
|
50
|
+
version: "0.7.2",
|
|
21
51
|
});
|
|
22
|
-
function getCuemapBinaryPath() {
|
|
23
|
-
if (process.env.CUEMAP_BIN)
|
|
24
|
-
return process.env.CUEMAP_BIN;
|
|
25
|
-
// Check optional dependencies dynamically
|
|
26
|
-
try {
|
|
27
|
-
const platform = os_1.default.platform();
|
|
28
|
-
const arch = os_1.default.arch();
|
|
29
|
-
const pkgName = `@cuemap-dev/engine-${platform}-${arch}`;
|
|
30
|
-
const resolved = require.resolve(`${pkgName}/package.json`);
|
|
31
|
-
const binPath = path_1.default.join(path_1.default.dirname(resolved), "bin", "cuemap");
|
|
32
|
-
if (fs_1.default.existsSync(binPath))
|
|
33
|
-
return binPath;
|
|
34
|
-
}
|
|
35
|
-
catch (e) {
|
|
36
|
-
// Ignored
|
|
37
|
-
}
|
|
38
|
-
// Fallback: Check locally built binary (for development)
|
|
39
|
-
const localPath = path_1.default.resolve(__dirname, "../../rust_engine/target/release/cuemap");
|
|
40
|
-
if (fs_1.default.existsSync(localPath))
|
|
41
|
-
return localPath;
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
52
|
async function startEngine() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
const configuredPort = process.env.CUEMAP_PORT
|
|
54
|
+
? Number.parseInt(process.env.CUEMAP_PORT, 10)
|
|
55
|
+
: undefined;
|
|
56
|
+
if (configuredPort !== undefined && (!Number.isInteger(configuredPort) || configuredPort < 1 || configuredPort > 65535)) {
|
|
57
|
+
throw new Error("CUEMAP_PORT must be an integer between 1 and 65535");
|
|
48
58
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
embeddedEngine = await embedded_1.EmbeddedCueMap.start({
|
|
60
|
+
url: CUEMAP_URL,
|
|
61
|
+
binPath: process.env.CUEMAP_BIN,
|
|
62
|
+
port: configuredPort,
|
|
63
|
+
requiredCapabilities: [
|
|
64
|
+
"repository_ingestion_scope_v1",
|
|
65
|
+
"semantic_retrieval_v1",
|
|
66
|
+
"chunk_embeddings_v1",
|
|
67
|
+
"intent_classification_v1",
|
|
68
|
+
"intent_job_status_v1",
|
|
69
|
+
],
|
|
70
|
+
configPath: process.env.CUEMAP_CONFIG_PATH,
|
|
71
|
+
apiKey: process.env.CUEMAP_API_KEY,
|
|
72
|
+
logger: (message) => console.error(message),
|
|
73
|
+
});
|
|
74
|
+
CUEMAP_URL = embeddedEngine.url;
|
|
75
|
+
}
|
|
76
|
+
async function shutdown(signal) {
|
|
77
|
+
try {
|
|
78
|
+
await embeddedEngine?.stop();
|
|
53
79
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
CUEMAP_URL = `http://127.0.0.1:${port}`;
|
|
57
|
-
const args = ["start", "--port", port];
|
|
58
|
-
if (process.env.CUEMAP_CONFIG_PATH) {
|
|
59
|
-
args.push("--config", process.env.CUEMAP_CONFIG_PATH);
|
|
60
|
-
console.error(`Using custom config at ${process.env.CUEMAP_CONFIG_PATH}`);
|
|
80
|
+
finally {
|
|
81
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
61
82
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
console.error("Failed to start embedded engine:", err);
|
|
69
|
-
});
|
|
70
|
-
engineProcess.on("exit", (code) => {
|
|
71
|
-
console.error(`Embedded engine exited with code ${code}`);
|
|
72
|
-
});
|
|
73
|
-
process.on("SIGINT", () => {
|
|
74
|
-
if (engineProcess)
|
|
75
|
-
engineProcess.kill("SIGINT");
|
|
76
|
-
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
function projectClient(project) {
|
|
85
|
+
return new cuemap_1.default({
|
|
86
|
+
url: CUEMAP_URL,
|
|
87
|
+
apiKey: process.env.CUEMAP_API_KEY,
|
|
88
|
+
projectId: project || DEFAULT_PROJECT,
|
|
77
89
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
}
|
|
91
|
+
function jsonToolResult(value) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{
|
|
94
|
+
type: "text",
|
|
95
|
+
text: JSON.stringify(value, null, 2),
|
|
96
|
+
}],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function confirmationRequired(action) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: `No changes were made because ${action} requires confirmed: true after explicit user confirmation.`,
|
|
104
|
+
}],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function toolError(toolName, error) {
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
console.error(`Error executing ${toolName}`, error);
|
|
110
|
+
return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `Error executing ${toolName} tool: ${message || "Unknown error"}`,
|
|
114
|
+
}],
|
|
115
|
+
isError: true,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function engineRequest(method, requestPath, body, project) {
|
|
119
|
+
if (!CUEMAP_URL)
|
|
120
|
+
throw new Error("CueMap engine URL is not available");
|
|
121
|
+
const response = await fetch(`${CUEMAP_URL}${requestPath}`, {
|
|
122
|
+
method,
|
|
123
|
+
headers: {
|
|
124
|
+
...(body === undefined ? {} : { "content-type": "application/json" }),
|
|
125
|
+
...(process.env.CUEMAP_API_KEY ? { "X-API-Key": process.env.CUEMAP_API_KEY } : {}),
|
|
126
|
+
...(project ? { "X-Project-ID": project } : {}),
|
|
127
|
+
},
|
|
128
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
82
129
|
});
|
|
83
|
-
|
|
84
|
-
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
const message = await response.text();
|
|
132
|
+
throw new Error(`CueMap returned HTTP ${response.status}: ${message}`);
|
|
133
|
+
}
|
|
134
|
+
return await response.json();
|
|
85
135
|
}
|
|
86
136
|
async function main() {
|
|
87
137
|
await startEngine();
|
|
88
|
-
|
|
138
|
+
process.once("SIGINT", () => void shutdown("SIGINT"));
|
|
139
|
+
process.once("SIGTERM", () => void shutdown("SIGTERM"));
|
|
140
|
+
client = new cuemap_1.default({
|
|
141
|
+
url: CUEMAP_URL,
|
|
142
|
+
apiKey: process.env.CUEMAP_API_KEY,
|
|
143
|
+
projectId: DEFAULT_PROJECT,
|
|
144
|
+
});
|
|
145
|
+
server.registerTool("cuemap_init_preview", {
|
|
146
|
+
description: "Preview supported repository files without ingesting content. Call this before first-time repository initialization, present the grouped paths to the user, and ask them to confirm or adjust the selection before calling cuemap_init.",
|
|
147
|
+
inputSchema: zod_1.z.object({
|
|
148
|
+
path: zod_1.z.string().describe("Absolute path to the repository root."),
|
|
149
|
+
projectName: zod_1.z.string().optional().describe("Optional project ID. Defaults to the stable repository-scoped CueMap project ID."),
|
|
150
|
+
includedPaths: zod_1.z.array(zod_1.z.string()).optional().describe("Optional repository-relative files or folders to preview. Empty means every supported file allowed by ignore rules."),
|
|
151
|
+
ignoredPatterns: zod_1.z.array(zod_1.z.string()).optional().describe("Optional additional gitignore-style exclusion patterns."),
|
|
152
|
+
ignoredExtensions: zod_1.z.array(zod_1.z.string()).optional().describe("Optional additional excluded extensions without a leading dot."),
|
|
153
|
+
}),
|
|
154
|
+
}, async (args) => {
|
|
155
|
+
try {
|
|
156
|
+
const project = args.projectName || defaultProjectId(args.path);
|
|
157
|
+
const projects = await client.listProjects();
|
|
158
|
+
const projectIds = projects.map((item) => typeof item === "string" ? item : item.project_id);
|
|
159
|
+
let currentScope = null;
|
|
160
|
+
if (projectIds.includes(project)) {
|
|
161
|
+
try {
|
|
162
|
+
currentScope = await engineRequest("GET", `/projects/${encodeURIComponent(project)}/watch-dir`);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
currentScope = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const preview = await engineRequest("POST", "/ingest/directory/preview", {
|
|
169
|
+
watch_dir: args.path,
|
|
170
|
+
included_paths: args.includedPaths,
|
|
171
|
+
ignored_patterns: args.ignoredPatterns,
|
|
172
|
+
ignored_extensions: args.ignoredExtensions,
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
content: [{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: JSON.stringify({
|
|
178
|
+
project_id: project,
|
|
179
|
+
current_scope: currentScope,
|
|
180
|
+
selection_semantics: "included_paths are repository-relative files or folders; an empty list selects all supported files allowed by ignore rules",
|
|
181
|
+
preview,
|
|
182
|
+
}, null, 2),
|
|
183
|
+
}],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
console.error("Error previewing CueMap initialization", error);
|
|
188
|
+
return {
|
|
189
|
+
content: [{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `Error previewing repository ingestion: ${error?.message || "Unknown error"}`,
|
|
192
|
+
}],
|
|
193
|
+
isError: true,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
});
|
|
89
197
|
server.registerTool("cuemap_init", {
|
|
90
|
-
description: "
|
|
198
|
+
description: "Apply a user-confirmed repository ingestion scope and start CueMap's incremental filesystem watcher. Always call cuemap_init_preview first for a new repository and obtain explicit user confirmation before setting confirmed=true.",
|
|
91
199
|
inputSchema: zod_1.z.object({
|
|
92
|
-
path: zod_1.z.string().describe("Absolute path to the
|
|
93
|
-
projectName: zod_1.z.string().optional().describe("
|
|
200
|
+
path: zod_1.z.string().describe("Absolute path to the repository root."),
|
|
201
|
+
projectName: zod_1.z.string().optional().describe("Optional project ID. Defaults to the stable repository-scoped CueMap project ID."),
|
|
202
|
+
includedPaths: zod_1.z.array(zod_1.z.string()).optional().describe("User-approved repository-relative files or folders. Empty means every supported file allowed by ignore rules."),
|
|
203
|
+
ignoredPatterns: zod_1.z.array(zod_1.z.string()).optional().describe("Additional gitignore-style exclusion patterns approved by the user."),
|
|
204
|
+
ignoredExtensions: zod_1.z.array(zod_1.z.string()).optional().describe("Additional excluded extensions without a leading dot."),
|
|
205
|
+
confirmed: zod_1.z.boolean().describe("Must be true only after the user explicitly confirms the previewed ingestion scope."),
|
|
94
206
|
})
|
|
95
207
|
}, async (args) => {
|
|
96
208
|
try {
|
|
97
|
-
|
|
209
|
+
if (!args.confirmed) {
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: "text", text: "No repository files were ingested because the proposed scope was not confirmed." }],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const pName = args.projectName || defaultProjectId(args.path);
|
|
98
215
|
const projects = await client.listProjects();
|
|
99
216
|
const projectIds = projects.map((p) => typeof p === 'string' ? p : p.project_id);
|
|
100
217
|
if (!projectIds.includes(pName)) {
|
|
101
218
|
await client.createProject(pName);
|
|
102
219
|
}
|
|
103
|
-
await
|
|
220
|
+
await engineRequest("POST", `/projects/${encodeURIComponent(pName)}/watch-dir`, {
|
|
221
|
+
watch_dir: args.path,
|
|
222
|
+
included_paths: args.includedPaths,
|
|
223
|
+
ignored_patterns: args.ignoredPatterns,
|
|
224
|
+
ignored_extensions: args.ignoredExtensions,
|
|
225
|
+
});
|
|
104
226
|
console.error(`Started ingestion for project ${pName} at ${args.path}`);
|
|
227
|
+
observedJobActivity.set(pName, false);
|
|
105
228
|
// Polling for job completion
|
|
106
229
|
let isComplete = false;
|
|
107
230
|
let checks = 0;
|
|
231
|
+
let lastStatus = null;
|
|
108
232
|
while (!isComplete && checks < 60) {
|
|
109
233
|
const status = await client.jobsStatus(pName);
|
|
110
|
-
|
|
234
|
+
lastStatus = status;
|
|
235
|
+
const evaluation = (0, job_status_js_1.evaluateJobStatus)(status, observedJobActivity.get(pName));
|
|
236
|
+
observedJobActivity.set(pName, evaluation.observed_activity);
|
|
237
|
+
if (evaluation.verified_complete) {
|
|
111
238
|
isComplete = true;
|
|
112
239
|
break;
|
|
113
240
|
}
|
|
@@ -116,12 +243,18 @@ async function main() {
|
|
|
116
243
|
}
|
|
117
244
|
if (isComplete) {
|
|
118
245
|
return {
|
|
119
|
-
content: [{
|
|
246
|
+
content: [{
|
|
247
|
+
type: "text",
|
|
248
|
+
text: `Successfully initialized and ingested project ${pName}. CueMap observed ingestion activity, reached a terminal job phase, and completed memory intent annotation. The watcher will automatically ingest new or changed supported files within the approved scope. You can now use cuemap_recall.`,
|
|
249
|
+
}]
|
|
120
250
|
};
|
|
121
251
|
}
|
|
122
252
|
else {
|
|
123
253
|
return {
|
|
124
|
-
content: [{
|
|
254
|
+
content: [{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: `Project ${pName} initialized with the approved scope, but ingestion has not reached a verified terminal state. Last job status: ${JSON.stringify(lastStatus)}. Call cuemap_status periodically until verified_complete is true. The watcher is active and partial results may be returned by cuemap_recall.`,
|
|
257
|
+
}]
|
|
125
258
|
};
|
|
126
259
|
}
|
|
127
260
|
}
|
|
@@ -134,34 +267,41 @@ async function main() {
|
|
|
134
267
|
}
|
|
135
268
|
});
|
|
136
269
|
server.registerTool("cuemap_add", {
|
|
137
|
-
description: "Store a natural-language memory in
|
|
270
|
+
description: "Store a natural-language memory in CueMap. Uses the repository-scoped default project unless one is supplied, creates it when needed, and applies deterministic cue extraction plus any cues and metadata.",
|
|
138
271
|
inputSchema: zod_1.z.object({
|
|
139
272
|
content: zod_1.z.string().min(1).describe("The natural-language memory content to store."),
|
|
140
|
-
project: zod_1.z.string().min(1).describe("
|
|
273
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to a stable ID derived from the current Git repository."),
|
|
141
274
|
cues: zod_1.z.array(zod_1.z.string()).optional().describe("Optional explicit cues/tags to associate with the memory."),
|
|
142
275
|
metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional().describe("Optional JSON metadata to store with the memory."),
|
|
143
276
|
source_key: zod_1.z.string().optional().describe("Optional stable source key for deterministic upsert/deduplication."),
|
|
144
|
-
|
|
277
|
+
event_time: zod_1.z.number().nonnegative().optional().describe("Optional original event timestamp as Unix seconds. Defaults to ingestion time."),
|
|
278
|
+
embedding: zod_1.z.array(zod_1.z.number()).nonempty().optional().describe("Optional precomputed memory embedding."),
|
|
145
279
|
disable_temporal_chunking: zod_1.z.boolean().optional().describe("Disable temporal chunking for this memory. Default is false."),
|
|
146
280
|
async_ingest: zod_1.z.boolean().optional().describe("Process ingestion in the background and return immediately. Default is false."),
|
|
147
281
|
})
|
|
148
282
|
}, async (args) => {
|
|
149
283
|
try {
|
|
284
|
+
const project = args.project || DEFAULT_PROJECT;
|
|
150
285
|
const projects = await client.listProjects();
|
|
151
286
|
const projectIds = projects.map((p) => typeof p === "string" ? p : p.project_id);
|
|
152
|
-
if (!projectIds.includes(
|
|
153
|
-
await client.createProject(
|
|
287
|
+
if (!projectIds.includes(project)) {
|
|
288
|
+
await client.createProject(project);
|
|
154
289
|
}
|
|
155
|
-
const projectClient = new cuemap_1.default({
|
|
290
|
+
const projectClient = new cuemap_1.default({
|
|
291
|
+
url: CUEMAP_URL,
|
|
292
|
+
apiKey: process.env.CUEMAP_API_KEY,
|
|
293
|
+
projectId: project,
|
|
294
|
+
});
|
|
156
295
|
const memoryId = await projectClient.add(args.content, args.cues || [], args.metadata, args.disable_temporal_chunking || false, {
|
|
157
296
|
sourceKey: args.source_key,
|
|
158
|
-
|
|
297
|
+
eventTime: args.event_time,
|
|
298
|
+
embedding: args.embedding,
|
|
159
299
|
asyncIngest: args.async_ingest,
|
|
160
300
|
});
|
|
161
301
|
return {
|
|
162
302
|
content: [{
|
|
163
303
|
type: "text",
|
|
164
|
-
text: `Stored memory ${memoryId} in CueMap project ${
|
|
304
|
+
text: `Stored memory ${memoryId} in CueMap project ${project}.`,
|
|
165
305
|
}],
|
|
166
306
|
};
|
|
167
307
|
}
|
|
@@ -176,8 +316,360 @@ async function main() {
|
|
|
176
316
|
};
|
|
177
317
|
}
|
|
178
318
|
});
|
|
319
|
+
server.registerTool("cuemap_intent_classify", {
|
|
320
|
+
description: "Classify text with CueMap's local intent model and return recall/memory eligibility signals. Scores are ranking signals, not calibrated probabilities.",
|
|
321
|
+
inputSchema: zod_1.z.object({
|
|
322
|
+
text: zod_1.z.string().min(1),
|
|
323
|
+
target: zod_1.z.enum(["query", "memory"]).optional().describe("Classification target. Default is query."),
|
|
324
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
325
|
+
}),
|
|
326
|
+
}, async (args) => {
|
|
327
|
+
try {
|
|
328
|
+
return jsonToolResult(await projectClient(args.project).classifyIntent(args.text, args.target || "query"));
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
return toolError("cuemap_intent_classify", error);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
server.registerTool("cuemap_status", {
|
|
335
|
+
description: "Check CueMap background ingestion progress for a project. After cuemap_init, poll this tool until verified_complete is true. An initial idle status with 0/0 writes is not proof that ingestion completed.",
|
|
336
|
+
inputSchema: zod_1.z.object({
|
|
337
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the stable repository-scoped CueMap project ID."),
|
|
338
|
+
}),
|
|
339
|
+
}, async (args) => {
|
|
340
|
+
try {
|
|
341
|
+
const project = args.project || DEFAULT_PROJECT;
|
|
342
|
+
const status = await client.jobsStatus(project);
|
|
343
|
+
const evaluation = (0, job_status_js_1.evaluateJobStatus)(status, observedJobActivity.get(project));
|
|
344
|
+
observedJobActivity.set(project, evaluation.observed_activity);
|
|
345
|
+
return {
|
|
346
|
+
content: [{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: JSON.stringify({
|
|
349
|
+
project_id: project,
|
|
350
|
+
...status,
|
|
351
|
+
...evaluation,
|
|
352
|
+
}, null, 2),
|
|
353
|
+
}],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
console.error("Error checking CueMap status", error);
|
|
358
|
+
return {
|
|
359
|
+
content: [{
|
|
360
|
+
type: "text",
|
|
361
|
+
text: `Error executing cuemap_status tool: ${error?.message || "Unknown error"}`,
|
|
362
|
+
}],
|
|
363
|
+
isError: true,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
server.registerTool("cuemap_projects", {
|
|
368
|
+
description: "List CueMap projects and their available summary metadata.",
|
|
369
|
+
inputSchema: zod_1.z.object({}),
|
|
370
|
+
}, async () => {
|
|
371
|
+
try {
|
|
372
|
+
return jsonToolResult(await client.listProjects());
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
return toolError("cuemap_projects", error);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
server.registerTool("cuemap_stats", {
|
|
379
|
+
description: "Read CueMap statistics for the repository-scoped project or globally across the engine.",
|
|
380
|
+
inputSchema: zod_1.z.object({
|
|
381
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
382
|
+
global: zod_1.z.boolean().optional().describe("Return global engine statistics instead of project statistics. Default is false."),
|
|
383
|
+
}),
|
|
384
|
+
}, async (args) => {
|
|
385
|
+
try {
|
|
386
|
+
if (args.global && args.project) {
|
|
387
|
+
throw new Error("project and global cannot be supplied together");
|
|
388
|
+
}
|
|
389
|
+
const project = args.global ? undefined : (args.project || DEFAULT_PROJECT);
|
|
390
|
+
return jsonToolResult(await engineRequest("GET", "/stats", undefined, project));
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
return toolError("cuemap_stats", error);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
server.registerTool("cuemap_memory_get", {
|
|
397
|
+
description: "Get one CueMap memory by numeric ID.",
|
|
398
|
+
inputSchema: zod_1.z.object({
|
|
399
|
+
memory_id: zod_1.z.number().int().nonnegative().max(4_294_967_295),
|
|
400
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
401
|
+
}),
|
|
402
|
+
}, async (args) => {
|
|
403
|
+
try {
|
|
404
|
+
const project = args.project || DEFAULT_PROJECT;
|
|
405
|
+
return jsonToolResult(await engineRequest("GET", `/memories/${args.memory_id}`, undefined, project));
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
return toolError("cuemap_memory_get", error);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
server.registerTool("cuemap_memory_reinforce", {
|
|
412
|
+
description: "Reinforce one CueMap memory, optionally along specific cue pathways.",
|
|
413
|
+
inputSchema: zod_1.z.object({
|
|
414
|
+
memory_id: zod_1.z.number().int().nonnegative().max(4_294_967_295),
|
|
415
|
+
cues: zod_1.z.array(zod_1.z.string().min(1)).optional(),
|
|
416
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
417
|
+
}),
|
|
418
|
+
}, async (args) => {
|
|
419
|
+
try {
|
|
420
|
+
const project = args.project || DEFAULT_PROJECT;
|
|
421
|
+
return jsonToolResult(await engineRequest("PATCH", `/memories/${args.memory_id}/reinforce`, { cues: args.cues || [] }, project));
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
return toolError("cuemap_memory_reinforce", error);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
server.registerTool("cuemap_memory_delete", {
|
|
428
|
+
description: "Permanently delete one CueMap memory. Set confirmed=true only after explicit user confirmation.",
|
|
429
|
+
inputSchema: zod_1.z.object({
|
|
430
|
+
memory_id: zod_1.z.number().int().nonnegative().max(4_294_967_295),
|
|
431
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
432
|
+
confirmed: zod_1.z.boolean().describe("Must be true only after the user explicitly confirms permanent deletion."),
|
|
433
|
+
}),
|
|
434
|
+
}, async (args) => {
|
|
435
|
+
if (!args.confirmed)
|
|
436
|
+
return confirmationRequired("memory deletion");
|
|
437
|
+
try {
|
|
438
|
+
const project = args.project || DEFAULT_PROJECT;
|
|
439
|
+
return jsonToolResult(await engineRequest("DELETE", `/memories/${args.memory_id}`, undefined, project));
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
return toolError("cuemap_memory_delete", error);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
server.registerTool("cuemap_ingest_url", {
|
|
446
|
+
description: "Explicitly ingest content from a URL, optionally crawling same-domain links. Use only when the user asks to ingest that URL.",
|
|
447
|
+
inputSchema: zod_1.z.object({
|
|
448
|
+
url: zod_1.z.string().url(),
|
|
449
|
+
depth: zod_1.z.number().int().nonnegative().max(10).optional().describe("Crawl depth. Zero ingests only the supplied page."),
|
|
450
|
+
same_domain_only: zod_1.z.boolean().optional().describe("Restrict recursive crawling to the starting domain. Default is true."),
|
|
451
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
452
|
+
}),
|
|
453
|
+
}, async (args) => {
|
|
454
|
+
try {
|
|
455
|
+
const result = await projectClient(args.project).ingestUrl(args.url, args.depth || 0, args.same_domain_only ?? true);
|
|
456
|
+
return jsonToolResult(result);
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
return toolError("cuemap_ingest_url", error);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
server.registerTool("cuemap_ingest_content", {
|
|
463
|
+
description: "Explicitly ingest supplied raw content into CueMap. Use only when the user asks to persist that content.",
|
|
464
|
+
inputSchema: zod_1.z.object({
|
|
465
|
+
content: zod_1.z.string().min(1),
|
|
466
|
+
filename: zod_1.z.string().min(1).optional().describe("Logical source filename used for type detection. Default is content.txt."),
|
|
467
|
+
source_key: zod_1.z.string().optional().describe("Stable source key for deterministic replacement or deduplication."),
|
|
468
|
+
metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
469
|
+
structural_cues: zod_1.z.array(zod_1.z.string()).optional(),
|
|
470
|
+
embeddings: zod_1.z.array(zod_1.z.array(zod_1.z.number()).nonempty()).optional().describe("Optional one-vector-per-produced-chunk embeddings."),
|
|
471
|
+
segmenter: zod_1.z.enum(["sentence_window", "logical_block"]).optional(),
|
|
472
|
+
segment_window_size: zod_1.z.number().int().positive().optional(),
|
|
473
|
+
segment_overlap: zod_1.z.number().int().nonnegative().optional(),
|
|
474
|
+
segment_min_chunk_chars: zod_1.z.number().int().positive().optional(),
|
|
475
|
+
segment_max_chunk_chars: zod_1.z.number().int().positive().optional(),
|
|
476
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
477
|
+
}),
|
|
478
|
+
}, async (args) => {
|
|
479
|
+
try {
|
|
480
|
+
const result = await projectClient(args.project).ingestContent(args.content, args.filename || "content.txt", {
|
|
481
|
+
sourceKey: args.source_key,
|
|
482
|
+
metadata: args.metadata,
|
|
483
|
+
structuralCues: args.structural_cues,
|
|
484
|
+
embeddings: args.embeddings,
|
|
485
|
+
segmenter: args.segmenter,
|
|
486
|
+
segmentWindowSize: args.segment_window_size,
|
|
487
|
+
segmentOverlap: args.segment_overlap,
|
|
488
|
+
segmentMinChunkChars: args.segment_min_chunk_chars,
|
|
489
|
+
segmentMaxChunkChars: args.segment_max_chunk_chars,
|
|
490
|
+
});
|
|
491
|
+
return jsonToolResult(result);
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
return toolError("cuemap_ingest_content", error);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
server.registerTool("cuemap_ingest_file", {
|
|
498
|
+
description: "Explicitly ingest one local file into CueMap. Use only for a file the user has placed in scope and asked to ingest.",
|
|
499
|
+
inputSchema: zod_1.z.object({
|
|
500
|
+
path: zod_1.z.string().min(1).describe("Absolute or repository-relative path to the file."),
|
|
501
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
502
|
+
}),
|
|
503
|
+
}, async (args) => {
|
|
504
|
+
try {
|
|
505
|
+
const filePath = path_1.default.resolve(args.path);
|
|
506
|
+
if (!(0, node_fs_1.statSync)(filePath).isFile()) {
|
|
507
|
+
throw new Error(`Not a regular file: ${filePath}`);
|
|
508
|
+
}
|
|
509
|
+
const file = new node_buffer_1.File([(0, node_fs_1.readFileSync)(filePath)], path_1.default.basename(filePath), { type: "application/octet-stream" });
|
|
510
|
+
return jsonToolResult(await projectClient(args.project).ingestFile(file));
|
|
511
|
+
}
|
|
512
|
+
catch (error) {
|
|
513
|
+
return toolError("cuemap_ingest_file", error);
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
server.registerTool("cuemap_project_export", {
|
|
517
|
+
description: "Export a cursor-paginated page of memories from a CueMap project.",
|
|
518
|
+
inputSchema: zod_1.z.object({
|
|
519
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
520
|
+
cursor: zod_1.z.union([zod_1.z.number().int().nonnegative(), zod_1.z.string().min(1)]).optional(),
|
|
521
|
+
limit: zod_1.z.number().int().positive().max(10_000).optional(),
|
|
522
|
+
include_content: zod_1.z.boolean().optional(),
|
|
523
|
+
include_cues: zod_1.z.boolean().optional(),
|
|
524
|
+
include_metadata: zod_1.z.boolean().optional(),
|
|
525
|
+
}),
|
|
526
|
+
}, async (args) => {
|
|
527
|
+
try {
|
|
528
|
+
const project = args.project || DEFAULT_PROJECT;
|
|
529
|
+
return jsonToolResult(await projectClient(project).exportProject(project, {
|
|
530
|
+
cursor: args.cursor,
|
|
531
|
+
limit: args.limit,
|
|
532
|
+
includeContent: args.include_content,
|
|
533
|
+
includeCues: args.include_cues,
|
|
534
|
+
includeMetadata: args.include_metadata,
|
|
535
|
+
}));
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
return toolError("cuemap_project_export", error);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
server.registerTool("cuemap_project_artifacts", {
|
|
542
|
+
description: "Inspect CueBridge artifact metadata for a CueMap project without reloading or mutating it.",
|
|
543
|
+
inputSchema: zod_1.z.object({
|
|
544
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
545
|
+
}),
|
|
546
|
+
}, async (args) => {
|
|
547
|
+
try {
|
|
548
|
+
const project = args.project || DEFAULT_PROJECT;
|
|
549
|
+
return jsonToolResult(await projectClient(project).projectArtifacts(project));
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
return toolError("cuemap_project_artifacts", error);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
server.registerTool("cuemap_alias_list", {
|
|
556
|
+
description: "List manual cue aliases associated with one cue.",
|
|
557
|
+
inputSchema: zod_1.z.object({
|
|
558
|
+
cue: zod_1.z.string().min(1),
|
|
559
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
560
|
+
}),
|
|
561
|
+
}, async (args) => {
|
|
562
|
+
try {
|
|
563
|
+
return jsonToolResult(await projectClient(args.project).getAliases(args.cue));
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
return toolError("cuemap_alias_list", error);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
server.registerTool("cuemap_alias_add", {
|
|
570
|
+
description: "Add a manual weighted mapping from one cue to another.",
|
|
571
|
+
inputSchema: zod_1.z.object({
|
|
572
|
+
from: zod_1.z.string().min(1),
|
|
573
|
+
to: zod_1.z.string().min(1),
|
|
574
|
+
weight: zod_1.z.number().min(0).max(1).optional().describe("Association weight from 0 to 1. Default is 1."),
|
|
575
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
576
|
+
}),
|
|
577
|
+
}, async (args) => {
|
|
578
|
+
try {
|
|
579
|
+
const added = await projectClient(args.project).addAlias(args.from, args.to, args.weight ?? 1);
|
|
580
|
+
if (!added)
|
|
581
|
+
throw new Error("CueMap rejected the alias mapping");
|
|
582
|
+
return jsonToolResult({ added: true, from: args.from, to: args.to, weight: args.weight ?? 1 });
|
|
583
|
+
}
|
|
584
|
+
catch (error) {
|
|
585
|
+
return toolError("cuemap_alias_add", error);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
server.registerTool("cuemap_alias_merge", {
|
|
589
|
+
description: "Merge multiple cues into one canonical cue. Set confirmed=true only after explicit user confirmation.",
|
|
590
|
+
inputSchema: zod_1.z.object({
|
|
591
|
+
cues: zod_1.z.array(zod_1.z.string().min(1)).min(2),
|
|
592
|
+
to: zod_1.z.string().min(1),
|
|
593
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
594
|
+
confirmed: zod_1.z.boolean().describe("Must be true only after the user explicitly confirms the merge."),
|
|
595
|
+
}),
|
|
596
|
+
}, async (args) => {
|
|
597
|
+
if (!args.confirmed)
|
|
598
|
+
return confirmationRequired("alias merging");
|
|
599
|
+
try {
|
|
600
|
+
const merged = await projectClient(args.project).mergeAliases(args.cues, args.to);
|
|
601
|
+
if (!merged)
|
|
602
|
+
throw new Error("CueMap rejected the alias merge");
|
|
603
|
+
return jsonToolResult({ merged: true, cues: args.cues, to: args.to });
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
return toolError("cuemap_alias_merge", error);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
server.registerTool("cuemap_lexicon_inspect", {
|
|
610
|
+
description: "Inspect one cue and its Lexicon relationships.",
|
|
611
|
+
inputSchema: zod_1.z.object({
|
|
612
|
+
cue: zod_1.z.string().min(1),
|
|
613
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
614
|
+
}),
|
|
615
|
+
}, async (args) => {
|
|
616
|
+
try {
|
|
617
|
+
return jsonToolResult(await projectClient(args.project).lexiconInspect(args.cue));
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
return toolError("cuemap_lexicon_inspect", error);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
server.registerTool("cuemap_lexicon_graph", {
|
|
624
|
+
description: "Read the current Lexicon graph for a project.",
|
|
625
|
+
inputSchema: zod_1.z.object({
|
|
626
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
627
|
+
}),
|
|
628
|
+
}, async (args) => {
|
|
629
|
+
try {
|
|
630
|
+
return jsonToolResult(await projectClient(args.project).lexiconGraph());
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
return toolError("cuemap_lexicon_graph", error);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
server.registerTool("cuemap_lexicon_wire", {
|
|
637
|
+
description: "Manually wire a token to a canonical Lexicon cue.",
|
|
638
|
+
inputSchema: zod_1.z.object({
|
|
639
|
+
token: zod_1.z.string().min(1),
|
|
640
|
+
canonical: zod_1.z.string().min(1),
|
|
641
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
642
|
+
}),
|
|
643
|
+
}, async (args) => {
|
|
644
|
+
try {
|
|
645
|
+
return jsonToolResult(await projectClient(args.project).lexiconWire(args.token, args.canonical));
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
return toolError("cuemap_lexicon_wire", error);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
server.registerTool("cuemap_lexicon_delete", {
|
|
652
|
+
description: "Permanently delete one Lexicon entry. Set confirmed=true only after explicit user confirmation.",
|
|
653
|
+
inputSchema: zod_1.z.object({
|
|
654
|
+
entry_id: zod_1.z.union([zod_1.z.number().int().nonnegative(), zod_1.z.string().min(1)]),
|
|
655
|
+
project: zod_1.z.string().min(1).optional().describe("Optional project ID. Defaults to the repository-scoped project."),
|
|
656
|
+
confirmed: zod_1.z.boolean().describe("Must be true only after the user explicitly confirms permanent deletion."),
|
|
657
|
+
}),
|
|
658
|
+
}, async (args) => {
|
|
659
|
+
if (!args.confirmed)
|
|
660
|
+
return confirmationRequired("Lexicon entry deletion");
|
|
661
|
+
try {
|
|
662
|
+
const deleted = await projectClient(args.project).lexiconDelete(String(args.entry_id));
|
|
663
|
+
if (!deleted)
|
|
664
|
+
throw new Error("CueMap did not delete the Lexicon entry");
|
|
665
|
+
return jsonToolResult({ deleted: true, entry_id: args.entry_id });
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
return toolError("cuemap_lexicon_delete", error);
|
|
669
|
+
}
|
|
670
|
+
});
|
|
179
671
|
server.registerTool("cuemap_recall", {
|
|
180
|
-
description: "Recall context
|
|
672
|
+
description: "Recall ranked context from CueMap using lexical, semantic, or hybrid query signals. Hybrid is the engine default and uses the configured local encoder to rerank lexical candidates.",
|
|
181
673
|
inputSchema: zod_1.z.object({
|
|
182
674
|
query: zod_1.z.string().describe("The natural language query to search the codebase memory for."),
|
|
183
675
|
limit: zod_1.z.number().optional().describe("Optional limit on the number of results to return. Default is 10."),
|
|
@@ -192,7 +684,6 @@ async function main() {
|
|
|
192
684
|
trace_timing: zod_1.z.boolean().optional().describe("Include v0.7 timing diagnostics in the response. Default is false."),
|
|
193
685
|
disable_salience_bias: zod_1.z.boolean().optional().describe("Disable salience bias scoring. Default is false."),
|
|
194
686
|
disable_alias_expansion: zod_1.z.boolean().optional().describe("Disable alias expansion during querying. Default is true."),
|
|
195
|
-
cuepacks: zod_1.z.array(zod_1.z.string()).optional().describe("Optional cuepack names to apply during query-intent expansion."),
|
|
196
687
|
parent_fusion: zod_1.z.enum(["off", "auto", "force"]).optional().describe("Parent fusion mode for chunk-parent reconstruction. Default is off."),
|
|
197
688
|
parent_fusion_limit: zod_1.z.number().optional().describe("Candidate limit for parent fusion. Default is 80."),
|
|
198
689
|
parent_fusion_min_chunks: zod_1.z.number().optional().describe("Minimum sibling chunks required for parent fusion. Default is 2."),
|
|
@@ -206,10 +697,12 @@ async function main() {
|
|
|
206
697
|
evidence_coverage_max_sessions: zod_1.z.number().optional().describe("Maximum sessions considered for evidence coverage. Default is 3."),
|
|
207
698
|
disable_cuebridge_artifacts: zod_1.z.boolean().optional().describe("Disable CueBridge artifact expansion. Default is false."),
|
|
208
699
|
cuebridge_gap_limit: zod_1.z.number().optional().describe("Maximum CueBridge gap expansions. Default is 6."),
|
|
700
|
+
semantic_mode: zod_1.z.enum(["lexical", "semantic", "hybrid"]).optional().describe("Query signal mode. lexical uses cue recall only, semantic uses vector candidate discovery, and hybrid reranks lexical candidates. Default is hybrid."),
|
|
701
|
+
query_embedding: zod_1.z.array(zod_1.z.number()).optional().describe("Optional precomputed query vector. Use this when the application owns the embedding provider."),
|
|
209
702
|
}),
|
|
210
703
|
}, async (args) => {
|
|
211
704
|
try {
|
|
212
|
-
const { query, limit = 10, projects, cues, query_time, depth = 1, expansion_depth = 1, auto_reinforce = false, min_intersection, explain = false, trace_timing = false, disable_salience_bias = false, disable_alias_expansion = true,
|
|
705
|
+
const { query, limit = 10, projects, cues, query_time, depth = 1, expansion_depth = 1, auto_reinforce = false, min_intersection, explain = false, trace_timing = false, disable_salience_bias = false, disable_alias_expansion = true, parent_fusion = "off", parent_fusion_limit = 80, parent_fusion_min_chunks = 2, ordered_reconstruction = "off", ordered_reconstruction_limit = 80, ordered_session_scan_limit = 4096, ordered_max_sessions = 3, evidence_coverage = "off", evidence_coverage_limit = 100, evidence_coverage_session_scan_limit = 4096, evidence_coverage_max_sessions = 3, disable_cuebridge_artifacts = false, cuebridge_gap_limit = 6, semantic_mode = "hybrid", query_embedding } = args;
|
|
213
706
|
const results = await client.recall({
|
|
214
707
|
query_text: query,
|
|
215
708
|
cues,
|
|
@@ -224,7 +717,6 @@ async function main() {
|
|
|
224
717
|
trace_timing,
|
|
225
718
|
disable_salience_bias,
|
|
226
719
|
disable_alias_expansion,
|
|
227
|
-
cuepacks,
|
|
228
720
|
parent_fusion,
|
|
229
721
|
parent_fusion_limit,
|
|
230
722
|
parent_fusion_min_chunks,
|
|
@@ -238,6 +730,8 @@ async function main() {
|
|
|
238
730
|
evidence_coverage_max_sessions,
|
|
239
731
|
disable_cuebridge_artifacts,
|
|
240
732
|
cuebridge_gap_limit,
|
|
733
|
+
semantic_mode,
|
|
734
|
+
query_embedding,
|
|
241
735
|
});
|
|
242
736
|
let items = [];
|
|
243
737
|
if (results.results && Array.isArray(results.results)) {
|
|
@@ -262,7 +756,7 @@ async function main() {
|
|
|
262
756
|
],
|
|
263
757
|
};
|
|
264
758
|
}
|
|
265
|
-
let formattedText = `CueMap found ${items.length} relevant
|
|
759
|
+
let formattedText = `CueMap found ${items.length} relevant memories:\n\n`;
|
|
266
760
|
items.forEach((r, i) => {
|
|
267
761
|
const scoreStr = r.score !== undefined ? ` (Score: ${Number(r.score).toFixed(2)})` : '';
|
|
268
762
|
formattedText += `### Result ${i + 1}${scoreStr}\n`;
|
|
@@ -303,7 +797,7 @@ async function main() {
|
|
|
303
797
|
});
|
|
304
798
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
305
799
|
await server.connect(transport);
|
|
306
|
-
console.error(
|
|
800
|
+
console.error(`CueMap MCP server running on stdio (default project: ${DEFAULT_PROJECT})`);
|
|
307
801
|
}
|
|
308
802
|
main().catch((error) => {
|
|
309
803
|
console.error("Server error:", error);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateJobStatus = evaluateJobStatus;
|
|
4
|
+
const TERMINAL_PHASES = new Set(["done", "idle"]);
|
|
5
|
+
function evaluateJobStatus(status, previouslyObservedActivity = false) {
|
|
6
|
+
const phase = String(status.phase || "").toLowerCase();
|
|
7
|
+
const writesCompleted = Number.isFinite(status.writes_completed)
|
|
8
|
+
? Math.max(0, status.writes_completed)
|
|
9
|
+
: 0;
|
|
10
|
+
const writesTotal = Number.isFinite(status.writes_total)
|
|
11
|
+
? Math.max(0, status.writes_total)
|
|
12
|
+
: 0;
|
|
13
|
+
const intentCompleted = Number.isFinite(status.intent_completed)
|
|
14
|
+
? Math.max(0, status.intent_completed || 0)
|
|
15
|
+
: 0;
|
|
16
|
+
const intentTotal = Number.isFinite(status.intent_total)
|
|
17
|
+
? Math.max(0, status.intent_total || 0)
|
|
18
|
+
: 0;
|
|
19
|
+
const intentFailed = Number.isFinite(status.intent_failed)
|
|
20
|
+
? Math.max(0, status.intent_failed || 0)
|
|
21
|
+
: 0;
|
|
22
|
+
const pendingWrites = Math.max(0, writesTotal - writesCompleted);
|
|
23
|
+
const pendingIntents = Math.max(0, intentTotal - intentCompleted - intentFailed);
|
|
24
|
+
const active = !TERMINAL_PHASES.has(phase) || pendingWrites > 0 || pendingIntents > 0;
|
|
25
|
+
const observedActivity = previouslyObservedActivity
|
|
26
|
+
|| active
|
|
27
|
+
|| writesCompleted > 0
|
|
28
|
+
|| writesTotal > 0
|
|
29
|
+
|| intentCompleted > 0
|
|
30
|
+
|| intentTotal > 0
|
|
31
|
+
|| intentFailed > 0;
|
|
32
|
+
return {
|
|
33
|
+
active,
|
|
34
|
+
observed_activity: observedActivity,
|
|
35
|
+
pending_writes: pendingWrites,
|
|
36
|
+
pending_intents: pendingIntents,
|
|
37
|
+
verified_complete: observedActivity
|
|
38
|
+
&& TERMINAL_PHASES.has(phase)
|
|
39
|
+
&& pendingWrites === 0
|
|
40
|
+
&& pendingIntents === 0
|
|
41
|
+
&& intentFailed === 0
|
|
42
|
+
&& status.intent_ready !== false,
|
|
43
|
+
};
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cuemap-mcp",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "MCP server for CueMap",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc",
|
|
16
|
+
"test": "npm run build && node --test test/*.test.cjs",
|
|
17
|
+
"test:e2e": "CUEMAP_E2E=1 node --test test/*.test.cjs",
|
|
18
|
+
"test:pack": "node scripts/verify-packed-install.cjs",
|
|
16
19
|
"start": "node build/index.js",
|
|
17
20
|
"dev": "tsc --watch"
|
|
18
21
|
},
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
"type": "commonjs",
|
|
33
36
|
"dependencies": {
|
|
34
37
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
35
|
-
"cuemap": "^0.7.
|
|
38
|
+
"cuemap": "^0.7.2",
|
|
36
39
|
"zod": "^4.3.6"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
@@ -40,8 +43,8 @@
|
|
|
40
43
|
"typescript": "^5.9.3"
|
|
41
44
|
},
|
|
42
45
|
"optionalDependencies": {
|
|
43
|
-
"@cuemap-dev/engine-darwin-arm64": "^0.7.
|
|
44
|
-
"@cuemap-dev/engine-darwin-x64": "^0.7.
|
|
45
|
-
"@cuemap-dev/engine-linux-x64": "^0.7.
|
|
46
|
+
"@cuemap-dev/engine-darwin-arm64": "^0.7.2",
|
|
47
|
+
"@cuemap-dev/engine-darwin-x64": "^0.7.2",
|
|
48
|
+
"@cuemap-dev/engine-linux-x64": "^0.7.2"
|
|
46
49
|
}
|
|
47
50
|
}
|