loopctl-mcp-server 2.40.0 → 2.42.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 +11 -3
- package/index.js +326 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (Model Context Protocol) server for [loopctl](https://loopctl.com) -- structural trust for AI development loops.
|
|
4
4
|
|
|
5
|
-
Wraps the loopctl REST API into
|
|
5
|
+
Wraps the loopctl REST API into 76 typed MCP tools so AI coding agents (Claude Code, etc.) can interact with loopctl without writing curl commands.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -121,7 +121,7 @@ REST endpoint (`PATCH /api/v1/tenants/me/llm-config`), and the docs — so you (
|
|
|
121
121
|
autonomous agent) can self-remediate without a human. Full agent-tenant lifecycle:
|
|
122
122
|
[`docs/onboarding-agent-tenant.md`](../docs/onboarding-agent-tenant.md).
|
|
123
123
|
|
|
124
|
-
## Tools (
|
|
124
|
+
## Tools (89)
|
|
125
125
|
|
|
126
126
|
> Plus **per-tenant generated Context Retriever tools** (`cr_*`) appended
|
|
127
127
|
> dynamically at runtime — see [Dynamic per-tenant Context Retriever
|
|
@@ -133,6 +133,7 @@ autonomous agent) can self-remediate without a human. Full agent-tenant lifecycl
|
|
|
133
133
|
|---|---|
|
|
134
134
|
| `get_tenant` | Get current tenant info. Use to verify connectivity. |
|
|
135
135
|
| `list_projects` | List all projects in the current tenant. |
|
|
136
|
+
| `resolve_project` | Resolve a repo to its `project_id` in one cheap call — provide any of `slug`, `repo_url` (`git@github.com:owner/repo.git`, `https://github.com/owner/repo`, and bare `owner/repo` all match), or `name`. Precedence `slug > repo_url > name`, first match wins. Use the returned `id` to scope captures/recall (`memory_*`, `recall_context`). `404` `not_found` if nothing matches, `422` `no_identifier` if none supplied, `409` if a fuzzy identifier matches more than one active project. |
|
|
136
137
|
| `create_project` | Create a new project in the current tenant. |
|
|
137
138
|
| `delete_project` | **Requires `LOOPCTL_USER_KEY`.** Delete a project and all of its dependent resources (epics, stories, audit entries). Irreversible — orchestrator role is not sufficient. |
|
|
138
139
|
| `get_progress` | Get progress summary for a project, including story counts by status. Pass `include_cost=true` for cost data. |
|
|
@@ -198,6 +199,8 @@ autonomous agent) can self-remediate without a human. Full agent-tenant lifecycl
|
|
|
198
199
|
| How many articles? (counts only) | `knowledge_stats` |
|
|
199
200
|
| Browse the catalog (lightweight id/title/category) | `knowledge_index` |
|
|
200
201
|
| Find by topic / relevance (ranked, published-only, lags writes) | `knowledge_search` |
|
|
202
|
+
| One trustworthy answer + provenance (curated-first, retrieval fallback) | `knowledge_hybrid_search` |
|
|
203
|
+
| Survey a topic cheaply (capped stubs) then open only what you need | `knowledge_progressive_index` + `knowledge_progressive_drill` |
|
|
201
204
|
| Enumerate / dedup / repair, or "does X exist?" (full fields, lag-free, all-status) | `knowledge_list` |
|
|
202
205
|
|
|
203
206
|
| Tool | Description |
|
|
@@ -207,6 +210,9 @@ autonomous agent) can self-remediate without a human. Full agent-tenant lifecycl
|
|
|
207
210
|
| `knowledge_count` | Count articles matching filters **without returning rows** within agent's visible set. Agent callers see only their own and `shared` articles. Same filters as `knowledge_list` (`category`, `status`, `tags`, `match`, `source_type`, `source_id`, `idempotency_key`, `project_id`). With `tags`+`match: all` (+`status`) → "how many published articles tagged both X and Y (that I can see)". Returns `{ count }`. |
|
|
208
211
|
| `knowledge_facets` | Count articles grouped by **distinct tag** within agent's visible set, no rows. Agent callers see only their own and `shared` articles. `tag_prefix` (e.g. `book-`) gives the distinct count of a tag family plus per-member totals. Returns `{ data: { tag: count }, meta: { distinct_count } }`. Optional: `category`, `status`, `tags`, `match`, `project_id`, `limit`. |
|
|
209
212
|
| `knowledge_search` | Search the knowledge wiki by topic (keyword, semantic, or combined). Returns snippets. **Ranked, published-only, and LAGS writes by minutes while embeddings index — do NOT use for existence/idempotency/dedup checks (a fresh write false-negatives); use `knowledge_list` for that.** `q` is optional when `tags`/`category` are supplied — that **list mode** returns the complete filtered set paginated via `offset`/`limit` over `meta.total_count`. `meta.total_count` is mode-dependent — read `meta.total_count_scope` (`keyword_matches`/`ranked_corpus`/`merged_candidates`/`filtered_set`) and don't use a relevance-mode count to size the wiki (use `knowledge_list` or `knowledge_stats`). Optional: `project_id`, `story_id` for attribution. |
|
|
213
|
+
| `knowledge_hybrid_search` | Resolve a topic to a **single best answer with provenance** (US-31.4). Runs combined keyword+semantic over the full ranked pool, then decides whether a governed **curated** source actually answers. `meta.provenance` is `curated` (trust it — the canonical article is first in `data`, `meta.curated_article_id` points at it) or `retrieved` (best fuzzy match, `curated_article_id` null); `meta.confidence` is the winner's absolute score. Prefer over `knowledge_search` when you want one trustworthy answer, not a list to triage. Degrades to keyword-only like `knowledge_search` when embeddings are unavailable. Required: `query`. Optional: `project_id`, `category`, `tags`, `match`, `limit`, `offset`. |
|
|
214
|
+
| `knowledge_progressive_index` | Progressive disclosure — a **cheap, capped index** of what's relevant to a topic (compact stubs: `id`/`title`/`category`/`summary`, **no bodies**), curated-preferred and hub-enriched, capped at top-K (`meta.truncated` when the pool exceeded it). Survey a topic without flooding context, then open only what you need via `knowledge_progressive_drill`. Required: `topic`. Optional: `category`, `limit`. |
|
|
215
|
+
| `knowledge_progressive_drill` | Open one stub from `knowledge_progressive_index` — returns the **full article body** for the given id, scope-enforced. Resolves both tenant-owned articles and published system canonicals (the same set the index surfaces). Required: `article_id`. |
|
|
210
216
|
| `knowledge_list` | List articles (`id`, `title`, `category`, `status`, `tags`, `source_type`, `source_id`, `idempotency_key`, timestamps), filtered + paginated. **Body-less summary by default** (safe to page up to `limit=1000`); pass `include_body: true` to also return `body`, in which case the page is bounded by a ~5 MB byte budget — continue via `meta.next_offset` while `meta.has_more`. **Lag-free, all-status** read of the DB of record — unlike `knowledge_search` (ranked, published-only, lags writes) and `knowledge_index` (id/title/category only). The right tool to enumerate/dedup/repair and for idempotency/existence checks: filter by `tags`, `source_type`+`source_id`, or `idempotency_key` and read `meta.total_count` (exact). Single full body → `knowledge_get`; relevant bodies → `knowledge_context`; bulk dump → `knowledge_export`. Optional: `project_id`, `category`, `status`, `tags`, `source_type`, `source_id`, `idempotency_key`, `offset`, `limit`, `include_body`. |
|
|
211
217
|
| `knowledge_get` | Get full article content by ID. Use after search to read an article in detail. Optional: `project_id`, `story_id` for attribution. |
|
|
212
218
|
| `knowledge_context` | Get relevance-and-recency-ranked full articles for a task query. Best knowledge for your current context. **Agent-memory scoping**: `memory_types` (comma-separated, OR — observation/finding/summary/decision/question/task), `agents` (comma-separated agent_ids, OR), `conversation_id` (exact) filter on article `metadata` (JSONB `@>`). Optional: `project_id`, `story_id` for attribution, `limit`, `recency_weight`. |
|
|
@@ -238,7 +244,9 @@ it is enforced server-side and a no-op for a non-superadmin key — see below.)
|
|
|
238
244
|
| `memory_recall` | Semantically recall your own long-term memories most similar to `query`. When embedding generation is unavailable the response degrades to a recent-first text match with `meta.fallback: true` and a stable `meta.reason` (score is `null` on that path) — check `meta.fallback` before treating a short/empty result as a genuinely empty scope. `meta.total_count`/`meta.underfilled` are also returned. Optional: `limit`, `include_superseded`. |
|
|
239
245
|
| `memory_list` | List your own long-term memories, newest first, paginated with `meta.total_count/limit/offset` (the true scoped count, never silently capped by `limit`). Optional: `limit`, `offset`, `include_superseded`, `all_subjects` (superadmin only; ignored for non-superadmin keys). |
|
|
240
246
|
| `memory_forget` | Delete one of your own long-term memories by id. A foreign-subject, foreign-tenant, or unknown id returns 404 (no existence leak). Required: `id`. |
|
|
241
|
-
| `memory_promote` | Call at session end to compile this session's short-term (`session`-tier) memory into durable `long_term` memory — unlike `memory_remember` (a single explicit write), this compiles the whole session in one shot; fire it once at session end, not per turn. Returns 202 with `{
|
|
247
|
+
| `memory_promote` | Call at session end to compile this session's short-term (`session`-tier) memory into durable `long_term` memory — unlike `memory_remember` (a single explicit write), this compiles the whole session in one shot; fire it once at session end, not per turn. Returns 202 with `{session_id, status: "enqueued"}` — promotion runs asynchronously, so the resulting memory is recallable via `memory_recall` only after the worker drains. You can only promote your own sessions (scope resolved server-side from your key). Required: `session_id`. |
|
|
248
|
+
| `recall_context` | ONE round-trip returning the re-ranked `global ∪ active-project` union of long-term MEMORY **and** KNOWLEDGE for `query` — what you previously assembled by calling `memory_recall` and `knowledge_search` separately. Pass `project_id` (from `resolve_project`) to merge global with that project on both sides; absent → global-only. The knowledge half is combined-search *summaries* (not full bodies — use `knowledge_context` for those). Response carries merged `results` (each tagged `source: memory\|knowledge`) plus the untouched per-source `memory`/`knowledge` envelopes; `meta.degraded?` flags a one-sided degrade (the other side is still returned — never a 500). A blank query, or one over 500 chars, is a `422` up front. Required: `query`. Optional: `project_id`, `limit`. |
|
|
249
|
+
| `memory_graduate` | Graduate ONE of your long-term memories into a durable Knowledge Wiki article — the explicit, on-demand version of the hourly graduation sweep. Use when a private memory has proven valuable enough to become durable knowledge. **Visibility**: the graduated article stays **owner-visible** (`metadata.visibility: "owner"`, keyed to your subject) — discoverable by YOU, NOT peer-readable (graduation does not share a memory to teammates; `re_scope: "global"` widens only the project scope, not visibility). Scope is key-derived (you can only graduate your OWN memory; a foreign/unknown `memory_id` → 404). DEDUPED by the novelty gate: `data.verdict` is `created` (novel → published) or `gated_to_draft` (near-dup → review draft) with a new article (**201**), or `duplicate`/`deduplicated` (already represented → canonical article, nothing created) (**200**). By default the article inherits the memory's project scope; pass `re_scope: "global"` to promote a PROJECT memory to a tenant-wide article — only valid on its FIRST graduation, and only if the hourly sweep hasn't graduated it project-scoped first (`409` `already_graduated` otherwise). An already-graduated global memory re-graduates idempotently (**200**). `503` `gate_unavailable` if the embedding backend is down — retry later. Required: `memory_id`. Optional: `re_scope` (`inherit`\|`global`). |
|
|
242
250
|
|
|
243
251
|
### Knowledge Management Tools (orchestrator key)
|
|
244
252
|
|
package/index.js
CHANGED
|
@@ -376,6 +376,22 @@ async function listProjects(args = {}) {
|
|
|
376
376
|
return toContent(result);
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
async function resolveProject({ slug, repo_url, name } = {}) {
|
|
380
|
+
// Cheap repo -> project_id resolution (loopctl #411 Gap 1). Server tries
|
|
381
|
+
// slug -> repo_url -> name and returns the first match; agent-role read.
|
|
382
|
+
const params = new URLSearchParams();
|
|
383
|
+
if (slug) params.set("slug", slug);
|
|
384
|
+
if (repo_url) params.set("repo_url", repo_url);
|
|
385
|
+
if (name) params.set("name", name);
|
|
386
|
+
const result = await apiCall(
|
|
387
|
+
"GET",
|
|
388
|
+
`/api/v1/projects/resolve?${params}`,
|
|
389
|
+
null,
|
|
390
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
391
|
+
);
|
|
392
|
+
return toContent(result);
|
|
393
|
+
}
|
|
394
|
+
|
|
379
395
|
async function createProject({ name, slug, repo_url, description, tech_stack, mission }) {
|
|
380
396
|
const body = { name, slug };
|
|
381
397
|
if (repo_url) body.repo_url = repo_url;
|
|
@@ -962,6 +978,52 @@ async function knowledgeSearch({ q, project_id, story_id, category, tags, match,
|
|
|
962
978
|
return withRemediationNotice(result);
|
|
963
979
|
}
|
|
964
980
|
|
|
981
|
+
async function knowledgeHybridSearch({ query, project_id, category, tags, match, limit, offset }) {
|
|
982
|
+
const bodyPayload = { query };
|
|
983
|
+
if (project_id) bodyPayload.project_id = project_id;
|
|
984
|
+
if (category) bodyPayload.category = category;
|
|
985
|
+
if (tags) bodyPayload.tags = tags;
|
|
986
|
+
if (match) bodyPayload.match = match;
|
|
987
|
+
if (limit != null) bodyPayload.limit = limit;
|
|
988
|
+
if (offset != null) bodyPayload.offset = offset;
|
|
989
|
+
|
|
990
|
+
const result = await apiCall(
|
|
991
|
+
"POST",
|
|
992
|
+
"/api/v1/knowledge/hybrid_search",
|
|
993
|
+
bodyPayload,
|
|
994
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
995
|
+
);
|
|
996
|
+
// meta.provenance ("curated" | "retrieved") is the load-bearing field and rides
|
|
997
|
+
// through toContent verbatim. Same missing-embedding-key remediation surfacing as
|
|
998
|
+
// knowledge_search, since hybrid runs the combined pool underneath.
|
|
999
|
+
return withRemediationNotice(result);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
async function knowledgeProgressiveIndex({ topic, category, limit }) {
|
|
1003
|
+
const params = new URLSearchParams();
|
|
1004
|
+
if (topic != null) params.set("topic", topic);
|
|
1005
|
+
if (category) params.set("category", category);
|
|
1006
|
+
if (limit != null) params.set("limit", String(limit));
|
|
1007
|
+
|
|
1008
|
+
const result = await apiCall(
|
|
1009
|
+
"GET",
|
|
1010
|
+
`/api/v1/knowledge/progressive_index?${params}`,
|
|
1011
|
+
null,
|
|
1012
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
1013
|
+
);
|
|
1014
|
+
return toContent(result);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
async function knowledgeProgressiveDrill({ article_id }) {
|
|
1018
|
+
const result = await apiCall(
|
|
1019
|
+
"GET",
|
|
1020
|
+
`/api/v1/knowledge/progressive/${article_id}`,
|
|
1021
|
+
null,
|
|
1022
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
1023
|
+
);
|
|
1024
|
+
return toContent(result);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
965
1027
|
async function knowledgeList({
|
|
966
1028
|
project_id,
|
|
967
1029
|
category,
|
|
@@ -1160,6 +1222,26 @@ async function memoryRecall({ query, limit, include_superseded }) {
|
|
|
1160
1222
|
return toContent(result);
|
|
1161
1223
|
}
|
|
1162
1224
|
|
|
1225
|
+
async function recallContext({ query, project_id, limit }) {
|
|
1226
|
+
// Merged recall (#411 Gap 2): ONE round-trip returning the re-ranked
|
|
1227
|
+
// global ∪ active-project union of long-term MEMORY and KNOWLEDGE. Scope
|
|
1228
|
+
// (tenant_id/subject_id) is derived server-side from the agent key; project_id is
|
|
1229
|
+
// the partition key (merges global with that project on BOTH sides).
|
|
1230
|
+
const payload = { query };
|
|
1231
|
+
if (project_id) payload.project_id = project_id;
|
|
1232
|
+
if (limit != null) payload.limit = limit;
|
|
1233
|
+
|
|
1234
|
+
const result = await apiCall(
|
|
1235
|
+
"POST",
|
|
1236
|
+
"/api/v1/recall",
|
|
1237
|
+
payload,
|
|
1238
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
1239
|
+
);
|
|
1240
|
+
// Surface both per-source metas (memory fallback/underfilled + knowledge degraded)
|
|
1241
|
+
// so the caller can tell a degraded recall from a genuinely empty scope.
|
|
1242
|
+
return toContent(result);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1163
1245
|
async function memoryList({ limit, offset, include_superseded, all_subjects }) {
|
|
1164
1246
|
// all_subjects is superadmin-only server-side; a non-superadmin key sending
|
|
1165
1247
|
// this is ignored (falls back to its own subject) rather than erroring — the
|
|
@@ -1200,6 +1282,24 @@ async function memoryPromote({ session_id }) {
|
|
|
1200
1282
|
return toContent(result);
|
|
1201
1283
|
}
|
|
1202
1284
|
|
|
1285
|
+
async function memoryGraduate({ memory_id, re_scope }) {
|
|
1286
|
+
// Explicit memory→knowledge graduation (#411 Gap 3). Scope (tenant_id/subject_id)
|
|
1287
|
+
// is derived server-side from the agent key; memory_id identifies the caller's OWN
|
|
1288
|
+
// memory to graduate. The graduated article stays OWNER-visible (not peer-readable).
|
|
1289
|
+
// re_scope: "global" widens only the project partition (project → tenant-wide), NOT
|
|
1290
|
+
// visibility, and only on the memory's first graduation.
|
|
1291
|
+
const payload = { memory_id };
|
|
1292
|
+
if (re_scope) payload.re_scope = re_scope;
|
|
1293
|
+
|
|
1294
|
+
const result = await apiCall(
|
|
1295
|
+
"POST",
|
|
1296
|
+
"/api/v1/memory/graduate",
|
|
1297
|
+
payload,
|
|
1298
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
1299
|
+
);
|
|
1300
|
+
return toContent(result);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1203
1303
|
// --- Knowledge Management Tools (orch key) ---
|
|
1204
1304
|
|
|
1205
1305
|
async function knowledgePublish({ article_id }) {
|
|
@@ -1952,6 +2052,34 @@ const TOOLS = [
|
|
|
1952
2052
|
required: [],
|
|
1953
2053
|
},
|
|
1954
2054
|
},
|
|
2055
|
+
{
|
|
2056
|
+
name: "resolve_project",
|
|
2057
|
+
description:
|
|
2058
|
+
"Resolve a repo to its project in one cheap call. Provide any of slug, " +
|
|
2059
|
+
"repo_url (git@github.com:owner/repo.git, https://github.com/owner/repo, " +
|
|
2060
|
+
"and bare owner/repo all match), or name. Precedence: slug > repo_url > " +
|
|
2061
|
+
"name; first match wins. Returns the project (use its id to scope " +
|
|
2062
|
+
"captures/recall), 404 not_found if nothing matches, 422 no_identifier " +
|
|
2063
|
+
"if none supplied.",
|
|
2064
|
+
inputSchema: {
|
|
2065
|
+
type: "object",
|
|
2066
|
+
properties: {
|
|
2067
|
+
slug: {
|
|
2068
|
+
type: "string",
|
|
2069
|
+
description: "Exact project slug (often the repo basename).",
|
|
2070
|
+
},
|
|
2071
|
+
repo_url: {
|
|
2072
|
+
type: "string",
|
|
2073
|
+
description: "Git remote URL or bare owner/repo.",
|
|
2074
|
+
},
|
|
2075
|
+
name: {
|
|
2076
|
+
type: "string",
|
|
2077
|
+
description: "Exact project name (case-insensitive).",
|
|
2078
|
+
},
|
|
2079
|
+
},
|
|
2080
|
+
required: [],
|
|
2081
|
+
},
|
|
2082
|
+
},
|
|
1955
2083
|
{
|
|
1956
2084
|
name: "create_project",
|
|
1957
2085
|
description: "Create a new project in the current tenant.",
|
|
@@ -3005,6 +3133,109 @@ const TOOLS = [
|
|
|
3005
3133
|
required: [],
|
|
3006
3134
|
},
|
|
3007
3135
|
},
|
|
3136
|
+
{
|
|
3137
|
+
name: "knowledge_hybrid_search",
|
|
3138
|
+
description:
|
|
3139
|
+
"Resolve a topic to a SINGLE best answer WITH provenance — the hybrid " +
|
|
3140
|
+
"retrieval entrypoint. Runs the combined keyword+semantic search over the full " +
|
|
3141
|
+
"ranked pool, then decides whether a governed CURATED source actually answers. " +
|
|
3142
|
+
"The verdict is meta.provenance: 'curated' means a curated, canonical article " +
|
|
3143
|
+
"answers — TRUST IT (it is guaranteed first in the results and meta.curated_article_id " +
|
|
3144
|
+
"points at it); 'retrieved' means no curated source cleared the bar, so the answer " +
|
|
3145
|
+
"is the best semantic/keyword match (a fuzzy fallback — verify before relying on it, " +
|
|
3146
|
+
"meta.curated_article_id is null). meta.confidence is the winning candidate's absolute " +
|
|
3147
|
+
"score for its provenance class. Prefer this over knowledge_search when you want ONE " +
|
|
3148
|
+
"trustworthy answer plus its provenance rather than a ranked list to triage yourself; " +
|
|
3149
|
+
"use knowledge_search when you want to browse/enumerate matches. Additive — existing " +
|
|
3150
|
+
"knowledge tools are unchanged. If semantic ranking is unavailable it degrades to " +
|
|
3151
|
+
"keyword-only (meta.fallback/fallback_reason), same as knowledge_search.",
|
|
3152
|
+
inputSchema: {
|
|
3153
|
+
type: "object",
|
|
3154
|
+
properties: {
|
|
3155
|
+
query: {
|
|
3156
|
+
type: "string",
|
|
3157
|
+
description: "The topic/question to resolve (max 500 characters). Required.",
|
|
3158
|
+
},
|
|
3159
|
+
project_id: {
|
|
3160
|
+
type: "string",
|
|
3161
|
+
format: "uuid",
|
|
3162
|
+
description: "Optional: scope to a project UUID.",
|
|
3163
|
+
},
|
|
3164
|
+
category: {
|
|
3165
|
+
type: "string",
|
|
3166
|
+
description: "Optional: filter by category.",
|
|
3167
|
+
},
|
|
3168
|
+
tags: {
|
|
3169
|
+
type: "string",
|
|
3170
|
+
description: "Optional: comma-separated tags to filter by.",
|
|
3171
|
+
},
|
|
3172
|
+
match: {
|
|
3173
|
+
type: "string",
|
|
3174
|
+
enum: ["any", "all"],
|
|
3175
|
+
description: "Optional: tag match mode — 'any' (default, OR) or 'all' (AND, every tag).",
|
|
3176
|
+
},
|
|
3177
|
+
limit: {
|
|
3178
|
+
type: "integer",
|
|
3179
|
+
description: "Optional: max results to return (default 10).",
|
|
3180
|
+
},
|
|
3181
|
+
offset: {
|
|
3182
|
+
type: "integer",
|
|
3183
|
+
description: "Optional: results to skip for pagination (default 0).",
|
|
3184
|
+
},
|
|
3185
|
+
},
|
|
3186
|
+
required: ["query"],
|
|
3187
|
+
},
|
|
3188
|
+
},
|
|
3189
|
+
{
|
|
3190
|
+
name: "knowledge_progressive_index",
|
|
3191
|
+
description:
|
|
3192
|
+
"Progressive disclosure — get a CHEAP, capped index of what's relevant to a topic " +
|
|
3193
|
+
"(compact stubs: id/title/category/summary, NO bodies), then open only what you need " +
|
|
3194
|
+
"with knowledge_progressive_drill. Curated sources are preferred and hub-linked " +
|
|
3195
|
+
"neighbors are enriched in; results are capped at a configured top-K (meta.truncated " +
|
|
3196
|
+
"is true when the candidate pool exceeded it). Use this to survey a topic without " +
|
|
3197
|
+
"flooding your context with full article bodies — it's the index half of the hybrid " +
|
|
3198
|
+
"interface. Follow up on a stub's id with knowledge_progressive_drill to read the body.",
|
|
3199
|
+
inputSchema: {
|
|
3200
|
+
type: "object",
|
|
3201
|
+
properties: {
|
|
3202
|
+
topic: {
|
|
3203
|
+
type: "string",
|
|
3204
|
+
description: "The topic to index (max 500 characters). Required.",
|
|
3205
|
+
},
|
|
3206
|
+
category: {
|
|
3207
|
+
type: "string",
|
|
3208
|
+
description: "Optional: filter by category.",
|
|
3209
|
+
},
|
|
3210
|
+
limit: {
|
|
3211
|
+
type: "integer",
|
|
3212
|
+
description: "Optional: top-K override (clamped to the configured cap).",
|
|
3213
|
+
},
|
|
3214
|
+
},
|
|
3215
|
+
required: ["topic"],
|
|
3216
|
+
},
|
|
3217
|
+
},
|
|
3218
|
+
{
|
|
3219
|
+
name: "knowledge_progressive_drill",
|
|
3220
|
+
description:
|
|
3221
|
+
"Drill into one stub from knowledge_progressive_index — returns the FULL article " +
|
|
3222
|
+
"body for the given id, scope-enforced. Resolves both tenant-owned articles and " +
|
|
3223
|
+
"published system canonicals (the same set the index surfaces). This is the drill " +
|
|
3224
|
+
"half of progressive disclosure: index cheaply, then open only the article(s) you " +
|
|
3225
|
+
"need. (knowledge_get works for tenant articles too; use this to also reach the " +
|
|
3226
|
+
"system canonicals the progressive index can surface.)",
|
|
3227
|
+
inputSchema: {
|
|
3228
|
+
type: "object",
|
|
3229
|
+
properties: {
|
|
3230
|
+
article_id: {
|
|
3231
|
+
type: "string",
|
|
3232
|
+
format: "uuid",
|
|
3233
|
+
description: "The UUID of the article to open (from a progressive index stub).",
|
|
3234
|
+
},
|
|
3235
|
+
},
|
|
3236
|
+
required: ["article_id"],
|
|
3237
|
+
},
|
|
3238
|
+
},
|
|
3008
3239
|
{
|
|
3009
3240
|
name: "knowledge_get",
|
|
3010
3241
|
description:
|
|
@@ -3335,6 +3566,45 @@ const TOOLS = [
|
|
|
3335
3566
|
required: ["query"],
|
|
3336
3567
|
},
|
|
3337
3568
|
},
|
|
3569
|
+
{
|
|
3570
|
+
name: "recall_context",
|
|
3571
|
+
description:
|
|
3572
|
+
"MERGED recall in ONE round-trip: the re-ranked global ∪ active-project union of " +
|
|
3573
|
+
"your long-term MEMORY (private working state) AND the shared KNOWLEDGE wiki for " +
|
|
3574
|
+
"`query` — instead of calling memory_recall and knowledge_context/knowledge_search " +
|
|
3575
|
+
"separately and merging by hand (#411 Gap 2). Both sides merge global with the " +
|
|
3576
|
+
"active project: pass project_id to include that project's rows alongside global " +
|
|
3577
|
+
"ones on BOTH sides (another project's rows are excluded); omit it for global-only. " +
|
|
3578
|
+
"project_id is a PARTITION key, NOT isolation — scope (tenant/subject) is resolved " +
|
|
3579
|
+
"server-side from your key. Returns `data` (merged, each item tagged source: " +
|
|
3580
|
+
"memory|knowledge, sorted by a heuristically-comparable score DESC) PLUS the " +
|
|
3581
|
+
"untouched per-source `memory` and `knowledge` envelopes so you can re-rank. " +
|
|
3582
|
+
"Cross-source scores are heuristic, not calibrated. If the knowledge search " +
|
|
3583
|
+
"degrades (embedding unavailable) or errors, the memory side is still returned and " +
|
|
3584
|
+
"meta.degraded is true — never a hard failure.",
|
|
3585
|
+
inputSchema: {
|
|
3586
|
+
type: "object",
|
|
3587
|
+
properties: {
|
|
3588
|
+
query: {
|
|
3589
|
+
type: "string",
|
|
3590
|
+
description: "Text to embed / match against on BOTH the memory and knowledge sides.",
|
|
3591
|
+
},
|
|
3592
|
+
project_id: {
|
|
3593
|
+
type: "string",
|
|
3594
|
+
format: "uuid",
|
|
3595
|
+
description:
|
|
3596
|
+
"Optional: partition scope. Present → both sides return global ∪ that project; " +
|
|
3597
|
+
"omit → global-only.",
|
|
3598
|
+
},
|
|
3599
|
+
limit: {
|
|
3600
|
+
type: "integer",
|
|
3601
|
+
description:
|
|
3602
|
+
"Optional: overall merged page size, clamped to [1, 50] (default 10).",
|
|
3603
|
+
},
|
|
3604
|
+
},
|
|
3605
|
+
required: ["query"],
|
|
3606
|
+
},
|
|
3607
|
+
},
|
|
3338
3608
|
{
|
|
3339
3609
|
name: "memory_list",
|
|
3340
3610
|
description:
|
|
@@ -3413,6 +3683,45 @@ const TOOLS = [
|
|
|
3413
3683
|
required: ["session_id"],
|
|
3414
3684
|
},
|
|
3415
3685
|
},
|
|
3686
|
+
{
|
|
3687
|
+
name: "memory_graduate",
|
|
3688
|
+
description:
|
|
3689
|
+
"Graduate ONE of your long-term memories into a durable Knowledge Wiki article — the " +
|
|
3690
|
+
"explicit, on-demand version of the hourly graduation sweep. Use when a private memory " +
|
|
3691
|
+
"has proven valuable enough to become durable, curated knowledge. IMPORTANT: the " +
|
|
3692
|
+
"graduated article stays OWNER-VISIBLE (metadata.visibility=owner, keyed to your " +
|
|
3693
|
+
"subject) — discoverable by YOU, NOT peer-readable; graduation does NOT share a memory " +
|
|
3694
|
+
"with teammates, and re_scope only widens project scope, not visibility. Scope " +
|
|
3695
|
+
"(tenant_id/subject_id) is resolved server-side from your API key: you can only " +
|
|
3696
|
+
"graduate your OWN memory; a foreign/unknown memory_id returns 404 (no cross-subject " +
|
|
3697
|
+
"leak). By default the article inherits the memory's project scope; pass " +
|
|
3698
|
+
're_scope: "global" to promote a PROJECT memory to a tenant-wide (global) article — ' +
|
|
3699
|
+
"only valid on the memory's FIRST graduation (re_scope: global on an already-graduated " +
|
|
3700
|
+
"PROJECT memory returns 409 already_graduated; on an already-graduated GLOBAL memory it " +
|
|
3701
|
+
"is an idempotent no-op → 200). The article is DEDUPED by the novelty gate: the " +
|
|
3702
|
+
'response `data.verdict` is "created" (novel → published) or "gated_to_draft" ' +
|
|
3703
|
+
'(near-duplicate → review draft) with a new article, or "duplicate"/"deduplicated" ' +
|
|
3704
|
+
"(content already represented → the canonical article, nothing created). Returns 503 " +
|
|
3705
|
+
"gate_unavailable if the embedding backend is down — retry later.",
|
|
3706
|
+
inputSchema: {
|
|
3707
|
+
type: "object",
|
|
3708
|
+
properties: {
|
|
3709
|
+
memory_id: {
|
|
3710
|
+
type: "string",
|
|
3711
|
+
description: "UUID of your own long-term memory to graduate into a knowledge article.",
|
|
3712
|
+
},
|
|
3713
|
+
re_scope: {
|
|
3714
|
+
type: "string",
|
|
3715
|
+
enum: ["inherit", "global"],
|
|
3716
|
+
description:
|
|
3717
|
+
'Article scope. "inherit" (default) keeps the memory\'s project scope; ' +
|
|
3718
|
+
'"global" promotes a PROJECT memory to a tenant-wide (global) article ' +
|
|
3719
|
+
"(only on its first graduation).",
|
|
3720
|
+
},
|
|
3721
|
+
},
|
|
3722
|
+
required: ["memory_id"],
|
|
3723
|
+
},
|
|
3724
|
+
},
|
|
3416
3725
|
|
|
3417
3726
|
// Knowledge Management Tools (orchestrator key)
|
|
3418
3727
|
{
|
|
@@ -4545,6 +4854,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4545
4854
|
case "list_projects":
|
|
4546
4855
|
return await listProjects(args);
|
|
4547
4856
|
|
|
4857
|
+
case "resolve_project":
|
|
4858
|
+
return await resolveProject(args);
|
|
4859
|
+
|
|
4548
4860
|
case "create_project":
|
|
4549
4861
|
return await createProject(args);
|
|
4550
4862
|
|
|
@@ -4654,6 +4966,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4654
4966
|
case "knowledge_search":
|
|
4655
4967
|
return await knowledgeSearch(args);
|
|
4656
4968
|
|
|
4969
|
+
case "knowledge_hybrid_search":
|
|
4970
|
+
return await knowledgeHybridSearch(args);
|
|
4971
|
+
|
|
4972
|
+
case "knowledge_progressive_index":
|
|
4973
|
+
return await knowledgeProgressiveIndex(args);
|
|
4974
|
+
|
|
4975
|
+
case "knowledge_progressive_drill":
|
|
4976
|
+
return await knowledgeProgressiveDrill(args);
|
|
4977
|
+
|
|
4657
4978
|
case "knowledge_list":
|
|
4658
4979
|
return await knowledgeList(args);
|
|
4659
4980
|
|
|
@@ -4675,6 +4996,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4675
4996
|
|
|
4676
4997
|
case "memory_recall":
|
|
4677
4998
|
return await memoryRecall(args);
|
|
4999
|
+
case "recall_context":
|
|
5000
|
+
return await recallContext(args);
|
|
4678
5001
|
|
|
4679
5002
|
case "memory_list":
|
|
4680
5003
|
return await memoryList(args);
|
|
@@ -4685,6 +5008,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4685
5008
|
case "memory_promote":
|
|
4686
5009
|
return await memoryPromote(args);
|
|
4687
5010
|
|
|
5011
|
+
case "memory_graduate":
|
|
5012
|
+
return await memoryGraduate(args);
|
|
5013
|
+
|
|
4688
5014
|
// Knowledge Management Tools
|
|
4689
5015
|
case "knowledge_publish":
|
|
4690
5016
|
return await knowledgePublish(args);
|