@contextq/mcp 2.0.0 → 2.1.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/README.md +3 -3
- package/dist/index.js +7 -209
- package/package.json +11 -14
- package/dist/index.d.ts +0 -2
- package/dist/index.js.map +0 -1
- package/dist/tools.d.ts +0 -23
- package/dist/tools.js +0 -2543
- package/dist/tools.js.map +0 -1
- package/dist/tools.profile.test.d.ts +0 -14
- package/dist/tools.profile.test.js +0 -133
- package/dist/tools.profile.test.js.map +0 -1
- package/dist/tools.trace.test.d.ts +0 -11
- package/dist/tools.trace.test.js +0 -75
- package/dist/tools.trace.test.js.map +0 -1
- package/dist/trace-context.d.ts +0 -44
- package/dist/trace-context.js +0 -90
- package/dist/trace-context.js.map +0 -1
- package/dist/trace-context.test.d.ts +0 -6
- package/dist/trace-context.test.js +0 -86
- package/dist/trace-context.test.js.map +0 -1
package/README.md
CHANGED
|
@@ -152,13 +152,13 @@ Treat both settings as recommendations, not guarantees, of how long any given ca
|
|
|
152
152
|
|
|
153
153
|
## API version compatibility
|
|
154
154
|
|
|
155
|
-
The
|
|
155
|
+
The 99-tool surface exposed by this MCP server is a direct projection of the ContextQ API (24 loaded by default, the rest via `CONTEXT_MCP_TOOL_PROFILE=full` or on-demand -- see "Client configuration" above). The tool count and signatures drift with the server. Pin compatible versions:
|
|
156
156
|
|
|
157
157
|
| MCP package | ContextQ server API |
|
|
158
158
|
|---|---|
|
|
159
|
-
| `@contextq/mcp@2.x` | ContextQ v2.x (
|
|
159
|
+
| `@contextq/mcp@2.x` | ContextQ v2.x (99 tools) |
|
|
160
160
|
|
|
161
|
-
When upgrading your ContextQ server, check the [changelog](https://
|
|
161
|
+
When upgrading your ContextQ server, check the [changelog](https://contextq.dev/changelog/) and bump the MCP package to the matching major version. A version mismatch may surface unknown tools or break call signatures.
|
|
162
162
|
|
|
163
163
|
## License
|
|
164
164
|
|
package/dist/index.js
CHANGED
|
@@ -1,210 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
-
|
|
4
|
-
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { getActiveTools, handleToolCall } from "./tools.js";
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// MCP package version (read from package.json at runtime)
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
function readPackageVersion() {
|
|
13
|
-
try {
|
|
14
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
|
|
16
|
-
return pkg.version ?? "0.0.0";
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
return "0.0.0";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const MCP_VERSION = readPackageVersion();
|
|
23
|
-
// Minimum API version this MCP supports. Bump when the MCP depends on
|
|
24
|
-
// API-side features that did not exist in older server releases.
|
|
25
|
-
const MIN_API_VERSION = "0.1.0";
|
|
26
|
-
function parseSemVer(v) {
|
|
27
|
-
const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
28
|
-
if (!m)
|
|
29
|
-
return null;
|
|
30
|
-
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
|
31
|
-
}
|
|
32
|
-
function semVerGte(a, b) {
|
|
33
|
-
if (a.major !== b.major)
|
|
34
|
-
return a.major > b.major;
|
|
35
|
-
if (a.minor !== b.minor)
|
|
36
|
-
return a.minor > b.minor;
|
|
37
|
-
return a.patch >= b.patch;
|
|
38
|
-
}
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// API version handshake
|
|
41
|
-
//
|
|
42
|
-
// On startup, probes the API's health endpoint to read its version and
|
|
43
|
-
// compares it against MIN_API_VERSION. Mismatches or unreachable APIs
|
|
44
|
-
// produce a warning on stderr but never prevent startup — the tool surface
|
|
45
|
-
// can drift, and connecting an outdated MCP to a newer API (or vice versa)
|
|
46
|
-
// must still work from the MCP side. The warning is the signal.
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
async function handshake(baseUrl) {
|
|
49
|
-
const base = baseUrl.replace(/\/+$/, "");
|
|
50
|
-
let reachable = false;
|
|
51
|
-
let apiVersion;
|
|
52
|
-
// Quick reachability probe via GET /health (always public, no DB hit).
|
|
53
|
-
try {
|
|
54
|
-
const resp = await fetch(`${base}/health`, { signal: AbortSignal.timeout(3000), redirect: 'manual' });
|
|
55
|
-
if (resp.ok) {
|
|
56
|
-
reachable = true;
|
|
57
|
-
// Some deployments may embed a version key on the simple endpoint.
|
|
58
|
-
const body = (await resp.json().catch(() => null));
|
|
59
|
-
if (body && typeof body.version === "string") {
|
|
60
|
-
apiVersion = body.version;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
// /health unreachable — try /health/deep below.
|
|
66
|
-
}
|
|
67
|
-
// Deep health returns a `version` field (git SHA in the current server,
|
|
68
|
-
// may become a semver in future releases).
|
|
69
|
-
if (!apiVersion) {
|
|
70
|
-
try {
|
|
71
|
-
const resp = await fetch(`${base}/health/deep`, { signal: AbortSignal.timeout(3000), redirect: 'manual' });
|
|
72
|
-
if (resp.ok) {
|
|
73
|
-
reachable = true;
|
|
74
|
-
const body = (await resp.json().catch(() => null));
|
|
75
|
-
if (body && typeof body.version === "string") {
|
|
76
|
-
apiVersion = body.version;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// Both endpoints unreachable or version-less.
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (!reachable) {
|
|
85
|
-
console.error(`[shared-context-mcp] WARNING: API at ${base} is unreachable. ` +
|
|
86
|
-
`The MCP server has started but tools will return errors until the API becomes reachable.`);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (apiVersion) {
|
|
90
|
-
const apiSemVer = parseSemVer(apiVersion);
|
|
91
|
-
const minSemVer = parseSemVer(MIN_API_VERSION);
|
|
92
|
-
if (apiSemVer && minSemVer) {
|
|
93
|
-
if (!semVerGte(apiSemVer, minSemVer)) {
|
|
94
|
-
console.error(`[shared-context-mcp] WARNING: API version ${apiVersion} is older ` +
|
|
95
|
-
`than the minimum supported version ${MIN_API_VERSION}. ` +
|
|
96
|
-
`The MCP server has started but some tools may behave differently.`);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
console.error(`[shared-context-mcp] API version ${apiVersion} satisfies min version ${MIN_API_VERSION}.`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
// Version field exists but is not a semver (e.g. a git SHA).
|
|
104
|
-
// Skip the comparison — can't evaluate.
|
|
105
|
-
console.error(`[shared-context-mcp] API reports version identifier: ${apiVersion} ` +
|
|
106
|
-
`(not a semantic version — compatibility check skipped).`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
console.error(`[shared-context-mcp] WARNING: API at ${base} does not report a version. ` +
|
|
111
|
-
`Compatibility with min ${MIN_API_VERSION} was not verified.`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
// Server setup
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
// T618: Claude Code (2.1.222+) defers a server's tools behind ToolSearch once
|
|
118
|
-
// it exposes many of them -- the model sees tool NAMES only and must find
|
|
119
|
-
// tools by keyword search against name + description. `instructions` is the
|
|
120
|
-
// one channel guaranteed to render into the system prompt regardless of
|
|
121
|
-
// deferral (observable under "MCP Server Instructions" in any CC session
|
|
122
|
-
// connected to this server), so it carries the prefix map + the "load these
|
|
123
|
-
// together" guidance that a per-tool description cannot. Kept short --
|
|
124
|
-
// see docs/mcp-tools.md "Discoverability under ToolSearch deferral" for the
|
|
125
|
-
// keyword heuristic behind individual tool descriptions.
|
|
126
|
-
const SERVER_INSTRUCTIONS = "ContextQ: persistent memory + goal graph for agents. Prefixes: ctx_* = " +
|
|
127
|
-
"knowledge CRUD/search (save/search/get/list a context; use ctx_search to " +
|
|
128
|
-
"recall or check for existing knowledge before re-saving it), agent_* = " +
|
|
129
|
-
"agent session memory (boot/checkpoint/resume/lesson/handoff), goal_* = " +
|
|
130
|
-
"shared goal graph (add/advance/frontier -- durable cross-session tasks), " +
|
|
131
|
-
"relay_* = directed cross-role handoff (open/inbox/claim/advance). At the " +
|
|
132
|
-
"start of tracked work, load ctx_search, ctx_save, and agent_boot together " +
|
|
133
|
-
"in one ToolSearch select call rather than one at a time. T668: only a " +
|
|
134
|
-
"small default tool set is loaded on connection (~24 tools) -- if the " +
|
|
135
|
-
"tool you need isn't in your list, call ctx_tool_groups to see the rest " +
|
|
136
|
-
"(code graph, admin, relay, world-model, saved searches, knowledge graph, " +
|
|
137
|
-
"bulk ingest) and ctx_load_tool_group to load a group into this session.";
|
|
138
|
-
const server = new Server({
|
|
139
|
-
name: "shared-context",
|
|
140
|
-
version: MCP_VERSION,
|
|
141
|
-
}, {
|
|
142
|
-
capabilities: {
|
|
143
|
-
// T668: listChanged:true -- ctx_load_tool_group promotes a group into
|
|
144
|
-
// the active profile at runtime and we notify via
|
|
145
|
-
// notifications/tools/list_changed. Clients that ignore the
|
|
146
|
-
// notification (or never subscribed) still work: handleToolCall
|
|
147
|
-
// dispatches on tool name regardless of the last tools/list result.
|
|
148
|
-
tools: { listChanged: true },
|
|
149
|
-
},
|
|
150
|
-
instructions: SERVER_INSTRUCTIONS,
|
|
151
|
-
});
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
// Request handlers
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
156
|
-
// T668: default/extended tool-profile split -- see tools.ts's
|
|
157
|
-
// getActiveTools()/TOOL_GROUPS doc comment. Starts at the default (or
|
|
158
|
-
// full, via CONTEXT_MCP_TOOL_PROFILE=full) profile and grows as
|
|
159
|
-
// ctx_load_tool_group is called.
|
|
160
|
-
return { tools: getActiveTools() };
|
|
161
|
-
});
|
|
162
|
-
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
163
|
-
const { name, arguments: args, _meta } = req.params;
|
|
164
|
-
// T558: `_meta` already exists on every `tools/call` request in the
|
|
165
|
-
// current protocol -- forward it through so `handleToolCall` can pull out
|
|
166
|
-
// `traceparent`/`tracestate`/`baggage` when a client sends them. Absent
|
|
167
|
-
// on every request today; `handleToolCall`'s own extraction is the
|
|
168
|
-
// silent no-op path.
|
|
169
|
-
const result = await handleToolCall(name, (args ?? {}), _meta);
|
|
170
|
-
// T668: the active tool set just grew -- tell any client that negotiated
|
|
171
|
-
// capabilities.tools.listChanged so it re-fetches tools/list. Best-effort:
|
|
172
|
-
// a client that never subscribed, or a closed transport, must never fail
|
|
173
|
-
// the tool call it rides on.
|
|
174
|
-
if (name === "ctx_load_tool_group" && !result.isError) {
|
|
175
|
-
server.sendToolListChanged().catch(() => { });
|
|
176
|
-
}
|
|
177
|
-
return result;
|
|
178
|
-
});
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
// Start
|
|
181
|
-
// ---------------------------------------------------------------------------
|
|
182
|
-
async function main() {
|
|
183
|
-
const apiUrl = process.env.CONTEXT_API_URL ?? "http://localhost:38200";
|
|
184
|
-
// Validate CONTEXT_API_URL before connecting. An invalid or non-HTTP URL
|
|
185
|
-
// would cause every tool call to throw a cryptic fetch error; fail fast here.
|
|
186
|
-
try {
|
|
187
|
-
const parsed = new URL(apiUrl);
|
|
188
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
189
|
-
console.error(`[shared-context-mcp] Fatal: CONTEXT_API_URL must use http: or https: protocol, ` +
|
|
190
|
-
`got "${parsed.protocol}". Set CONTEXT_API_URL to a valid API base URL.`);
|
|
191
|
-
process.exit(1);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
console.error(`[shared-context-mcp] Fatal: CONTEXT_API_URL "${apiUrl}" is not a valid URL. ` +
|
|
196
|
-
`Set CONTEXT_API_URL to a valid API base URL (e.g. http://localhost:38200).`);
|
|
197
|
-
process.exit(1);
|
|
198
|
-
}
|
|
199
|
-
const transport = new StdioServerTransport();
|
|
200
|
-
await server.connect(transport);
|
|
201
|
-
const apiKey = process.env.CONTEXT_API_KEY;
|
|
202
|
-
console.error(`[shared-context-mcp] Connected (v${MCP_VERSION}). API: ${apiUrl}, Auth: ${apiKey ? 'configured' : 'NONE'}`);
|
|
203
|
-
// Non-fatal: warns on stderr but never blocks startup.
|
|
204
|
-
await handshake(apiUrl);
|
|
205
|
-
}
|
|
206
|
-
main().catch((err) => {
|
|
207
|
-
console.error("[shared-context-mcp] Fatal:", err);
|
|
208
|
-
process.exit(1);
|
|
209
|
-
});
|
|
210
|
-
//# sourceMappingURL=index.js.map
|
|
2
|
+
var M=Object.defineProperty;var a=(e,t)=>M(e,"name",{value:t,configurable:!0});import{Server as de}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as pe}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as le,CallToolRequestSchema as ue}from"@modelcontextprotocol/sdk/types.js";import{readFileSync as he}from"fs";import{fileURLToPath as me}from"url";import{dirname as ye,join as ge}from"path";import{AsyncLocalStorage as $}from"async_hooks";var b=/^[0-9a-f]+$/,D=256,G=512,F=8192;function W(e){if(typeof e!="string"||e.length===0||e.length>D)return!1;let t=e.split("-");if(t.length<4)return!1;let[n,r,i,o]=t;return!(n.length!==2||!b.test(n)||n==="ff"||r.length!==32||!b.test(r)||/^0+$/.test(r)||i.length!==16||!b.test(i)||/^0+$/.test(i)||o.length!==2||!b.test(o)||n==="00"&&t.length!==4)}a(W,"isValidTraceparent");function O(e){if(!e||typeof e!="object")return null;let t=e,n=t.traceparent;if(!W(n))return null;let r=t.tracestate,i=typeof r=="string"&&r.length>0&&r.length<=G?r:null,o=t.baggage,p=typeof o=="string"&&o.length>0&&o.length<=F?o:null;return{traceId:n.split("-")[1],traceparent:n,tracestate:i,baggage:p}}a(O,"extractTraceContext");var T=new $;var B="x-mcp-client-name",H="x-mcp-client-version",X=128,V=64,f={};function R(e,t){if(typeof e!="string")return;let n=e.replace(/[\p{Cc}]/gu,"").trim();return n?n.slice(0,t):void 0}a(R,"boundedClientValue");function q(e){f={name:R(e?.name,X),version:R(e?.version,V)}}a(q,"setMcpClientInfo");function Y(e){return`${(process.env.CONTEXT_API_URL??"http://localhost:38200").replace(/\/+$/,"")}${e}`}a(Y,"apiUrl");function c(e){let t=new URLSearchParams;for(let[r,i]of Object.entries(e))if(i!=null)if(Array.isArray(i))for(let o of i)t.append(r,String(o));else t.set(r,String(i));let n=t.toString();return n?`?${n}`:""}a(c,"toQueryString");async function s(e,t,n){let r={"Content-Type":"application/json"},i=process.env.CONTEXT_API_KEY;i&&(r.Authorization=`Bearer ${i}`);let o=T.getStore();o&&(r.traceparent=o.traceparent,o.tracestate&&(r.tracestate=o.tracestate),o.baggage&&(r.baggage=o.baggage)),f.name&&(r[B]=f.name),f.version&&(r[H]=f.version);let p={method:e,headers:r,redirect:"manual"};n!==void 0&&(p.body=JSON.stringify(n));let d=await fetch(Y(t),p);if(d.status>=300&&d.status<400)throw new Error(`Unexpected redirect from API (${d.status}) for ${e} ${t}`);let l=await d.text();if(!d.ok)throw new Error(`API ${e} ${t} returned ${d.status}: ${l}`);return l?JSON.parse(l):null}a(s,"request");var w=["reference","feedback","project","incident","lesson","user","synthesis","map","moc"],v=["personal","workspace","team"],g=["episodic","semantic","procedural"],S=["fleeting","working","evergreen","archived"],z=["supersedes","contradicts","implements","derived_from","related_to"],K=["outbound","inbound","both"],C="ContextQ: persistent memory + goal graph for agents. Prefixes: ctx_* = knowledge CRUD/search (save/search/get/list a context; use ctx_search to recall or check for existing knowledge before re-saving it), agent_* = agent session memory (boot/checkpoint/resume/lesson/handoff), goal_* = shared goal graph (add/advance/frontier -- durable cross-session tasks), relay_* = directed cross-role handoff (open/inbox/claim/advance). At the start of tracked work, load ctx_search, ctx_save, and agent_boot together in one ToolSearch select call rather than one at a time. T668: only a small default tool set is loaded on connection (~24 tools) -- if the tool you need isn't in your list, call ctx_tool_groups to see the rest (code graph, admin, relay, world-model, saved searches, knowledge graph, bulk ingest) and ctx_load_tool_group to load a group into this session.",x=[{name:"ctx_save",description:`Save a new context entry (reference doc, feedback, project note, incident report, lesson learned, or user profile). Use this when you want to persist knowledge for future retrieval. Optional lifecycle/valid_from/valid_to flag the note's maturity and bi-temporal validity. Response includes atomic, quality_score, and lifecycle once the backend judge has run. Trigger: user asks you to remember/save something ("nh\u1EDB c\xE1i n\xE0y", "l\u01B0u l\u1EA1i", "ghi nh\u1EDB gi\xFAp", "remember this", "save this", "note this down") \u2014 call whenever work-relevant info should persist across sessions. Only call when the request is actually about tracked work/memory; ignore unrelated casual chat.`,inputSchema:{type:"object",properties:{name:{type:"string",description:"Short, descriptive title"},description:{type:"string",description:"One-line summary used for search ranking"},content:{type:"string",description:"Full content / body of the context entry"},type:{type:"string",enum:[...w],description:"Category of the context entry"},scope:{type:"string",enum:[...v],description:"Visibility scope (defaults to personal)"},workspace:{type:"string",description:"Workspace identifier that owns this context"},project:{type:"string",description:"Optional project identifier within the workspace"},tags:{type:"array",items:{type:"string"},description:"Tags for categorization and filtering"},metadata:{type:"object",description:"Arbitrary key-value metadata"},lifecycle:{type:"string",enum:[...S],description:"Lifecycle state of the note. Omit to let the backend default to 'working'. Use 'fleeting' for transient captures, 'evergreen' for durable knowledge, 'archived' to retire from active surfacing."},memoryKind:{type:"string",enum:[...g],description:"Taxonomy override: 'episodic' (an event/interaction happened), 'semantic' (durable factual/reference knowledge), or 'procedural' (how-to / lesson that changes future behavior). Omit to let the backend classify it from the context type (a cheap heuristic, optionally refined by the atomicity judge)."},valid_from:{type:"string",description:"Bi-temporal: ISO 8601 timestamp when the fact this context describes started being true. Optional."},valid_to:{type:"string",description:"Bi-temporal: ISO 8601 timestamp when the fact stopped being true. Optional; null means still valid."}},required:["name","description","content","type","workspace"]}},{name:"ctx_search",description:"Search / recall saved knowledge and memories using hybrid full-text + semantic search ranked by relevance \u2014 the default tool for 'what do I know about X' or 'did I already save this'. Use this when you need to find, remember, or look up existing knowledge by keyword or phrase. Set chunk_search=false to disable per-chunk passage matching, lifecycle_boost=false for legacy ranking, or include_archived=true to surface retired notes. Results may include lifecycle, quality_score, and matched_chunk per hit.",inputSchema:{type:"object",properties:{query:{type:"string",description:"Search query (full-text + semantic)"},workspace:{type:"string",description:"Filter by workspace identifier"},project:{type:"string",description:"Filter by project identifier"},type:{type:"string",enum:[...w],description:"Filter by context type"},tags:{type:"array",items:{type:"string"},description:"Filter by tags (all must match)"},scope:{type:"string",enum:[...v],description:"Filter by visibility scope"},subjectId:{type:"string",description:"Filter to memories scoped to a single end-user (Mem0-parity user_id axis). Matches the subjectId used when the memory was created via ctx_remember / POST /api/memory. Omit to search across all subjects."},memoryKind:{type:"string",enum:[...g],description:"Filter to a single taxonomy kind: 'episodic' (events/interactions), 'semantic' (durable reference knowledge), or 'procedural' (how-to / lessons). Omit to search across all kinds."},epistemicMin:{type:"string",enum:["observed","told","inferred","assumed"],description:"Epistemic floor (T358): only return contexts at or above this confidence tier (weakest->strongest: assumed < inferred < told < observed). E.g. 'told' excludes 'assumed'/'inferred' rows. Omit to search across all tiers."},limit:{type:"number",description:"Max results to return (default 20)"},offset:{type:"number",description:"Offset for pagination"},chunk_search:{type:"boolean",description:"When true (default), search at the chunk level so individual passages can match. When false, only whole-context fields are scored."},lifecycle_boost:{type:"boolean",description:"When true (default), apply the evergreen/fleeting lifecycle multipliers to the ranking. Set false for legacy ts_rank * tagBoost * recencyBoost only."},include_archived:{type:"boolean",description:"When true, include lifecycle='archived' rows. Default false \u2014 archived notes are excluded from regular searches."},level:{type:"string",enum:["full","paragraph","sentence"],description:"Layered representation level. 'full' (default) keeps the stored description; 'paragraph' replaces it with a ~120-word distill; 'sentence' replaces it with a ~25-word claim. Use 'sentence' for cheap high-density agent prompts where every token matters. Falls back to the stored description when the distill is not yet populated for a row."},include_trace_events:{type:"boolean",description:"T375: when true, ALSO search episodic tool-call trace summaries ('what did I try before this worked?') and return them in a separate `traceEvents` field. Default false \u2014 this is fully additive and never changes `results` or its ranking. Combine with trace_session_id to scope to one session."},trace_session_id:{type:"number",description:"Scope trace-event fusion to one agent session (omit to search across the tenant's trace events). Ignored unless include_trace_events is true."}},required:["query"]}},{name:"ctx_explain_recall",description:"Explain WHY a search result was recalled by reconstructing the RRF (Reciprocal Rank Fusion) math for a captured retrieval trace: per-signal score breakdown (PG lexical rank + raw ts_rank, Elasticsearch BM25 rank + raw score, chunk cosine similarity, recency/tag/lifecycle/salience/confidence boost multipliers), the RRF-fused score, and the final rank -- for every result of that search, plus a human-readable narrative per result. Requires the server to have RETRIEVAL_TRACE_ENABLED=true and that this particular search was sampled in by RETRIEVAL_TRACE_SAMPLE_RATE -- this is a V1 observability surface with no listing endpoint yet, so trace ids come from direct DB inspection during this phase. Each certificate is optionally signed with the tenant's Ed25519 export key (T370 MIF); `signatureVerified` in the response reports whether the signature checks out against the tenant's published public key, making the certificate usable as tamper-evident evidence for an auditor.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The retrieval_traces row id to explain."}},required:["id"]}},{name:"ctx_list",description:"List context entries with optional filters. Use this to browse existing contexts by workspace, project, type, or tag without a search query.",inputSchema:{type:"object",properties:{workspace:{type:"string",description:"Filter by workspace identifier"},project:{type:"string",description:"Filter by project identifier"},type:{type:"string",enum:[...w],description:"Filter by context type"},tag:{type:"string",description:"Filter by a single tag"},scope:{type:"string",enum:[...v],description:"Filter by visibility scope"},memoryKind:{type:"string",enum:[...g],description:"Filter to a single taxonomy kind: 'episodic', 'semantic', or 'procedural'."},limit:{type:"number",description:"Max results to return (default 20)"},offset:{type:"number",description:"Offset for pagination"}},required:[]}},{name:"ctx_get",description:"Get a single context entry by its ID. Use this when you already know the exact context ID you want to read. Response includes lifecycle, atomic, quality_score, valid_from, and valid_to alongside the standard fields.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID"}},required:["id"]}},{name:"ctx_update",description:"Update an existing context entry. Only the provided fields are changed; omitted fields remain unchanged. Use this to correct, append to, reclassify, or retire (archive) an existing entry. Optional lifecycle/valid_from/valid_to update note maturity and bi-temporal validity.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID to update"},name:{type:"string",description:"New title"},description:{type:"string",description:"New description"},content:{type:"string",description:"New content body"},type:{type:"string",enum:[...w],description:"New context type"},scope:{type:"string",enum:[...v],description:"New visibility scope"},tags:{type:"array",items:{type:"string"},description:"Replacement set of tags"},metadata:{type:"object",description:"Replacement metadata object"},archivedAt:{type:["string","null"],description:"Set to null to unarchive, or ISO date string to archive"},lifecycle:{type:"string",enum:[...S],description:"New lifecycle state (fleeting/working/evergreen/archived)."},memoryKind:{type:"string",enum:[...g],description:"Taxonomy override: 'episodic', 'semantic', or 'procedural'. Setting this locks the value against the fire-and-forget atomicity-judge LLM refinement on this write."},valid_from:{type:"string",description:"Bi-temporal: ISO 8601 timestamp when the fact started being true."},valid_to:{type:"string",description:"Bi-temporal: ISO 8601 timestamp when the fact stopped being true; null to keep open."}},required:["id"]}},{name:"ctx_delete",description:"Permanently delete a context entry by ID. This action cannot be undone. Use this only when you are sure the entry should be removed.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID to delete"}},required:["id"]}},{name:"ctx_bulk_update",description:"Apply the same lifecycle move and/or archive flag to many contexts in a single call. Use this when you need to retire, mature, or reclassify a batch (capped at 200 ids per call). Tenant-scoped on the backend \u2014 ids belonging to other tenants are silently dropped and reported in the failed list. Returns { updated: number[], failed: { id, error }[] }.",inputSchema:{type:"object",properties:{ids:{type:"array",items:{type:"number"},description:"Context IDs to update (max 200 per call)"},patch:{type:"object",description:"Fields to apply uniformly. Supply lifecycle, archive, or both. archive=true stamps archived_at to NOW().",properties:{lifecycle:{type:"string",enum:[...S],description:"New lifecycle state for every supplied id"},archive:{type:"boolean",description:"When true, stamp archived_at = NOW() on every supplied id (use to retire a batch)."}}}},required:["ids","patch"]}},{name:"ctx_undo_archive",description:"Undo a memory-evolution auto-archive within the configurable window (default 72h, env EVOLUTION_ARCHIVE_UNDO_WINDOW_HOURS). Only contexts archived by the memory-evolution feature qualify - manual PATCH lifecycle archives don't. Returns the restored context. Companion endpoint: ctx_undo_archive_info for eligibility check.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Context ID to undo archive on."}},required:["id"]}},{name:"ctx_undo_archive_info",description:"Check whether a context's auto-archive is still undo-able. Returns `{eligible: bool, archivedAt, expiresAt, archivedByFocalId, confidence, withinWindow}`. Use before ctx_undo_archive to surface a meaningful UX (button enabled/disabled, countdown to expiry) instead of round-tripping the undo attempt.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Context ID to inspect."}},required:["id"]}},{name:"ctx_import",description:"Bulk import context entries from an external source. Optionally filter by workspace and preview with a dry run before committing.",inputSchema:{type:"object",properties:{workspace:{type:"string",description:"Filter import to a specific workspace"},dryRun:{type:"boolean",description:"If true, validate and preview the import without persisting changes"}},required:[]}},{name:"ctx_stats",description:"Get aggregate statistics: total context count, breakdown by workspace, type, tag, recently updated entries, and orphan_rate (notes with no tags and no inbound references). Use this for an overview of what is stored and to spot disconnected knowledge.",inputSchema:{type:"object",properties:{},required:[]}},{name:"ctx_dream",description:`Run memory consolidation (dream) on a workspace's context entries. Clusters related entries using vector similarity, synthesizes each cluster into a 'synthesis' entry via LLM, and writes a dream log. Use dryRun to preview clusters without persisting.
|
|
3
|
+
|
|
4
|
+
T497: dream on a large workspace (more than DREAM_ASYNC_THRESHOLD contexts, default 100) runs async \u2014 the response is { jobId, statusUrl } and you must poll ctx_ingest_status (or GET /api/ingest-jobs/:id) until status='succeeded' or 'failed' to read the full result. Small workspaces still return the full result inline. Pass async=true/false to force a path explicitly.`,inputSchema:{type:"object",properties:{workspace:{type:"string",description:"Workspace identifier to consolidate knowledge for"},project:{type:"string",description:"Optional project filter \u2014 consolidate only entries for this project"},dryRun:{type:"boolean",description:"If true, preview clusters without creating synthesis entries (default false)"},async:{type:"boolean",description:"Force the async path (true) or sync path (false). Omit to let the server auto-pick based on workspace size \u2014 async responses are { jobId, statusUrl }; sync responses are the full dream result."}},required:["workspace"]}},{name:"ctx_link",description:"Manually create a typed link from one context to another (supersedes/contradicts/implements/derived_from/related_to). The backend already auto-creates links during memory evolution; only call this tool when you want to assert a relationship the system missed or override it explicitly. The 'reason' arg is recorded as the link's created_by annotation for audit.",inputSchema:{type:"object",properties:{source_id:{type:"number",description:"ID of the source context (the link's tail)"},target_id:{type:"number",description:"ID of the target context (the link's head)"},type:{type:"string",enum:[...z],description:"Link semantics: supersedes (replaces), contradicts (disagrees), implements (concrete impl of target spec), derived_from (extracted from target), related_to (generic association)."},confidence:{type:"number",description:"Optional confidence score in [0, 1]. Omit if not meaningful."},reason:{type:"string",description:"Optional human-readable rationale; recorded as created_by annotation for audit."}},required:["source_id","target_id","type"]}},{name:"ctx_links",description:"List typed links for a context \u2014 incoming, outgoing, or both. Use this to discover how a context is connected (what it supersedes, what supersedes it, what implements it, etc.) before making decisions about updates or archival. The `created_by` field on each link indicates origin: `wikilink:<raw title>` marks edges auto-created from `[[Wiki Title]]` tokens in the source content, `memory-evolution` marks LLM-judged edges, and other values are manual.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID whose links you want to list"},direction:{type:"string",enum:[...K],description:"Which links to return: 'outbound' (this context links to others), 'inbound' (others link to this), or 'both' (default)."}},required:["id"]}},{name:"ctx_evolve",description:"Re-run memory evolution for an existing context. Examines K=5 nearest neighbors and creates typed links / archives superseded notes via LLM judging. Use when you've updated a context substantially or want to recompute relationships. Pair with dry_run=true first to preview.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID to re-run memory evolution for"},k_neighbors:{type:"number",description:"Override the default neighbor count (env EVOLUTION_K_NEIGHBORS or 5). Capped at 20."},dry_run:{type:"boolean",description:"When true, run the LLM judgments but skip link creation, archival, and activity logging. Returns the proposals so you can preview before committing. Default false."}},required:["id"]}},{name:"ctx_chunks",description:"List the block-level chunks for a context, ordered by chunk_index. Each chunk includes its heading_path, content, and has_embedding flag (false while the embedding job is still pending). Use this to inspect how a context was split for chunked retrieval, debug per-passage matches surfaced by ctx_search, or check embedding coverage before relying on semantic ranking.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID whose chunks you want to list"}},required:["id"]}},{name:"ctx_code_refs",description:"Find every file that imports or calls a given source file. Returns the set of referencing files (with link type and callee-symbol hint) from the code graph stored in context_links. Use this instead of grep to answer 'what references X' \u2014 it reads from the pre-built graph rather than scanning the repo. `ref` can be a repo-relative file path (e.g. 'src/services/search.service.ts') or a numeric context id. Returns 404 when the file has not been indexed.",inputSchema:{type:"object",properties:{ref:{type:"string",description:"Repo-relative file path (e.g. 'src/services/context.service.ts') or numeric context id. The path is matched against the indexed external_id suffix, so partial trailing paths work as long as they are unambiguous within the tenant."}},required:["ref"]}},{name:"ctx_code_trace",description:"Trace the directional call chain FROM a given source file via BFS over CALLS edges in the code graph. Returns nodes and edges so you can reconstruct the call path. Use this to answer 'what does X call, and what do those files call' up to N levels deep \u2014 reads from the pre-built graph, not file content. `ref` can be a file path or numeric context id. `depth` controls BFS hops (default 2, max 3). Returns 404 when the file has not been indexed.",inputSchema:{type:"object",properties:{ref:{type:"string",description:"Repo-relative file path (e.g. 'src/services/search.service.ts') or numeric context id to start the trace from."},depth:{type:"number",description:"How many CALLS hops to follow. Default 2, max 3. The service clamps the value \u2014 passing 4 is treated as 3."}},required:["ref"]}},{name:"ctx_provenance_get",description:"Walk the belief provenance chain backward from a context (T358): who/what produced it, and what it was derived from. BFS over context_provenance.source_context_ids, same traversal shape as ctx_code_trace. Returns { root: {id, name, epistemicStatus}, nodes: [{id, name, epistemicStatus}], edges: [{contextId, sourceContextId, transformType, producerId, modelId, promptHash, createdAt}], depth, truncated }. transformType is one of manual/agent_write/ingest/dream_synthesis/evolve. epistemicStatus is one of observed/told/inferred/assumed (weakest->strongest: assumed < inferred < told < observed) \u2014 use this to judge how much to trust a fact and its ancestors. Use this to answer 'where did this claim come from' or 'what fed into this dream synthesis'. Returns 404 when the context does not exist or isn't visible to the caller.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID to walk provenance backward from"},depth:{type:"number",description:"How many hops to follow back through source_context_ids. Default 5, max 5."}},required:["id"]}},{name:"ctx_confidence_get",description:"Get a context's current belief confidence (T359): a continuously-recomputed [0,1] score, distinct from epistemicStatus (a coarse write-time tag) and context_links.confidence (a static per-edge score). This one moves over time \u2014 corroborating derived_from links raise it, contradicts/supersedes links against it lower it, weighted by the linking source context's own confidence, and it slowly decays toward the neutral 0.5 prior if the context goes stale without new evidence. Returns { contextId, confidence, confidenceUpdatedAt, evidenceCount }. Use this to judge how much to trust a specific fact right now, as opposed to ctx_belief_history which shows how that trust got here. Returns 404 when the context does not exist or isn't visible to the caller.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID to look up confidence for"}},required:["id"]}},{name:"ctx_belief_history",description:"Get the append-only ledger of every confidence change applied to a context (T359): each entry records the triggering link, a human-readable reason, and the old/new confidence values. Use this to audit WHY a context's belief confidence is what it is right now (see ctx_confidence_get for the current value) \u2014 e.g. 'why do we no longer trust this note' or 'what corroborated this claim'. Returns { results: [{id, contextId, triggeringLinkId, reason, oldConfidence, newConfidence, createdAt}] }, newest first. Returns an empty results array when the context isn't visible to the caller or has no recorded events yet.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID to fetch belief history for"},limit:{type:"number",description:"Max events to return, newest first. Default 50, max 200."}},required:["id"]}},{name:"ctx_blast_radius",description:"Compute the blast radius of a context BEFORE trusting a correction to it (T362): 'what breaks if this context turns out to be wrong'. Bounded BFS over context_links following derived_from/supersedes/implements edges downstream (same traversal shape as ctx_code_trace/ctx_provenance_get), plus a structural join against goal_nodes and a best-effort match against agent_lessons that reference the context. The exact same fan-out count gates whether the server's own automated supersede/archive/contradict decisions (memory-evolution, T352 contradicts links) auto-apply or are held for manual review \u2014 call this first when you're about to believe or act on a correction to a context with many dependents. Returns { contextId, contextName, totalFanOut, threshold, exceedsThreshold, depthReached, truncated, contextLinkCount, goalNodeCount, agentLessonCount, topNodes: [{id, name, kind, linkType, depth, fanOutScore}] }. `topNodes` is ranked highest-impact first. Returns 404 when the context does not exist or isn't visible to the caller.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The context entry ID to compute blast radius for"},depth:{type:"number",description:"Max BFS hops to follow downstream. Default/max 3."},top:{type:"number",description:"Max number of top-impact nodes to return. Default 10, max 50."}},required:["id"]}},{name:"ctx_graph_search",description:"Entity-anchored fact search over the temporal knowledge graph. Resolves entities mentioned in the query (name/alias match, no LLM call), walks entity_edges 1-2 hops, and returns ranked facts with provenance (which context each fact came from) and validity (current vs. historical/invalidated). Use this to answer relationship questions ('who does X work for', 'what depends on Y') that plain text search can't reliably surface. Pass `as_of` for a point-in-time view of what was believed true at that date. Returns empty arrays (not an error) when no entities in the query resolve or the graph has no data yet.",inputSchema:{type:"object",properties:{query:{type:"string",description:"Free-text query naming one or more entities and/or a relationship, e.g. 'who does Carol Vu work for'."},as_of:{type:"string",description:"Optional ISO 8601 timestamp. Returns facts as they were believed true at this point in time, including facts since invalidated. Omit for the current state."},limit:{type:"number",description:"Max provenance context ids to return, default 20, max 50."}},required:["query"]}},{name:"ctx_entity_get",description:"Fetch an entity card from the knowledge graph: the entity's identity (name, aliases, type, summary), its current edges (facts currently believed true), and its full timeline (every fact ever extracted about it, including ones since invalidated/superseded, newest first). Use this to look up everything the graph knows about a specific entity id. Returns 404 when the entity does not exist under the authenticated tenant.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Numeric entity id (from an entities row, or the sourceEntityId/targetEntityId on a fact returned by ctx_graph_search)."},as_of:{type:"string",description:"Optional ISO 8601 timestamp. Scopes the 'edges' field (not the timeline, which always shows full history) to what was valid at this point in time."}},required:["id"]}},{name:"ctx_mocs",description:"List Maps of Content (MOCs) for the tenant. MOCs are curated index notes that summarize and link to clusters of related contexts, providing a navigation layer over the knowledge graph. Use this to discover existing high-level views before creating new synthesis notes.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Optional max results to return."}},required:[]}},{name:"ctx_regenerate_mocs",description:"Regenerate Maps of Content by re-clustering the tenant's contexts and synthesizing one MOC per cluster via LLM. Expensive (runs an LLM call per cluster) \u2014 call sparingly, typically after a substantial batch of new contexts has been added. Requires admin scope.",inputSchema:{type:"object",properties:{},required:[]}},{name:"ctx_maps",description:"List memory maps (Dream-generated multi-shape navigation TOCs) for the caller's tenant. Optionally filter by workspace / project slug. Each row carries `{id, shape, version, generatedAt, userEdited, scope}` - fetch the full map_payload via /api/maps/:id (not wrapped here yet to keep this surface small). Useful when listing or summarising available maps to an AI agent.",inputSchema:{type:"object",properties:{workspace:{type:"string",description:"Filter by workspace slug (optional)."},project:{type:"string",description:"Filter by project slug (optional)."}},required:[]}},{name:"ctx_saved_searches_list",description:"List all saved searches visible to the caller (their own + tenant-shared). Saved searches are named, reusable retrieval views (Dataview-style) that bundle a query string with filters so agents/sessions can rerun them without re-typing the criteria.",inputSchema:{type:"object",properties:{},required:[]}},{name:"ctx_saved_searches_create",description:"Create a new saved search. Supply a unique name (per owner within the tenant), an optional description, the search query string, and the filter blob. Set shared_with_tenant=true to let other users in the tenant see and run it.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Unique name for the saved search (per owner within the tenant)"},description:{type:"string",description:"Optional one-line summary"},query:{type:"string",description:"Search query (full-text + semantic). May be empty for filter-only views."},filters:{type:"object",description:"Filter blob \u2014 accepts the same fields as ctx_search (workspace, project, type, tags, scope, lifecycle, useMap, includeMaps, includeArchived, lifecycleBoost, chunkSearch, limit, offset). Unknown keys are stripped server-side."},shared_with_tenant:{type:"boolean",description:"When true, other users in the tenant can read and run (but not delete) this saved search. Default false."}},required:["name"]}},{name:"ctx_saved_searches_get",description:"Fetch a single saved search by id. Returns 404 if it does not exist or the caller cannot see it (different tenant, or owned by another user and not shared).",inputSchema:{type:"object",properties:{id:{type:"number",description:"The saved search id"}},required:["id"]}},{name:"ctx_saved_searches_update",description:"Update an existing saved search. Owner can update their own searches; admins/owners can also update tenant-shared searches owned by others. Only the provided fields are changed.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The saved search id"},name:{type:"string",description:"New name"},description:{type:"string",description:"New description"},query:{type:"string",description:"New query string"},filters:{type:"object",description:"Replacement filter blob (same shape as create)"},shared_with_tenant:{type:"boolean",description:"Toggle tenant-wide visibility"}},required:["id"]}},{name:"ctx_saved_searches_delete",description:"Delete a saved search by id. Owner only \u2014 even tenant-shared searches can only be deleted by their original owner.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The saved search id to delete"}},required:["id"]}},{name:"ctx_saved_searches_run",description:"Execute a saved search by id and return the same shape as ctx_search. Optional limit/offset/query overrides apply at run time without modifying the persisted view. Use this when you already know the saved search id.",inputSchema:{type:"object",properties:{id:{type:"number",description:"The saved search id"},query:{type:"string",description:"Optional query override. If omitted, the persisted query is used."},limit:{type:"number",description:"Optional limit override"},offset:{type:"number",description:"Optional offset override"}},required:["id"]}},{name:"ctx_run_saved_search",description:"Convenience: resolve a saved search by name (caller's own row preferred, falling back to a tenant-shared row with the same name) and execute it. Use this when an agent has the human-readable name but not the id.",inputSchema:{type:"object",properties:{name:{type:"string",description:"The saved search name to resolve and run"},query:{type:"string",description:"Optional query override"},limit:{type:"number",description:"Optional limit override"},offset:{type:"number",description:"Optional offset override"}},required:["name"]}},{name:"ctx_ingest",description:`Ingest raw material (URL or inline content) and turn it into atomic claims that get diff'd against the existing knowledge base. The server fetches/parses the source, extracts claims via LLM, runs kNN against the tenant + workspace scope, and decides create / update / archive per claim. Set dry_run=true to preview the plan without persisting any changes \u2014 recommended for the first call against a new source.
|
|
5
|
+
|
|
6
|
+
Wave 13b: large or URL-sourced ingests run async \u2014 the response is { jobId, statusUrl } and you must poll ctx_ingest_status (or GET /api/ingest-jobs/:id) until status='succeeded' or 'failed' to read the breakdown. Small inline ingests still return the full result inline. Pass async=true to force the async path explicitly.`,inputSchema:{type:"object",properties:{source:{type:"object",description:"Raw material to ingest. Provide either url (fetched server-side, capped at 2 MB / 15 s) OR content (inline text). 'label' is a short human-readable identifier recorded on the activity log (defaults to the URL or 'inline').",properties:{url:{type:"string",description:"HTTP(S) URL to fetch and parse. HTML is stripped to plain text; markdown / plain text / JSON are passed through."},content:{type:"string",description:"Inline source content (markdown, plain text, transcript). Use when the agent already has the document in memory."},label:{type:"string",description:"Optional short label (filename, doc title, ticket ID) recorded on the audit log."}}},workspace:{type:"string",description:"Workspace identifier the ingested claims belong to. Required for tenant + workspace scoped diff."},project:{type:"string",description:"Optional project identifier within the workspace."},dry_run:{type:"boolean",description:"When true, run the full extract + diff pipeline but skip every DB write (no create / update / archive / activity log). The response still lists what *would* happen so the agent can preview before committing. Default false."},max_claims:{type:"number",description:"Cap on claims extracted from the source. Default 10, hard max 25. Lower this for noisy sources where you only want the top few facts."},async:{type:"boolean",description:"Force the async path (true) or sync path (false). Omit to let the server auto-pick \u2014 URL sources and inputs with max_claims > INGEST_ASYNC_THRESHOLD (default 5) run async; everything else runs inline. Async responses are { jobId, statusUrl }; sync responses are the full IngestResult."}},required:["source","workspace"]}},{name:"ctx_ingest_status",description:"Fetch the current status of an async background job. Generic poller (T497) \u2014 use this for the job id returned by ctx_ingest, ctx_remember, OR ctx_dream when any of them took the async path, until status='succeeded' or 'failed'. Returns counters (total_items, processed_items, failed_items), timestamps (created_at, started_at, completed_at), and on success the full result_summary (shape depends on which tool started the job: ctx_ingest/ctx_remember's created/updated/archived/skipped arrays, or ctx_dream's dream result).",inputSchema:{type:"object",properties:{job_id:{type:"number",description:"The job id returned by ctx_ingest, ctx_remember, or ctx_dream when the async path was taken."}},required:["job_id"]}},{name:"ctx_remember",description:`Extract durable memories from a raw multi-turn conversation and save them as deduped atomic contexts. Turn-aware sibling of ctx_ingest: the server builds a speaker-attributed transcript, extracts only durable facts/preferences via LLM (skipping chit-chat), and runs the claims through the SAME kNN-dedup + diff + create/update/archive pipeline ctx_ingest uses. Pass subjectId to scope memories to a single end-user of your application (Mem0-parity user_id) \u2014 dedup then only considers that subject's own prior memories, and every created context is tagged with that subjectId so ctx_search (subjectId param) and GET /api/memory can retrieve it later. Set dryRun=true to preview without persisting.
|
|
7
|
+
|
|
8
|
+
Long conversations run async \u2014 the response is { jobId, statusUrl } and you must poll ctx_ingest_status (or GET /api/ingest-jobs/:id) until status='succeeded' or 'failed'. Short conversations return the full result inline. Pass async=true/false to force a path explicitly.`,inputSchema:{type:"object",properties:{messages:{type:"array",items:{type:"object",properties:{role:{type:"string",description:"Speaker role, e.g. 'user', 'assistant', 'system'."},content:{type:"string",description:"Turn content."},name:{type:"string",description:"Optional speaker identifier (e.g. a specific agent name in a multi-agent transcript)."}},required:["role","content"]},description:"Conversation turns in chronological order."},subjectId:{type:"string",description:"End-user identity this conversation belongs to (Mem0-parity user_id). Scopes dedup and tags every created context so it can be retrieved later via ctx_search subjectId or GET /api/memory."},agentSlug:{type:"string",description:"Optional identifier of the agent that produced/consumed this conversation. Recorded as metadata only."},workspace:{type:"string",description:"Workspace identifier the extracted memories belong to. Falls back to the request's active scope when omitted."},project:{type:"string",description:"Optional project identifier within the workspace."},sessionId:{type:"string",description:"Optional conversation/session identifier. Recorded as metadata and on the audit row only."},dryRun:{type:"boolean",description:"When true, run the full extract + diff pipeline but skip every DB write. Default false."},maxClaims:{type:"number",description:"Cap on claims extracted from the conversation. Default 10, hard max 25."},async:{type:"boolean",description:"Force the async path (true) or sync path (false). Omit to let the server auto-pick \u2014 conversations longer than MEMORY_ASYNC_THRESHOLD messages (default 8) run async."}},required:["messages"]}},{name:"ctx_events_recent",description:"List the most recent context events (created / updated / archived / linked) from the in-memory ring buffer. Tenant-scoped \u2014 only events for the caller's tenant are returned. Useful for downstream agents that want to react to writes; the ring is capped at 200 envelopes server-side. Note: streaming subscriptions are HTTP-only via GET /api/events (Server-Sent Events) \u2014 MCP does not expose a streaming variant.",inputSchema:{type:"object",properties:{topic:{type:"string",enum:["context.created","context.updated","context.archived","context.linked"],description:"Optional single-topic filter. Omit to receive every topic."},limit:{type:"number",description:"Max events to return (default 50, max 200)."}},required:[]}},{name:"ctx_memory_review_logs",description:"List recent memory-review verdicts (keep / retag / merge / archive / contradict / error). Each row records one LLM-judged review of a context during the periodic review-for-correctness ritual; verdicts are advisory unless 'archive' was auto-applied. Use this to audit what the reviewer has flagged and triage retag/merge/contradict candidates by hand. Superadmin scope only.",inputSchema:{type:"object",properties:{tenant_id:{type:"number",description:"Optional tenant ID filter. Omit to list across all tenants."},limit:{type:"number",description:"Max rows to return (default 100, max 500)."}},required:[]}},{name:"ctx_contradictions_list",description:"List contradictions detected by the proactive contradiction hunter (T360) for the caller's tenant. Each row is a high-similarity context pair the LLM classifier judged as disagreeing, with severity, rationale, whether the T352 dispute flip actually applied or was held by the T362 blast-radius gate, and (when applied) the context_links id. Complements ctx_link (which disputes explicitly) -- this surfaces disagreements the hunter found on its own. Resolve via the existing POST /api/contexts/:id/resolve-contradiction route (ctx_get the disputed context id first if unclear which side is 'target').",inputSchema:{type:"object",properties:{workspace:{type:"string",description:"Optional workspace slug filter. Omit to use the active scope, or list across all workspaces if none is set."},status:{type:"string",enum:["open","resolved","dismissed"],description:"Filter by queue status (default 'open')."},limit:{type:"number",description:"Max rows to return (default 50, max 200)."}},required:[]}},{name:"ctx_admin_rate_limit_get",description:"Inspect the in-memory per-tenant rate-limit buckets. Returns one row per tenant currently tracked by the limiter (tenant_id, tenant_slug, tokens remaining, capacity, refill_per_min). The bucket map lives in process memory, so the snapshot reflects only the API instance that handled the request. Useful for ops triage when a tenant is reporting 429s. Superadmin scope only.",inputSchema:{type:"object",properties:{},required:[]}},{name:"ctx_admin_rate_limit_set",description:"Override the per-tenant rate limit. Patches `tenants.settings.rate_limit` with the provided fields and invalidates the cached bucket so the new config takes effect on the tenant's next request. Defaults are 600 capacity / 600 refill per minute. At least one of capacity or refill_per_min must be provided. Superadmin scope only.",inputSchema:{type:"object",properties:{tenant_id:{type:"number",description:"Tenant ID to override."},capacity:{type:"number",description:"Maximum tokens the bucket can hold (positive integer)."},refill_per_min:{type:"number",description:"Tokens added back per minute (positive number)."}},required:["tenant_id"]}},{name:"ctx_memory_review_run",description:"Manually trigger a one-shot memory review pass for a tenant. Samples a handful of older + well-connected contexts (default 5, env MEMORY_REVIEW_SAMPLE_SIZE), asks the LLM to verdict each, and persists results to memory_review_logs. Returns a summary with per-verdict counters and how many archives were auto-applied (only when LLM confidence > 0.8). Useful for ad-hoc audits without waiting for the scheduler. Superadmin scope only.",inputSchema:{type:"object",properties:{tenant_id:{type:["number","null"],description:"Tenant to review. Pass null to review tenant-less contexts. Omit to default to the caller's tenant."}},required:[]}},{name:"ctx_audit_cleanup_run",description:"Manually trigger one pass of the audit log retention cleaner. Deletes rows from activity_logs whose created_at is older than retention_days (capped at the AUDIT_LOG_RETENTION_DAYS env default when omitted; floor of 7 days is always enforced server-side to prevent nuking recent history). Single call deletes at most 5000 rows; rerun if more remain. Returns { deleted, retention_days }. Superadmin scope only.",inputSchema:{type:"object",properties:{retention_days:{type:"number",description:"Override the env-configured retention window for this single run. Server enforces a minimum of 7 days. Omit to use AUDIT_LOG_RETENTION_DAYS (default 90)."}},required:[]}},{name:"ctx_health",description:"Run the deep health probe and return the full report. Probes DB, Elasticsearch, embedding provider, LLM provider, and scheduler states. 30-second in-memory cache on the server. Returns `{status, components, schedulers, queue}` - status is `ok|degraded|fail`. Useful for ad-hoc prod health checks from MCP clients.",inputSchema:{type:"object",properties:{},required:[]}},{name:"ctx_audit_chain_status",description:"Verify the per-row sha256 chain of activity_logs. Returns `{ok: true, total_checked}` when intact, or `{ok: false, broken_at_id, total_checked}` when a row's prev_hash mismatches the previous row's row_hash. Accepts `prev_hash=''` as valid chain-restart genesis (see migration 0029 + PR #157). Superadmin scope only.",inputSchema:{type:"object",properties:{},required:[]}},{name:"ctx_admin_queue_stats",description:"Inspect the in-process LLM job queue. Returns `{pending, running, completed, failed, rejected, capacity:{concurrency, maxPending}, enabled}`. Counters reflect a single API instance because the queue is in-process. Use after a burst of writes or 429s from a provider to confirm the queue is not saturated. Superadmin scope only.",inputSchema:{type:"object",properties:{},required:[]}},{name:"agent_boot",description:'Boot an autonomous agent: ONE token-budgeted call returning everything needed to start or resume work. Call this FIRST in any agent run. Returns {agent, session:{...,role}, resume:{checkpoint_summary, open_tasks}, handoff:{tldr, source}, lessons:[], facts:[], brief, skills:[], skills_full, repo_map, siblings:[], budget:{limit, used, dropped}, client}. If a non-terminal session exists for this agent (or session_id is given), `resume` tells you exactly where you left off; `handoff` is the best-ranked latest handoff (a hand-written wrap SEED first). `goal` drives the facts, lessons AND skills retrieval. `skills` are parametrized procedures distilled from verified past runs matching the goal ({context_id, name, description, success_count, similarity}) -- check them BEFORE re-deriving a solution. On a session\'s later boots `skills` holds only new or changed entries and `skills_full` is false (empty then means nothing new, not no skills); pass `full:true` for the complete set. With `project_id` you also get a goal-graph `brief` {north_star, role, lane, next, blocked_on, blocking, done} and the session role is inferred from the matched goal node. With `include_repo_map:true` on a code-indexed workspace, `repo_map` carries top-ranked file signatures to answer "where is X handled" without grepping. `siblings` lists other active sessions in this workspace (last ~60 min, max 5) so you can coordinate via relay_* before touching shared resources. Slots fill resume > handoff > lessons > facts > brief; overflow is reported in budget.dropped. Trigger: resume or catch up on tracked work ("h\xF4m tr\u01B0\u1EDBc t\u1EDBi \u0111\xE2u", "ti\u1EBFp g\xEC", "t\xF3m l\u1EA1i \u0111ang l\xE0m g\xEC", "what\'s next", "resume", "where did we leave off", "catch me up"). Skip for unrelated casual questions.',_meta:{"anthropic/alwaysLoad":!0},inputSchema:{type:"object",properties:{agent:{type:"string",description:"Stable agent slug (handle the agent boots with every run, e.g. 'claude-code')"},agent_name:{type:"string",description:"Human-readable name; used only when the agent is first created"},goal:{type:"string",description:"The objective for this run \u2014 drives relevant-facts + lessons retrieval AND goal-node role inference"},workspace:{type:"string",description:"Workspace slug to scope handoff + facts to"},project_id:{type:"number",description:"Project id to scope the goal-graph situation brief + role inference to (omit = no brief, classic pack)"},token_budget:{type:"number",description:"Max tokens for the assembled pack (default 4000)"},session_id:{type:"number",description:"Resume a specific session by id (otherwise the latest active/paused session for this agent)"},include_repo_map:{type:"boolean",description:"When true, include a token-budgeted repo map (entries with path+signatures) from code-indexed contexts. Only useful for workspaces indexed with contextq index. Default false."},repo_map_token_budget:{type:"number",description:"Token cap for the repo map slot (default ~2000, range 100-16000). Ignored when include_repo_map is false."},epistemic_min:{type:"string",enum:["observed","told","inferred","assumed"],description:"Epistemic floor (T358) for the FACTS slot: only surface facts at or above this confidence tier (weakest->strongest: assumed < inferred < told < observed). Omit for no floor."},full:{type:"boolean",description:"T506: bypass the skills diff-since-last-boot behavior and always return the full current skills match set. Default false (repeat boots of the same session return only new/changed skills)."}},required:["agent"]}},{name:"agent_session_start",description:"Start a new agent session (a run with a goal). Returns the created session including its id. Use when beginning a fresh task that you want to track and resume. Pass parent_session_id to chain a resumed run to its predecessor.",inputSchema:{type:"object",properties:{agent:{type:"string",description:"Stable agent slug"},agent_name:{type:"string",description:"Human-readable name (used only on first creation)"},goal:{type:"string",description:"The A-Z objective for this run"},workspace:{type:"string",description:"Workspace slug this run operates in"},project:{type:"string",description:"Optional project slug within the workspace"},role:{type:"string",description:"Optional role this session plays (frontend, backend, design, ...). Usually inferred at boot from the matched goal node instead."},parent_session_id:{type:"number",description:"Id of the session this one resumes/continues"},metadata:{type:"object",description:"Arbitrary run metadata"}},required:["agent"]}},{name:"agent_session_end",description:"End or update an agent session's status. Use status='completed' when the goal is met, 'paused' to suspend (resume later from the checkpoint), 'stalled' when the vibe-loop stall detector trips, or 'abandoned' to drop the run. Setting completed/abandoned stamps ended_at.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"Session id to update"},status:{type:"string",enum:["active","paused","completed","stalled","abandoned"],description:"New session status"},goal:{type:"string",description:"Optionally revise the goal"},metadata:{type:"object",description:"Metadata to merge into the session"}},required:["session_id","status"]}},{name:"agent_checkpoint",description:"Snapshot the agent's working state so a restart/crash can resume from exactly here. `state` is an arbitrary JSON scratchpad (cursor, partial results, plan, open files). `summary` is a 1-line 'where I am'. Returns the checkpoint with its monotonic seq. Call periodically after each chunk of progress.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"Session id to checkpoint"},state:{type:"object",description:"Working-state scratchpad (arbitrary JSON)"},summary:{type:"string",description:"One-line human-readable 'where I am'"},token_estimate:{type:"number",description:"Optional explicit token size of the state (auto-estimated if omitted)"}},required:["session_id"]}},{name:"agent_context_pressure",description:"Advisory check: compares your estimated_tokens (and optionally turn_count) for this session against a server-side threshold and returns {should_checkpoint, reason, threshold}. Purely advisory \u2014 no server-side token accounting is kept, nothing is persisted. When should_checkpoint is true, call agent_checkpoint next to snapshot your working state before continuing.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"Session id to evaluate"},estimated_tokens:{type:"number",description:"Your own estimate of accumulated context tokens for this run"},turn_count:{type:"number",description:"Optional: number of turns/steps taken so far this run"}},required:["session_id","estimated_tokens"]}},{name:"agent_resume",description:"Read the resume bundle for a session WITHOUT booting fresh: latest checkpoint, open tasks (pending/in_progress/blocked), and goal-relevant lessons. Use when you already know the session_id and just need to reload where you left off.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"Session id to resume"}},required:["session_id"]}},{name:"agent_task_upsert",description:"Create or update one checklist item in a session's task tree. Omit task_id to create; pass task_id to update. `verify_cmd` names HOW the item is proven done (the agent must run it before ticking). Use parent_task_id for subtasks. This productizes the vibe goal-file checklist. Trigger: call at the START of a tracked piece of work to record a checklist item (session-scoped \u2014 for a task meant to persist across sessions use goal_add on the board instead). Only call when the work is actually being tracked; ignore unrelated casual chat.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"Session that owns this task"},task_id:{type:"number",description:"Existing task id to update (omit to create)"},parent_task_id:{type:"number",description:"Parent task id for a subtask"},title:{type:"string",description:"Task title (required when creating)"},status:{type:"string",enum:["pending","in_progress","verified","blocked","skipped"],description:"Task status"},verify_cmd:{type:"string",description:"Command/observation that proves this task done"},order_index:{type:"number",description:"Ordering within the session"},context_id:{type:"number",description:"Optional id of the durable context this task produced"},goal_node_id:{type:"number",description:"Optional goal-graph node this task rolls up to (links session work to the project goal)"}},required:["session_id"]}},{name:"agent_task_tick",description:`Flip a task's status. Setting status='verified' REQUIRES non-empty \`evidence\` (real observed output: test result, HTTP status, exit code) \u2014 the no-self-certification rule. Returns 400 if you try to verify without evidence. Use this as each checklist item is proven. Trigger: user reports finishing a piece of tracked work ("xong r\u1ED3i", "xong X", "done X", "done", "mark done", "finished X") \u2014 tick the matching session-scoped checklist item here (use goal_advance instead for a board-level task). Only call when it maps to a tracked item; ignore unrelated casual chatter.`,inputSchema:{type:"object",properties:{task_id:{type:"number",description:"Task id to tick"},status:{type:"string",enum:["pending","in_progress","verified","blocked","skipped"],description:"New status"},evidence:{type:"string",description:"Real observed output proving the task (required to set 'verified')"}},required:["task_id","status"]}},{name:"agent_lesson_add",description:"Record a lesson learned during a run so the agent doesn't repeat the failure. Embedded for goal-relevant recall at the next agent_boot. Mirrors the vibe-loop '## Lessons' log. scope controls breadth: 'session' (this run), 'agent' (this agent always), or 'workspace'.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"Session this lesson came from"},what_failed:{type:"string",description:"What was attempted that failed"},why:{type:"string",description:"Why it failed"},try_instead:{type:"string",description:"What to do differently next time"},scope:{type:"string",enum:["session","agent","workspace"],description:"How broadly the lesson applies (default 'session')"}},required:["session_id","what_failed"]}},{name:"agent_handoff",description:"Generate a handoff document for the session's workspace at run end (wraps the dream handoff generator \u2014 LLM-synthesized TL;DR + in-progress + next-steps + open-questions). Links the handoff context back to the session. Pass complete=true to also mark the session completed. Requires an LLM provider configured on the server.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"Session to generate a handoff for"},workspace:{type:"string",description:"Workspace slug (falls back to the session's workspace)"},project:{type:"string",description:"Optional project slug to narrow the handoff"},since_days:{type:"number",description:"Look-back window in days (default 7)"},dry_run:{type:"boolean",description:"Generate without persisting the handoff context"},complete:{type:"boolean",description:"Also set the session status to 'completed'"}},required:["session_id"]}},{name:"agent_handoff_save",description:'Save a handoff note you wrote yourself, keyed on external_id so re-running UPDATES the same entry instead of forking a duplicate. Unlike agent_handoff this needs no LLM provider and no agent session \u2014 use it when the handoff text already exists and must be persisted verbatim. Trigger: "wrap up", "save the handoff", "ch\u1ED1t session", "ghi handoff".',inputSchema:{type:"object",properties:{external_id:{type:"string",description:"Stable identifier for this handoff; re-using it updates the same entry"},title:{type:"string",description:'Short title, e.g. "handoff: <repo> <date>"'},body:{type:"string",description:"Full handoff text: decisions, in-progress work, next steps"},description:{type:"string",description:"One-line summary used for search ranking"},workspace:{type:"string",description:"Workspace slug to write into; defaults to the active scope, then GLOBAL"},tags:{type:"array",items:{type:"string"},description:"Tags for categorization and filtering"},metadata:{type:"object",description:"Arbitrary key-value metadata"},project:{type:"string",description:"Project identifier this handoff belongs to. Sets the indexed `project` column that ctx_list and ctx_search filter on -- pass it, or the row is only findable by full-corpus search."},memoryKind:{type:"string",enum:[...g],description:"Taxonomy override. Omit to let the backend classify it from the 'handoff' type."}},required:["external_id","title","body"]}},{name:"agent_replay",description:"Reconstruct the ordered tool-call sequence for a session (T375 episodic trace). Every tool call this MCP bridge makes is logged fire-and-forget with a one-line LLM-compressed summary; agent_replay returns them in order (seq, tool_name, args, result, duration_ms, summary), optionally bounded to [from_seq, to_seq]. Use this to answer 'what did I actually do in this session' \u2014 checkpoints and lessons are lossy summaries, this is the full record. Also feeds ctx_search when include_trace_events is set on a search request ('what did I try before this worked?').",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"Session id to replay"},from_seq:{type:"number",description:"First seq to include (default 1)"},to_seq:{type:"number",description:"Last seq to include (default: latest)"}},required:["session_id"]}},{name:"agent_skill_suggest",description:"Find already-distilled, parametrized procedures matching a goal (T376). A scheduled job clusters repeated, independently-verified agent_task successes (by goal similarity + tool-call-sequence shingling) into type='procedure' contexts with literal args lifted to {{placeholders}} and lineage back to their source sessions/tasks. Returns {suggestions:[{context_id, name, description, success_count, similarity}]} \u2014 fetch the full procedure with ctx_get(context_id) before starting work on a matching goal, instead of re-deriving a solution from scratch. `similarity` is null when no embedding provider is configured (fallback ranks by success_count/recency instead of semantic match). Also surfaced proactively in agent_boot's `skills` slot \u2014 call this directly when you want to re-check mid-run or narrow to a specific workspace.",inputSchema:{type:"object",properties:{goal:{type:"string",description:"The objective to find a matching distilled procedure for"},workspace:{type:"string",description:"Optional workspace slug to scope suggestions to"},limit:{type:"number",description:"Max suggestions to return (default 3, max 10)"}},required:["goal"]}},{name:"agent_identity_attest",description:"Record a model-upgrade for an agent's durable identity (T373). Call this whenever the model backing an agent is swapped (e.g. moving to a newer Claude release) so continuity is provable: appends one hash-chained transition to identity_transitions, linked to the previous transition's row_hash (or '' for the first-ever transition on this identity). Also snapshots a sha256 hash of the REAL agent_boot payload immediately before and after the swap (bootpack_hash_before/after) \u2014 an unchanged hash proves memory survived the swap intact. Returns {identity, transition}.",inputSchema:{type:"object",properties:{agent:{type:"string",description:"Stable agent slug whose identity is being attested"},agent_name:{type:"string",description:"Human-readable name; used only when the agent is first created"},workspace:{type:"string",description:"Workspace slug used to assemble the before/after boot-pack snapshot"},new_model_id:{type:"string",description:"The model id now running this agent (e.g. 'claude-sonnet-5-...')"}},required:["agent","new_model_id"]}},{name:"agent_identity_verify",description:"Walk an agent's identity_transitions chain from genesis (seq=1) to the latest transition, verifying every prev_hash links to the previous row's row_hash AND every row_hash matches a fresh recompute from current content. Returns {ok:true, total_checked, last_seq} when intact, or {ok:false, broken_at_seq, total_checked, last_seq, reason:'chain_broken'|'row_hash_mismatch'} on the first failure. Use this to prove an agent's identity continuity across any number of model swaps.",inputSchema:{type:"object",properties:{agent:{type:"string",description:"Stable agent slug whose identity chain to verify"}},required:["agent"]}},{name:"agent_intention_set",description:"Set a prospective memory intention (T374) \u2014 'remember to remember'. Stores a future trigger paired with a payload context to resurface once it fires. trigger_type='event' fires when a matching action/resource is logged (synchronously, off every activity_logs write); 'condition' is evaluated on a periodic poll (currently supports kind='context_count_gte'); 'time' fires at/after a timestamp. When the trigger matches, the payload context surfaces in the NEXT matching agent_boot's push slot with reason='prospective_trigger' \u2014 a guaranteed delivery, not a relevance-gated suggestion. Pass agent to scope delivery to one agent's boots only (omit for any agent in the tenant). Pass expires_at so a stale intention never fires past its window. Returns the created intention row.",inputSchema:{type:"object",properties:{agent:{type:"string",description:"Scope delivery to one agent's boots only (omit = any agent in the tenant)"},trigger_type:{type:"string",enum:["event","condition","time"],description:"How this intention fires"},trigger_spec:{type:"object",description:"Trigger definition; shape depends on trigger_type. event: {action?, actionPrefix?, resourceContains?} (at least one of action/actionPrefix required) \u2014 matched (AND semantics) against every logged activity's action/resource. condition: {kind:'context_count_gte', workspace?, count} \u2014 polled periodically. time: {fireAt: ISO datetime string} \u2014 fires at/after this timestamp."},payload_context_id:{type:"number",description:"The context id to resurface once the trigger fires"},reason:{type:"string",description:"Why this intention exists (human-readable, shown in the audit trail)"},expires_at:{type:"string",description:"ISO datetime after which this intention never fires (optional)"}},required:["trigger_type","trigger_spec","payload_context_id"]}},{name:"agent_intention_list",description:"List prospective memory intentions for this tenant, optionally filtered by agent slug (scope) or status (active|fired|delivered|expired|cancelled).",inputSchema:{type:"object",properties:{agent:{type:"string",description:"Filter to intentions scoped to this agent slug"},status:{type:"string",enum:["active","fired","delivered","expired","cancelled"],description:"Filter by status"}},required:[]}},{name:"agent_intention_cancel",description:"Cancel a prospective memory intention before it fires. Only succeeds while the intention is still 'active' \u2014 returns 404 if it doesn't exist or has already fired/delivered/expired/been cancelled.",inputSchema:{type:"object",properties:{intention_id:{type:"number",description:"Intention id to cancel"}},required:["intention_id"]}},{name:"ctx_use",description:"Select the active working scope (workspace + optional project) for this session/key. Subsequent reads default to this scope; writes resolve their target from it. Pass workspace=null (or omit) to clear back to ALL workspaces. Returns the resolved {workspace, project, persisted}. This is 'select the project/context to work with'.",inputSchema:{type:"object",properties:{workspace:{type:"string",description:"Workspace slug to scope to. Omit or null = ALL workspaces."},project:{type:"string",description:"Optional project slug within the workspace."}},required:[]}},{name:"ctx_scope",description:"Show the current active working scope (workspace + project) for this session/key, or 'ALL workspaces' when nothing is selected. Returns {workspace, project}.",inputSchema:{type:"object",properties:{},required:[]}},{name:"goal_set_objective",description:"Set (or fetch) the north-star objective for a project \u2014 the root of the goal graph everything hangs off. Returns the kind='objective' node, which carries an `objective_run_id` \u2014 a run id lazily assigned once and then shared by every subsequent goal_add/goal_advance/goal_link_dep/goal_decompose/goal_import/goal_sync mutation under this objective. Query activity_logs (or goal_list/goal_frontier with objective_run_id) by that value to reconstruct this objective's whole activity story, the same way Claude Code's workflow.run_id reconstructs one workflow run.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project id the goal graph belongs to (optional \u2014 omit for a workspace-level graph)"},title:{type:"string",description:"The objective / north-star statement"},why:{type:"string",description:"Optional rationale (stored as the node's context)"}},required:["title"]}},{name:"goal_add",description:`Add a node to the goal graph. Progressive elaboration: only title is required -- omit parent_id to create a root node (a vague node is created status='draft'); fill the rest as reality reveals it. kind: objective|milestone|goal|work_item|relay. owner_role/status/origin are free strings. Trigger: when a user (including a non-technical one) asks you to remember or hand off a piece of work for later ("th\xEAm vi\u1EC7c", "th\xEAm task", "todo", "add a task", "add to the board"), create a node here with a valid status (draft is fine if details are vague) \u2014 this is how a casual request becomes a durable tracked task. Only call when the request is genuinely about work to track; ignore unrelated casual questions.`,inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project id (optional)"},parent_id:{type:"number",description:"Parent node id (containment tree; null/omit for a root)"},title:{type:"string",description:"Node title (the only hard requirement)"},kind:{type:"string",description:"objective|milestone|goal|work_item|relay (default work_item)"},owner_role:{type:"string",description:"Role that owns this node (e.g. frontend, backend, design)"},status:{type:"string",description:"draft|not_started|ready|in_progress|blocked|done|superseded"},origin:{type:"string",description:"greenfield|leverage|migrate|unknown"},size:{type:"string",description:"S|M|L|XL"},effort_weeks:{type:"number",description:"Estimated effort in weeks"},target_weeks:{type:"number",description:"Milestone target (weeks)"},verify_cmd:{type:"string",description:"How a leaf is proven done"},external_ref:{type:"object",description:"External tracker ref, e.g. {jira: 'FIP-123'}"},content:{type:"string",description:"Long description (creates a searchable contexts row)"},do:{type:"string",description:"One-sentence work order (stored as payload.do)"},session_id:{type:"number",description:"Your agent session id, recorded on the node's history row"},brief:{type:"object",description:"Cold-executor brief: all six keys or omit (a partial brief is rejected). A work_item added without one gets a `hint` with the template.",properties:{intent:{type:"string"},decisions:{type:"string"},pointers:{type:"string"},rejected:{type:"string"},doneWhen:{type:"string"},gotchas:{type:"string"}},required:["intent","decisions","pointers","rejected","doneWhen","gotchas"]}},required:["title"]}},{name:"goal_link_dep",description:"Create a dependency edge between two goal nodes (the DAG that cuts across the containment tree). kind='blocks' (from blocks to) or 'informs'. Rejected with a cycle error if it would make the graph cyclic.",inputSchema:{type:"object",properties:{from_node_id:{type:"number",description:"Source node id"},to_node_id:{type:"number",description:"Target node id"},kind:{type:"string",description:"blocks|informs (default blocks)"}},required:["from_node_id","to_node_id"]}},{name:"goal_advance",description:`Advance a node's status. A leaf moving to 'done' REQUIRES non-empty evidence (real observed output) \u2014 the no-self-certification rule. Parent status rolls up automatically from children. Trigger: when the user reports finishing a tracked piece of work ("xong r\u1ED3i", "xong X", "done X", "done", "mark done", "finished X"), advance the matching board node's status here. Only call when the report maps to a tracked board item; ignore unrelated casual chatter.`,inputSchema:{type:"object",properties:{node_id:{type:"number",description:"Node id to advance"},status:{type:"string",description:"New status (draft|not_started|ready|in_progress|blocked|done|superseded). Always required: to edit a field below WITHOUT a status change, pass the node's CURRENT status."},evidence:{type:"string",description:"Real observed output proving the node (required for a leaf -> done)"},title:{type:"string",description:"Rename the node (manifest task title)"},verify_cmd:{type:"string",description:"Replace the node's done-when text (manifest `done-when:`). Whole-value replace, max 2000 chars."},payload:{type:"object",description:'Shallow-merged into the node\'s payload; `{"do": "<text>"}` is the manifest `do:` line. Keys not named here survive.'},external_ref:{type:"object",description:"Shallow-merged into the node's externalRef. Never trusted as a security predicate."},order_index:{type:"number",description:"Manifest position"},note:{type:"object",description:'Named annotation kept under payload.notes, e.g. {name: "rechecked-2026-09-27", text: "..."}',properties:{name:{type:"string"},text:{type:"string"}},required:["name","text"]},session_id:{type:"number",description:"Your agent session id, recorded on the node's history row"}},required:["node_id","status"]}},{name:"agent_run_start",description:`Open a fan-out RUN and mint the id every agent in it shares. The ORCHESTRATOR calls this once before spawning, then pastes the returned run_id into each agent's prompt alongside the other pinned contracts. Registering the run is what lets an empty findings read mean "nobody has found anything yet" instead of "you were left out". Idempotent: re-opening the same id returns the existing record.`,inputSchema:{type:"object",properties:{run_id:{type:"string",description:"Reuse a specific id. Omit to have one minted."},label:{type:"string",description:"What this fan-out is doing, for a human reading the runs later"},session_id:{type:"number",description:"The orchestrator's own session id, recorded as the opener"}},required:[]}},{name:"agent_finding_add",description:"Record something you learned so the OTHER agents in this fan-out can use it before you finish. Use it the moment you discover a non-obvious behaviour, a wrong turn you ruled out, or a gotcha that cost you time -- not at the end, which is what the handoff is for. A finding is one claim plus the evidence for it, never a session summary. Check `run_known` in the response: false means the run id you were given was never opened, so nobody will read this -- say so rather than continuing quietly.",inputSchema:{type:"object",properties:{run_id:{type:"string",description:"The run every agent in this fan-out shares, as given in your prompt"},session_id:{type:"number",description:"Your agent session id"},title:{type:"string",description:"The claim, in one line"},body:{type:"string",description:"What you actually observed, and where"},workspace:{type:"string",description:"Workspace slug to write into (required -- findings must not default into GLOBAL)"}},required:["run_id","session_id","title","body","workspace"]}},{name:"agent_finding_list",description:"Read what your SIBLING agents have learned so far in this run, newest first. Call it before starting a sub-task and after any surprise, so you do not re-discover what another agent already paid for. Reads exactly, not by search, so a finding written seconds ago is already visible. Pass your own session_id as exclude_session_id to drop the echo of your own writes. An empty list means two different things and `run_known` tells them apart: true = nobody has found anything yet, carry on; false = the run id you hold was never opened, so your own findings reach no one -- report that instead of working on silently.",inputSchema:{type:"object",properties:{run_id:{type:"string",description:"The run to read"},exclude_session_id:{type:"number",description:"Your own session id, to see only what siblings found"},limit:{type:"number",description:"Max findings to return (default 20, max 100)"}},required:["run_id"]}},{name:"goal_claim",description:"Atomically claim a work-item node for your session, so two agents on DIFFERENT MACHINES cannot pick up the same task. Losing is a normal answer, not an error: the response is {claimed:false, heldBy:<session id>} naming who holds it \u2014 act on that, do not retry. Use before starting work on a node from goal_frontier when other agents may be running. Release with goal_release when you stop.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Node id to claim"},session_id:{type:"number",description:"Your agent session id (from agent_boot / agent_session_start)"}},required:["id","session_id"]}},{name:"goal_release",description:"Release a claim your session holds on a node, freeing it for another agent. Conditional on you being the holder: releasing a node you do not hold reports {released:false} rather than failing, and never takes someone else's claim away. Call it when you stop work on a node you did not finish.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Node id to release"},session_id:{type:"number",description:"Your agent session id \u2014 must be the one holding the claim"}},required:["id","session_id"]}},{name:"goal_import",description:"Bulk-import an existing backlog (CSV, e.g. a Jira export) into a living goal tree: parses to nodes, builds the containment tree from epic/phase columns, maps roles from issue type/assignee, and INFERS implicit cross-role dependencies (e.g. an FE task that needs a BE task). Tolerates messy data. Returns {created, deps, nodes}.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project id to import into (optional)"},csv:{type:"string",description:"Raw CSV content"},role_map:{type:"object",description:"Assignee -> role map, e.g. {Nghia:'frontend', Nguyen:'backend'}"}},required:["csv"]}},{name:"goal_decompose",description:"LLM-expand a goal node into child nodes + dependencies, self-checked by a judge before writing (no garbage on fail). `size` is an ADVISORY granularity hint for the LLM only (small=coarse/fewer children, medium=default/today's behavior, large=fine-grained/more children) \u2014 never a hard count enforced by the judge, since decomposition quality beats an exact node count. Returns {proposed, applied, reason, objective_run_id} \u2014 applied children share the parent objective's run id (lazily assigned on first decompose if the objective predates run-id tagging). If no LLM is configured returns applied=false, reason='llm_not_configured' (never errors).",inputSchema:{type:"object",properties:{node_id:{type:"number",description:"Node to decompose"},available_roles:{type:"array",items:{type:"string"},description:"Roles the plan may assign work to"},size:{type:"string",description:"Advisory decomposition granularity: small|medium|large (default medium)"}},required:["node_id"]}},{name:"goal_list",description:`List goal nodes, filtered. Use to read the graph (your lane, a status column, all milestones, or one objective's whole run via objective_run_id). Returns a COMPACT view by default (id, title, status, kind, externalRef.local_id/priority, blocked, depsIn/depsOut) and omits done nodes unless include_done is true or a status filter is given; call goal_get for one node's full payload, or pass view="full".`,inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Filter by project"},owner_role:{type:"string",description:"Filter by owning role"},status:{type:"string",description:"Filter by status"},kind:{type:"string",description:"Filter by kind"},objective_run_id:{type:"string",description:"Filter to nodes stamped with one objective's run id (from goal_set_objective/goal_get/goal_decompose)"},view:{type:"string",enum:["compact","full"],description:"compact (default) drops payload and brief; full returns every field"},include_done:{type:"boolean",description:"Include done nodes (default false)"}},required:[]}},{name:"goal_get",description:"Get one goal node with its why-path (walk up to the objective) and its in/out dependency edges. Returns {node, why_path, deps_in, deps_out, duplicates} \u2014 `duplicates` lists tombstones merged into this node (externalRef.duplicate_of = its local_id); `node.objective_run_id` (and the objective's own entry in why_path) identifies the run whose activity_logs tell this node's whole objective story.",inputSchema:{type:"object",properties:{node_id:{type:"number",description:"Node id"}},required:["node_id"]}},{name:"goal_frontier",description:"The ready-frontier: nodes (optionally for a role, or scoped to one objective_run_id) whose ALL blocking dependencies are done and that aren't done yet \u2014 i.e. 'what can I start NOW'. Returns GoalNode[] with depsIn/depsOut ([{nodeId, kind}]). A node whose externalRef.blocker is set is waiting on a person or an external event, not on a dependency: treat it as not runnable.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Filter by project"},objective_run_id:{type:"string",description:"Scope to one objective's run id"},owner_role:{type:"string",description:"Filter to a role's ready work"}},required:[]}},{name:"relay_open",description:"Open a directed relay (a baton from one role to another, e.g. FE -> BE for an API contract). Built-in kinds: api_contract (frontend->backend), design_handoff (design->frontend); or pass from_role/to_role explicitly. Any role/agent name works immediately, no prior registration or setup call needed -- an unseen role is addressable on its very first relay_open. Born status='open', unclaimed. If your runtime supports cross-session messaging (e.g. Claude Code's ListAgents/SendMessage), consider pinging a plausibly-matching local session right after opening -- relay_inbox stays the source of truth, the ping is just a real-time nudge.",inputSchema:{type:"object",properties:{relay_kind:{type:"string",description:"api_contract | design_handoff (fills from/to role)"},from_role:{type:"string",description:"Originating role (overrides relay_kind)"},to_role:{type:"string",description:"Addressed role (overrides relay_kind)"},title:{type:"string",description:"What's being handed off"},project_id:{type:"number",description:"Project id (optional)"},parent_id:{type:"number",description:"The work item this relay hangs off (optional)"},payload:{type:"object",description:"Structured handoff payload per the kind's schema"}},required:["title"]}},{name:"relay_inbox",description:"List relays addressed to a role (your inbox). status='open' by default; include_all=true also shows claimed relays (in progress by another session). Returns GoalNode[] (kind='relay').",inputSchema:{type:"object",properties:{role:{type:"string",description:"The to_role whose inbox to read"},project_id:{type:"number",description:"Filter by project"},include_all:{type:"boolean",description:"Also include claimed relays (default false)"}},required:["role"]}},{name:"relay_get",description:"Fetch one directed cross-role relay/handoff (a baton passed between roles, e.g. frontend to backend) by id. Returns the kind='relay' GoalNode with its status, payload, and claim state. Use once you already have a relay id from relay_open or relay_inbox and want its full detail.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Relay id"}},required:["id"]}},{name:"relay_claim",description:"Claim a relay (atomic, first-write-wins). If already claimed, returns claimed=false with heldBy=<session id> so you skip it (no double-work). Returns {relay, claimed, heldBy}.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Relay id to claim"},session_id:{type:"number",description:"Your session id (the current baton holder)"}},required:["id","session_id"]}},{name:"relay_advance",description:"Advance a relay's lifecycle: open -> claimed -> delivered -> verified -> closed (delivered -> claimed = kickback). 'verified' REQUIRES evidence. Illegal transitions are rejected. Pass session_id to enable the identity-reuse guard: if a DIFFERENT attested agent identity now holds this session than the one that claimed the relay, the advance is flagged (default, response + audit log) or rejected with a retarget error (RELAY_IDENTITY_GUARD_MODE=strict). Omit session_id to skip the guard entirely.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Relay id"},status:{type:"string",description:"Target status"},evidence:{type:"string",description:"Real observed output (required for -> verified)"},session_id:{type:"number",description:"Your session id -- enables the identity-reuse guard against the identity that claimed this relay"}},required:["id","status"]}},{name:"relay_nudge",description:"Nudge a stuck 'claimed' relay to wake the holder (only 'claimed' relays can be nudged). Increments a per-relay nudge count; once it reaches the tenant's threshold (default 3), the same call auto-releases the claim back to status='open' so another session can pick it up. Returns {relay, released, nudgeCount, threshold}. If your runtime supports cross-session messaging and the holder's session is locally reachable, send it a native ping alongside this call for a real-time wake instead of waiting on its next poll.",inputSchema:{type:"object",properties:{id:{type:"number",description:"Relay id to nudge"},session_id:{type:"number",description:"Your session id (recorded as the nudger, optional)"},reason:{type:"string",description:"Why you're nudging it (optional, for the audit trail)"}},required:["id"]}},{name:"ctx_web_search",description:"Search the web through a configured provider (Tavily, Brave, or Serper). Use this when you need to look up current information online, find documentation for a live service, or search the internet for facts not in your training data. Tenant-scoped and gated behind WEB_SEARCH_ENABLED plus an active search credential. Optionally pipe results through ctx_ingest to persist them as knowledge base entries.",inputSchema:{type:"object",properties:{query:{type:"string",description:"Search query string (1-2000 chars)"},maxResults:{type:"number",description:"Max results to return (1-20, default 5)"},ingest:{type:"boolean",description:"If true, pipe each result through the ingest pipeline to persist as knowledge base entries (best-effort, default false)"}},required:["query"]}},{name:"ctx_feedback",description:"Record a helpful/unhelpful verdict on a context that was returned by ctx_search (or POST /api/retrieve), closing the recall loop back into ranking. Pass the retrieval trace id from ctx_explain_recall (or from a search response's trace metadata) to join the verdict to the exact score breakdown it is about -- omit it if you don't have one, the verdict still records. Verdicts accumulate into a per-context, per-tenant adjustment (bounded by a small cap so no single client can reshape ranking) that folds into the T345 salience/reinforcement term the next time this context is retrieved. Tenant-scoped: a contextId or retrievalTraceId belonging to another tenant is silently dropped rather than accepted.",inputSchema:{type:"object",properties:{contextId:{type:"number",description:"The context id this verdict is about"},used:{type:"boolean",description:"true = the recalled context was actually useful/relevant; false = it was returned but not helpful"},retrievalTraceId:{type:"number",description:"Optional -- the retrieval_traces row id (from ctx_explain_recall or a search trace, NOT the 32-hex trace_id sidecar) this verdict is joined to."},sessionId:{type:"number",description:"Optional agent session id, for forensic correlation"},source:{type:"string",description:"Optional harness tag (e.g. 'agent-boot', 'manual', 'eval'), max 32 chars"}},required:["contextId","used"]}},{name:"ctx_snapshot_create",description:"Take a content-addressed snapshot of a workspace's memory state (contexts, context_links, and its goal graph) at this moment. Returns {id, manifestHash, sourceWorkspaceId, ...} -- the snapshot id feeds ctx_fork_world for counterfactual replay or safe memory-surgery testing.",inputSchema:{type:"object",properties:{workspace:{type:"string",description:"Workspace slug to snapshot"}},required:["workspace"]}},{name:"ctx_fork_world",description:"Fork a throwaway SANDBOX workspace cloned from a snapshot (or a fresh snapshot of `workspace` taken on the fly). Mutate memory inside the sandbox freely -- production rows are never touched. Sandboxes are quota-capped per tenant and auto-expire after ttl_minutes (default from WORLD_SANDBOX_TTL_MINUTES). Returns {sandboxWorkspaceSlug, snapshotId, expiresAt, clonedCounts}.",inputSchema:{type:"object",properties:{snapshot_id:{type:"number",description:"Fork from an existing snapshot id"},workspace:{type:"string",description:"Or: take a fresh snapshot of this workspace slug and fork it"},ttl_minutes:{type:"number",description:"Sandbox lifetime in minutes (0 = no expiry; default from WORLD_SANDBOX_TTL_MINUTES)"}},required:[]}},{name:"ctx_diff_world",description:"Diff a sandbox workspace's CURRENT rows against the snapshot it was forked from -- added/modified/removed/unchanged contexts, plus coarse counts for context_links and the goal graph. Use before deciding whether to apply a sandbox experiment's changes back to production (there is no auto-merge -- replay the accepted changes yourself, then ctx_discard_sandbox).",inputSchema:{type:"object",properties:{sandbox_workspace:{type:"string",description:"Sandbox workspace slug (returned by ctx_fork_world)"}},required:["sandbox_workspace"]}},{name:"ctx_discard_sandbox",description:"Hard-delete a sandbox workspace and everything in it (contexts, links, cloned goal nodes/deps). Irreversible. Sandboxes past their TTL are also swept automatically by a background job.",inputSchema:{type:"object",properties:{sandbox_workspace:{type:"string",description:"Sandbox workspace slug to discard"}},required:["sandbox_workspace"]}},{name:"ctx_tool_groups",description:"List additional groups of ContextQ tools not loaded in this session by default -- code-graph lookup, admin/audit, relay handoff, world-model snapshots, saved searches, knowledge-graph traversal, bulk import/ingest, and more. Search here first if a ContextQ tool you expect (a saved search, a relay, a snapshot, a code reference) is missing from your current tool list. Returns each group's name, one-line purpose, member tool names, and how many of them are already loaded, plus how to load a group with ctx_load_tool_group.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"ctx_load_tool_group",description:`Load one additional group of ContextQ tools into this session (group names come from ctx_tool_groups) so they become callable without reconnecting. Pass "all" to load every remaining ContextQ tool at once. Some MCP clients need to refresh their tool list to actually see newly loaded tools in the model's context -- if a loaded tool still doesn't show up, call it directly by name anyway (ContextQ accepts a tool call for any known tool name regardless of what tools/list currently returns), or restart this server with the environment variable CONTEXT_MCP_TOOL_PROFILE=full to get every tool from the start.`,inputSchema:{type:"object",properties:{group:{type:"string",description:'Group name from ctx_tool_groups (e.g. "pkm", "admin", "relay", "knowledge-graph"), or "all" to load every remaining ContextQ tool.'}},required:["group"],additionalProperties:!1}},{name:"ctx_workspace_list",description:'List the workspaces this credential can write to (id, slug, name, context count, archived flag). Call this before ctx_workspace_ensure to check whether this project already has a workspace. Trigger: "which workspaces do I have", "list workspaces", "set up ContextQ here".',inputSchema:{type:"object",properties:{includeArchived:{type:"boolean",description:"Include archived workspaces in the result (default false)"}}}},{name:"ctx_workspace_ensure",description:'Idempotently get-or-create a workspace for this tenant: returns the existing one (created=false) or creates it (created=true). Safe to call repeatedly. The returned `slug` MAY DIFFER from the one you asked for \u2014 workspace slugs are globally unique, so a taken base gets a random suffix. Always persist the slug from the response and use THAT for every later call. Trigger: first-time project setup, "set up ContextQ for this repo".',inputSchema:{type:"object",properties:{name:{type:"string",description:"Display name for the workspace, e.g. the repository name"},slug:{type:"string",description:"Preferred slug, used only to look up an already-existing workspace. A newly created workspace derives its slug from `name`, so it can come back different."}},required:["name"]}},{name:"ctx_project_ensure",description:"Idempotently get-or-create a project inside a workspace, matched on (workspace, slug). Returns the existing project or a new one. Call after ctx_workspace_ensure, passing the slug that call returned. Trigger: first-time project setup.",inputSchema:{type:"object",properties:{workspace:{type:"string",description:"Slug of the workspace that owns the project"},name:{type:"string",description:"Display name (defaults to the workspace slug)"},slug:{type:"string",description:"Project slug; defaults to the slugified name, then to the workspace slug"}},required:["workspace"]}}],L={"search-advanced":{description:"Explainable-recall score breakdowns, live web search, and recall-loop feedback beyond the default ctx_search.",tools:["ctx_explain_recall","ctx_web_search","ctx_feedback"]},"crud-advanced":{description:"Bulk updates, archive-undo, and per-entry usage/scope tracking beyond the default single-entry CRUD.",tools:["ctx_bulk_update","ctx_undo_archive","ctx_undo_archive_info","ctx_use","ctx_scope"]},ingest:{description:"Bulk document/URL import and raw ingest-job status.",tools:["ctx_import","ctx_ingest","ctx_ingest_status"]},pkm:{description:"Personal-knowledge-management synthesis: dream runs, context linking, evolution, chunk inspection, and maps-of-content.",tools:["ctx_dream","ctx_link","ctx_links","ctx_evolve","ctx_chunks","ctx_mocs","ctx_regenerate_mocs","ctx_maps"]},code:{description:"Code-graph reference lookup and call-trace queries for code-indexed contexts.",tools:["ctx_code_refs","ctx_code_trace"]},"knowledge-graph":{description:"Provenance, confidence, belief history, blast-radius, entity, and contradiction traversal over the knowledge graph.",tools:["ctx_provenance_get","ctx_confidence_get","ctx_belief_history","ctx_blast_radius","ctx_graph_search","ctx_entity_get","ctx_contradictions_list"]},"saved-searches":{description:"CRUD and manual/scheduled run of saved/recurring searches.",tools:["ctx_saved_searches_list","ctx_saved_searches_create","ctx_saved_searches_get","ctx_saved_searches_update","ctx_saved_searches_delete","ctx_saved_searches_run","ctx_run_saved_search"]},admin:{description:"Operational/admin tools: recent-events feed, memory-review logs and runs, rate limits, audit cleanup/chain status, queue stats.",tools:["ctx_events_recent","ctx_memory_review_logs","ctx_admin_rate_limit_get","ctx_admin_rate_limit_set","ctx_memory_review_run","ctx_audit_cleanup_run","ctx_audit_chain_status","ctx_admin_queue_stats"]},"agent-advanced":{description:"Context-pressure checks, episodic tool-call replay, procedural skill suggestions, identity attestation, intention tracking, the mid-run findings channel, and LLM-free verbatim handoff upsert beyond the default agent session loop.",tools:["agent_handoff_save","agent_run_start","agent_finding_add","agent_finding_list","agent_context_pressure","agent_replay","agent_skill_suggest","agent_identity_attest","agent_identity_verify","agent_intention_set","agent_intention_list","agent_intention_cancel"]},"goal-advanced":{description:"Objective setup, dependency links, import, decomposition, single-node reads, and the cross-machine claim/release pair, beyond the default add/advance/list/frontier.",tools:["goal_set_objective","goal_link_dep","goal_import","goal_decompose","goal_get","goal_claim","goal_release"]},relay:{description:"Directed cross-role handoff: open, inbox, get, claim, advance, nudge.",tools:["relay_open","relay_inbox","relay_get","relay_claim","relay_advance","relay_nudge"]},"world-model":{description:"Content-addressed workspace snapshots and forkable sandboxes for counterfactual memory experiments.",tools:["ctx_snapshot_create","ctx_fork_world","ctx_diff_world","ctx_discard_sandbox"]},workspace:{description:"One-time project provisioning: list workspaces, and idempotently get-or-create a workspace or a project inside one.",tools:["ctx_workspace_list","ctx_workspace_ensure","ctx_project_ensure"]}},Q=new Set(["agent_boot","agent_session_start","agent_session_end","agent_checkpoint","agent_resume","agent_task_upsert","agent_task_tick","agent_lesson_add","agent_handoff","ctx_search","ctx_save","ctx_get","ctx_list","ctx_update","ctx_delete","ctx_remember","ctx_stats","ctx_health","goal_add","goal_advance","goal_list","goal_frontier","ctx_tool_groups","ctx_load_tool_group"]);function J(){return(process.env.CONTEXT_MCP_TOOL_PROFILE??"").trim().toLowerCase()==="full"?"full":"default"}a(J,"resolveInitialToolProfile");var _=J()==="full"?new Set(x.map(e=>e.name)):new Set(Q);function P(){return x.filter(e=>_.has(e.name))}a(P,"getActiveTools");function Z(e){let t=e.trim().toLowerCase(),n=t==="all"?x.map(i=>i.name):L[t]?.tools;if(!n)return{added:[],unknownGroup:!0};let r=[];for(let i of n)_.has(i)||(_.add(i),r.push(i));return{added:r,unknownGroup:!1}}a(Z,"loadToolGroup");var ee={async ctx_save(e){return s("POST","/api/contexts",e)},async ctx_search(e){return s("POST","/api/search",e)},async ctx_explain_recall(e){let{id:t}=e;return s("GET",`/api/search/traces/${t}/explain`)},async ctx_web_search(e){return s("POST","/api/search/web",e)},async ctx_feedback(e){return s("POST","/api/memory-push/feedback",e)},async ctx_list(e){let t=c(e);return s("GET",`/api/contexts${t}`)},async ctx_get(e){return s("GET",`/api/contexts/${e.id}`)},async ctx_update(e){let{id:t,...n}=e;return s("PUT",`/api/contexts/${t}`,n)},async ctx_delete(e){return s("DELETE",`/api/contexts/${e.id}`)},async ctx_bulk_update(e){return s("POST","/api/contexts/bulk",e)},async ctx_undo_archive(e){let{id:t}=e;return s("POST",`/api/contexts/${t}/undo-archive`,{})},async ctx_undo_archive_info(e){let{id:t}=e;return s("GET",`/api/contexts/${t}/undo-archive-info`)},async ctx_import(e){return s("POST","/api/import",e)},async ctx_stats(){return s("GET","/api/stats")},async ctx_dream(e){return s("POST","/api/dream",e)},async ctx_link(e){let{source_id:t,target_id:n,type:r,confidence:i,reason:o}=e,p={target_id:n,type:r};return i!==void 0&&(p.confidence=i),o!==void 0&&(p.created_by=o),s("POST",`/api/contexts/${t}/links`,p)},async ctx_links(e){let{id:t,direction:n}=e,r=c({direction:n??"both"});return s("GET",`/api/contexts/${t}/links${r}`)},async ctx_evolve(e){let{id:t,k_neighbors:n,dry_run:r}=e,i={};return n!==void 0&&(i.k_neighbors=n),r!==void 0&&(i.dry_run=r),s("POST",`/api/contexts/${t}/evolve`,i)},async ctx_chunks(e){return s("GET",`/api/contexts/${e.id}/chunks`)},async ctx_code_refs(e){let{ref:t}=e;return s("GET",`/api/code/refs${c({ref:t})}`)},async ctx_code_trace(e){let{ref:t,depth:n}=e;return s("GET",`/api/code/trace${c({ref:t,depth:n})}`)},async ctx_provenance_get(e){let{id:t,depth:n}=e;return s("GET",`/api/contexts/${t}/provenance${c({depth:n})}`)},async ctx_confidence_get(e){let{id:t}=e;return s("GET",`/api/contexts/${t}/confidence`)},async ctx_belief_history(e){let{id:t,limit:n}=e;return s("GET",`/api/contexts/${t}/belief-events${c({limit:n})}`)},async ctx_blast_radius(e){let{id:t,depth:n,top:r}=e;return s("GET",`/api/contexts/${t}/blast-radius${c({depth:n,top:r})}`)},async ctx_graph_search(e){let{query:t,as_of:n,limit:r}=e,i={query:t};return n!==void 0&&(i.asOf=n),r!==void 0&&(i.limit=r),s("POST","/api/graph/search",i)},async ctx_entity_get(e){let{id:t,as_of:n}=e;return s("GET",`/api/graph/entities/${t}${c({asOf:n})}`)},async ctx_mocs(e){let t=c(e);return s("GET",`/api/mocs${t}`)},async ctx_regenerate_mocs(){return s("POST","/api/mocs/regenerate",{})},async ctx_maps(e){let t=c(e);return s("GET",`/api/maps${t}`)},async ctx_saved_searches_list(){return s("GET","/api/saved-searches")},async ctx_saved_searches_create(e){return s("POST","/api/saved-searches",e)},async ctx_saved_searches_get(e){return s("GET",`/api/saved-searches/${e.id}`)},async ctx_saved_searches_update(e){let{id:t,...n}=e;return s("PUT",`/api/saved-searches/${t}`,n)},async ctx_saved_searches_delete(e){return s("DELETE",`/api/saved-searches/${e.id}`)},async ctx_saved_searches_run(e){let{id:t,...n}=e;return s("POST",`/api/saved-searches/${t}/run`,n)},async ctx_run_saved_search(e){return s("POST","/api/saved-searches/run-by-name",e)},async ctx_ingest(e){return s("POST","/api/ingest",e)},async ctx_ingest_status(e){let{job_id:t}=e;return s("GET",`/api/ingest-jobs/${t}`)},async ctx_remember(e){return s("POST","/api/memory",e)},async ctx_events_recent(e){let t=c(e);return s("GET",`/api/events/recent${t}`)},async ctx_memory_review_logs(e){let t=c(e);return s("GET",`/api/admin/memory-review-logs${t}`)},async ctx_memory_review_run(e){let t={};return"tenant_id"in e&&(t.tenant_id=e.tenant_id),s("POST","/api/admin/memory-review/run",t)},async ctx_contradictions_list(e){let t=c(e);return s("GET",`/api/contradictions${t}`)},async ctx_audit_cleanup_run(e){let t={};return e.retention_days!==void 0&&(t.retention_days=e.retention_days),s("POST","/api/admin/audit-cleanup/run",t)},async ctx_admin_rate_limit_get(){return s("GET","/api/admin/rate-limit/buckets")},async ctx_admin_rate_limit_set(e){let{tenant_id:t,...n}=e;return s("PUT",`/api/admin/tenants/${t}/rate-limit`,n)},async ctx_health(){return s("GET","/health/deep")},async ctx_audit_chain_status(){return s("GET","/api/admin/audit-logs/verify-chain")},async ctx_admin_queue_stats(){return s("GET","/api/admin/queue-stats")},async agent_boot(e){return s("POST","/api/agent/boot",e)},async agent_session_start(e){return s("POST","/api/agent/sessions",e)},async agent_session_end(e){let{session_id:t,...n}=e;return s("PATCH",`/api/agent/sessions/${t}`,n)},async agent_checkpoint(e){let{session_id:t,...n}=e;return s("POST",`/api/agent/sessions/${t}/checkpoint`,n)},async agent_context_pressure(e){let{session_id:t,...n}=e;return s("POST",`/api/agent/sessions/${t}/context-pressure`,n)},async agent_resume(e){let{session_id:t}=e;return s("GET",`/api/agent/sessions/${t}/resume`)},async agent_task_upsert(e){let{session_id:t,...n}=e;return s("POST",`/api/agent/sessions/${t}/tasks`,n)},async agent_task_tick(e){let{task_id:t,...n}=e;return s("PATCH",`/api/agent/tasks/${t}`,n)},async agent_lesson_add(e){let{session_id:t,...n}=e;return s("POST",`/api/agent/sessions/${t}/lessons`,n)},async agent_handoff(e){let{session_id:t,...n}=e;return s("POST",`/api/agent/sessions/${t}/handoff`,n)},async agent_handoff_save(e){let{external_id:t,workspace:n,...r}=e;return s("POST","/api/contexts/bulk-upsert",{workspace:n,contexts:[{externalId:t,type:"handoff",...r}]})},async agent_replay(e){let{session_id:t,...n}=e;return s("GET",`/api/agent/sessions/${t}/trace-events${c(n)}`)},async agent_skill_suggest(e){return s("GET",`/api/agent/skills/suggest${c(e)}`)},async agent_identity_attest(e){return s("POST","/api/agent/identity/attest",e)},async agent_identity_verify(e){let{agent:t}=e;return s("GET",`/api/agent/identity/verify${c({agent:t})}`)},async agent_intention_set(e){return s("POST","/api/agent/intentions",e)},async agent_intention_list(e){let{agent:t,status:n}=e;return s("GET",`/api/agent/intentions${c({agent:t,status:n})}`)},async agent_intention_cancel(e){let{intention_id:t}=e;return s("POST",`/api/agent/intentions/${t}/cancel`,{})},async ctx_use(e){return s("POST","/api/session/scope",e)},async ctx_scope(){return s("GET","/api/session/scope")},async goal_set_objective(e){return s("POST","/api/goal/objective",e)},async goal_add(e){return s("POST","/api/goal/nodes",e)},async goal_link_dep(e){return s("POST","/api/goal/deps",e)},async goal_advance(e){let{node_id:t,...n}=e;return s("PATCH",`/api/goal/nodes/${t}/advance`,n)},async agent_run_start(e){return s("POST","/api/agent/runs",e??{})},async agent_finding_add(e){return s("POST","/api/agent/findings",e)},async agent_finding_list(e){return s("GET",`/api/agent/findings${c(e)}`)},async goal_claim(e){return s("POST","/api/goal/nodes/claim",e)},async goal_release(e){return s("POST","/api/goal/nodes/release",e)},async goal_import(e){return s("POST","/api/goal/import",e)},async goal_decompose(e){return s("POST","/api/goal/decompose",e)},async goal_list(e){let{view:t,include_done:n,...r}=e;return s("GET",`/api/goal/nodes${c({...r,view:t??"compact",include_done:n??!1})}`)},async goal_get(e){let{node_id:t}=e;return s("GET",`/api/goal/nodes/${t}`)},async goal_frontier(e){return s("GET",`/api/goal/frontier${c(e)}`)},async relay_open(e){return s("POST","/api/relay",e)},async relay_inbox(e){return s("GET",`/api/relay/inbox${c(e)}`)},async relay_get(e){let{id:t}=e;return s("GET",`/api/relay/${t}`)},async relay_claim(e){let{id:t,...n}=e;return s("POST",`/api/relay/${t}/claim`,n)},async relay_advance(e){let{id:t,...n}=e;return s("PATCH",`/api/relay/${t}/advance`,n)},async relay_nudge(e){let{id:t,...n}=e;return s("POST",`/api/relay/${t}/nudge`,n)},async ctx_snapshot_create(e){return s("POST","/api/world/snapshots",e)},async ctx_fork_world(e){return s("POST","/api/world/fork",e)},async ctx_diff_world(e){let{sandbox_workspace:t}=e;return s("GET",`/api/world/sandboxes/${encodeURIComponent(t)}/diff`)},async ctx_discard_sandbox(e){let{sandbox_workspace:t}=e;return s("DELETE",`/api/world/sandboxes/${encodeURIComponent(t)}`)},async ctx_tool_groups(){let e=Object.entries(L).map(([t,n])=>({group:t,description:n.description,tool_count:n.tools.length,loaded:n.tools.every(r=>_.has(r)),tools:n.tools}));return{loaded_tool_count:_.size,total_tool_count:x.length,groups:e,how_to_load:`Call ctx_load_tool_group with { group: "<name>" } (or "all") to add a group's tools to this session. If your tool list does not refresh automatically, call the newly loaded tool directly by name anyway -- ContextQ accepts a call for any known tool name regardless of what tools/list currently returns -- or reconnect with the environment variable CONTEXT_MCP_TOOL_PROFILE=full to start with every tool loaded.`}},async ctx_load_tool_group(e){let{group:t}=e;if(typeof t!="string"||t.trim()==="")return{error:'group is required -- call ctx_tool_groups to see valid group names, or pass "all".'};let{added:n,unknownGroup:r}=Z(t);return r?{error:`Unknown group "${t}". Call ctx_tool_groups to see valid group names.`}:{group:t.trim().toLowerCase(),added_tools:n,already_loaded:n.length===0,note:"These tools are now callable. If they do not appear in your tool list yet, call them directly by name -- ContextQ accepts a call for any known tool name regardless of what tools/list currently shows."}},async ctx_workspace_list(e){return s("GET",`/api/workspaces${c(e)}`)},async ctx_workspace_ensure(e){return s("POST","/api/workspaces/ensure",e)},async ctx_project_ensure(e){return s("POST","/api/projects",e)}},u=null,E=!1;function te(){return process.env.CONTEXTQ_BRAIN_STATE_INJECT!=="false"}a(te,"brainStateInjectionEnabled");var ne=6e4,j=0,se=new Set(["ctx_save","ctx_update","ctx_delete","ctx_bulk_update","ctx_undo_archive","ctx_import","ctx_dream","ctx_link","ctx_evolve","ctx_regenerate_mocs","ctx_saved_searches_create","ctx_saved_searches_update","ctx_saved_searches_delete","ctx_saved_searches_run","ctx_run_saved_search","ctx_ingest","ctx_remember","ctx_memory_review_run","ctx_audit_cleanup_run","ctx_admin_rate_limit_set","ctx_use","agent_session_start","agent_session_end","agent_task_upsert","agent_task_tick","agent_lesson_add","agent_handoff","agent_handoff_save","agent_identity_attest","agent_intention_set","agent_intention_cancel","goal_set_objective","goal_add","goal_link_dep","agent_run_start","agent_finding_add","goal_advance","goal_claim","goal_release","goal_import","goal_decompose","relay_open","relay_claim","relay_advance","relay_nudge","ctx_snapshot_create","ctx_fork_world","ctx_discard_sandbox","ctx_workspace_ensure","ctx_project_ensure"]);function re(){return process.env.CONTEXTQ_AUTO_CHECKPOINT!=="false"}a(re,"autoCheckpointEnabled");function ie(e,t){let n=Date.now();n-j<ne||(j=n,s("POST",`/api/agent/sessions/${e}/checkpoint`,{summary:`auto-checkpoint after ${t}`}).catch(()=>{j=0}))}a(ie,"autoCheckpoint");async function oe(){try{let e=await s("GET","/api/agent/brain-state");if(!e||typeof e!="object")return null;let t=e.block;return typeof t=="string"?t:null}catch{return null}}a(oe,"buildBrainStateBlock");function ae(e){let t=e?.session_id;return typeof t=="number"?t:null}a(ae,"extractSessionId");function ce(e){if(e&&typeof e=="object"&&"id"in e){let t=e.id;if(typeof t=="number")return t}return null}a(ce,"extractSessionIdFromResult");function I(e,t,n,r,i,o){s("POST",`/api/agent/sessions/${e}/trace-events`,{tool_name:t,args:n,result:i?{error:String(r)}:r,duration_ms:o}).catch(()=>{})}a(I,"traceToolCall");async function A(e,t,n){let r=ee[e];if(!r)return{content:[{type:"text",text:`Unknown tool: ${e}`}],isError:!0};let i=ae(t);i!==null&&(u=i);let o=O(n),p=Date.now();try{let d=await T.run(o,()=>r(t)),l=Date.now()-p;if(e==="agent_session_start"){let y=ce(d);y!==null&&(u=y)}u!==null&&(I(u,e,t,d,!1,l),se.has(e)&&re()&&ie(u,e));let m={content:[{type:"text",text:d!==null?JSON.stringify(d,null,2):"OK"}]};if(!E&&(E=!0,e!=="agent_boot"&&te())){let y=await oe();y&&m.content.push({type:"text",text:y})}return m}catch(d){let l=Date.now()-p,m=d instanceof Error?d.message:String(d);return u!==null&&I(u,e,t,m,!0,l),{content:[{type:"text",text:`Error: ${m}`}],isError:!0}}}a(A,"handleToolCall");function fe(){try{let e=ye(me(import.meta.url));return JSON.parse(he(ge(e,"..","package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}a(fe,"readPackageVersion");var U=fe(),k="0.1.0";function N(e){let t=e.match(/^(\d+)\.(\d+)\.(\d+)/);return t?{major:Number(t[1]),minor:Number(t[2]),patch:Number(t[3])}:null}a(N,"parseSemVer");function _e(e,t){return e.major!==t.major?e.major>t.major:e.minor!==t.minor?e.minor>t.minor:e.patch>=t.patch}a(_e,"semVerGte");async function be(e){let t=e.replace(/\/+$/,""),n=!1,r;try{let i=await fetch(`${t}/health`,{signal:AbortSignal.timeout(3e3),redirect:"manual"});if(i.ok){n=!0;let o=await i.json().catch(()=>null);o&&typeof o.version=="string"&&(r=o.version)}}catch{}if(!r)try{let i=await fetch(`${t}/health/deep`,{signal:AbortSignal.timeout(3e3),redirect:"manual"});if(i.ok){n=!0;let o=await i.json().catch(()=>null);o&&typeof o.version=="string"&&(r=o.version)}}catch{}if(!n){console.error(`[shared-context-mcp] WARNING: API at ${t} is unreachable. The MCP server has started but tools will return errors until the API becomes reachable.`);return}if(r){let i=N(r),o=N(k);i&&o?_e(i,o)?console.error(`[shared-context-mcp] API version ${r} satisfies min version ${k}.`):console.error(`[shared-context-mcp] WARNING: API version ${r} is older than the minimum supported version ${k}. The MCP server has started but some tools may behave differently.`):console.error(`[shared-context-mcp] API reports version identifier: ${r} (not a semantic version \u2014 compatibility check skipped).`)}else console.error(`[shared-context-mcp] WARNING: API at ${t} does not report a version. Compatibility with min ${k} was not verified.`)}a(be,"handshake");var h=new de({name:"shared-context",version:U},{capabilities:{tools:{listChanged:!0}},instructions:C});h.setRequestHandler(le,async()=>({tools:P()}));h.setRequestHandler(ue,async e=>{let{name:t,arguments:n,_meta:r}=e.params,i=await A(t,n??{},r);return t==="ctx_load_tool_group"&&!i.isError&&h.sendToolListChanged().catch(()=>{}),i});async function we(){let e=process.env.CONTEXT_API_URL??"http://localhost:38200";try{let r=new URL(e);r.protocol!=="http:"&&r.protocol!=="https:"&&(console.error(`[shared-context-mcp] Fatal: CONTEXT_API_URL must use http: or https: protocol, got "${r.protocol}". Set CONTEXT_API_URL to a valid API base URL.`),process.exit(1))}catch{console.error(`[shared-context-mcp] Fatal: CONTEXT_API_URL "${e}" is not a valid URL. Set CONTEXT_API_URL to a valid API base URL (e.g. http://localhost:38200).`),process.exit(1)}h.oninitialized=()=>{q(h.getClientVersion())};let t=new pe;await h.connect(t);let n=process.env.CONTEXT_API_KEY;console.error(`[shared-context-mcp] Connected (v${U}). API: ${e}, Auth: ${n?"configured":"NONE"}`),await be(e)}a(we,"main");we().catch(e=>{console.error("[shared-context-mcp] Fatal:",e),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextq/mcp",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"mcpName": "io.github.
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"mcpName": "io.github.contextq/contextq-mcp",
|
|
5
5
|
"description": "MCP server for ContextQ — exposes the full ContextQ knowledge-management API as Model Context Protocol tools",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"contextq-mcp": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"dist"
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/**/*.test.*"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
15
|
"dev": "tsx watch src/index.ts",
|
|
15
|
-
"build": "tsc",
|
|
16
|
+
"build": "tsc --noEmit && tsup",
|
|
16
17
|
"start": "node dist/index.js",
|
|
17
|
-
"prepublishOnly": "
|
|
18
|
-
"prepare": "
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"prepare": "npm run build",
|
|
19
20
|
"audit-tokens": "tsx scripts/audit-tool-tokens.ts",
|
|
20
21
|
"test": "tsx --test 'src/**/*.test.ts'",
|
|
21
22
|
"check:protocol": "tsx scripts/check-protocol-version.ts",
|
|
@@ -29,12 +30,7 @@
|
|
|
29
30
|
"ai-agents"
|
|
30
31
|
],
|
|
31
32
|
"license": "MIT",
|
|
32
|
-
"
|
|
33
|
-
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/HENIART/contextq.git",
|
|
35
|
-
"directory": "mcp"
|
|
36
|
-
},
|
|
37
|
-
"homepage": "https://github.com/HENIART/contextq#readme",
|
|
33
|
+
"homepage": "https://contextq.dev",
|
|
38
34
|
"engines": {
|
|
39
35
|
"node": ">=22"
|
|
40
36
|
},
|
|
@@ -45,8 +41,9 @@
|
|
|
45
41
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
46
42
|
},
|
|
47
43
|
"devDependencies": {
|
|
48
|
-
"
|
|
44
|
+
"@types/node": "^22.15.0",
|
|
45
|
+
"tsup": "^8.5.1",
|
|
49
46
|
"tsx": "^4.23.12",
|
|
50
|
-
"
|
|
47
|
+
"typescript": "^5.8.3"
|
|
51
48
|
}
|
|
52
49
|
}
|
package/dist/index.d.ts
DELETED
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|