loopctl-mcp-server 2.41.0 → 2.43.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 +5 -2
- package/index.js +201 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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. |
|
|
@@ -243,7 +244,9 @@ it is enforced server-side and a no-op for a non-superadmin key — see below.)
|
|
|
243
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`. |
|
|
244
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). |
|
|
245
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`. |
|
|
246
|
-
| `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`). |
|
|
247
250
|
|
|
248
251
|
### Knowledge Management Tools (orchestrator key)
|
|
249
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;
|
|
@@ -386,6 +402,17 @@ async function createProject({ name, slug, repo_url, description, tech_stack, mi
|
|
|
386
402
|
return toContent(result);
|
|
387
403
|
}
|
|
388
404
|
|
|
405
|
+
async function createKbScope({ name, slug, repo_url, description, tech_stack }) {
|
|
406
|
+
const body = { name, slug };
|
|
407
|
+
if (repo_url) body.repo_url = repo_url;
|
|
408
|
+
if (description) body.description = description;
|
|
409
|
+
if (tech_stack) body.tech_stack = tech_stack;
|
|
410
|
+
// Uses the AGENT key (not ORCH): a KB scope is agent-createable on the KB tier — that is
|
|
411
|
+
// the whole point. The server forces kind: :kb; a body-supplied kind is ignored.
|
|
412
|
+
const result = await apiCall("POST", "/api/v1/kb-scopes", body, process.env.LOOPCTL_AGENT_KEY);
|
|
413
|
+
return toContent(result);
|
|
414
|
+
}
|
|
415
|
+
|
|
389
416
|
async function deleteProject({ project_id }) {
|
|
390
417
|
const result = await apiCall(
|
|
391
418
|
"DELETE",
|
|
@@ -1206,6 +1233,26 @@ async function memoryRecall({ query, limit, include_superseded }) {
|
|
|
1206
1233
|
return toContent(result);
|
|
1207
1234
|
}
|
|
1208
1235
|
|
|
1236
|
+
async function recallContext({ query, project_id, limit }) {
|
|
1237
|
+
// Merged recall (#411 Gap 2): ONE round-trip returning the re-ranked
|
|
1238
|
+
// global ∪ active-project union of long-term MEMORY and KNOWLEDGE. Scope
|
|
1239
|
+
// (tenant_id/subject_id) is derived server-side from the agent key; project_id is
|
|
1240
|
+
// the partition key (merges global with that project on BOTH sides).
|
|
1241
|
+
const payload = { query };
|
|
1242
|
+
if (project_id) payload.project_id = project_id;
|
|
1243
|
+
if (limit != null) payload.limit = limit;
|
|
1244
|
+
|
|
1245
|
+
const result = await apiCall(
|
|
1246
|
+
"POST",
|
|
1247
|
+
"/api/v1/recall",
|
|
1248
|
+
payload,
|
|
1249
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
1250
|
+
);
|
|
1251
|
+
// Surface both per-source metas (memory fallback/underfilled + knowledge degraded)
|
|
1252
|
+
// so the caller can tell a degraded recall from a genuinely empty scope.
|
|
1253
|
+
return toContent(result);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1209
1256
|
async function memoryList({ limit, offset, include_superseded, all_subjects }) {
|
|
1210
1257
|
// all_subjects is superadmin-only server-side; a non-superadmin key sending
|
|
1211
1258
|
// this is ignored (falls back to its own subject) rather than erroring — the
|
|
@@ -1246,6 +1293,24 @@ async function memoryPromote({ session_id }) {
|
|
|
1246
1293
|
return toContent(result);
|
|
1247
1294
|
}
|
|
1248
1295
|
|
|
1296
|
+
async function memoryGraduate({ memory_id, re_scope }) {
|
|
1297
|
+
// Explicit memory→knowledge graduation (#411 Gap 3). Scope (tenant_id/subject_id)
|
|
1298
|
+
// is derived server-side from the agent key; memory_id identifies the caller's OWN
|
|
1299
|
+
// memory to graduate. The graduated article stays OWNER-visible (not peer-readable).
|
|
1300
|
+
// re_scope: "global" widens only the project partition (project → tenant-wide), NOT
|
|
1301
|
+
// visibility, and only on the memory's first graduation.
|
|
1302
|
+
const payload = { memory_id };
|
|
1303
|
+
if (re_scope) payload.re_scope = re_scope;
|
|
1304
|
+
|
|
1305
|
+
const result = await apiCall(
|
|
1306
|
+
"POST",
|
|
1307
|
+
"/api/v1/memory/graduate",
|
|
1308
|
+
payload,
|
|
1309
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
1310
|
+
);
|
|
1311
|
+
return toContent(result);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1249
1314
|
// --- Knowledge Management Tools (orch key) ---
|
|
1250
1315
|
|
|
1251
1316
|
async function knowledgePublish({ article_id }) {
|
|
@@ -1998,6 +2063,34 @@ const TOOLS = [
|
|
|
1998
2063
|
required: [],
|
|
1999
2064
|
},
|
|
2000
2065
|
},
|
|
2066
|
+
{
|
|
2067
|
+
name: "resolve_project",
|
|
2068
|
+
description:
|
|
2069
|
+
"Resolve a repo to its project in one cheap call. Provide any of slug, " +
|
|
2070
|
+
"repo_url (git@github.com:owner/repo.git, https://github.com/owner/repo, " +
|
|
2071
|
+
"and bare owner/repo all match), or name. Precedence: slug > repo_url > " +
|
|
2072
|
+
"name; first match wins. Returns the project (use its id to scope " +
|
|
2073
|
+
"captures/recall), 404 not_found if nothing matches, 422 no_identifier " +
|
|
2074
|
+
"if none supplied.",
|
|
2075
|
+
inputSchema: {
|
|
2076
|
+
type: "object",
|
|
2077
|
+
properties: {
|
|
2078
|
+
slug: {
|
|
2079
|
+
type: "string",
|
|
2080
|
+
description: "Exact project slug (often the repo basename).",
|
|
2081
|
+
},
|
|
2082
|
+
repo_url: {
|
|
2083
|
+
type: "string",
|
|
2084
|
+
description: "Git remote URL or bare owner/repo.",
|
|
2085
|
+
},
|
|
2086
|
+
name: {
|
|
2087
|
+
type: "string",
|
|
2088
|
+
description: "Exact project name (case-insensitive).",
|
|
2089
|
+
},
|
|
2090
|
+
},
|
|
2091
|
+
required: [],
|
|
2092
|
+
},
|
|
2093
|
+
},
|
|
2001
2094
|
{
|
|
2002
2095
|
name: "create_project",
|
|
2003
2096
|
description: "Create a new project in the current tenant.",
|
|
@@ -2018,6 +2111,25 @@ const TOOLS = [
|
|
|
2018
2111
|
required: ["name", "slug"],
|
|
2019
2112
|
},
|
|
2020
2113
|
},
|
|
2114
|
+
{
|
|
2115
|
+
name: "create_kb_scope",
|
|
2116
|
+
description:
|
|
2117
|
+
"Create a knowledge-only project scope (kind: kb) for the current tenant. Unlike create_project (work project, orchestrator+ / human-anchored), this is available to an agent-rooted (KB-tier) tenant with an agent key: a kb scope carries NO chain-of-custody / work-breakdown surface (it cannot host epics/stories/dispatch/ui-tests), it exists only to partition knowledge articles by repo so captured/created articles can be project-scoped. Then resolve_project by its repo_url/slug and pass the returned id as project_id on article/knowledge writes. Counts toward the tenant's max_projects budget.",
|
|
2118
|
+
inputSchema: {
|
|
2119
|
+
type: "object",
|
|
2120
|
+
properties: {
|
|
2121
|
+
name: { type: "string", description: "Scope name (often the repo name)." },
|
|
2122
|
+
slug: { type: "string", description: "URL-safe slug (often the repo basename)." },
|
|
2123
|
+
repo_url: {
|
|
2124
|
+
type: "string",
|
|
2125
|
+
description: "Repo URL, so resolve_project can map a repo to this scope.",
|
|
2126
|
+
},
|
|
2127
|
+
description: { type: "string", description: "Scope description." },
|
|
2128
|
+
tech_stack: { type: "string", description: "Tech stack summary." },
|
|
2129
|
+
},
|
|
2130
|
+
required: ["name", "slug"],
|
|
2131
|
+
},
|
|
2132
|
+
},
|
|
2021
2133
|
{
|
|
2022
2134
|
name: "delete_project",
|
|
2023
2135
|
description:
|
|
@@ -3484,6 +3596,45 @@ const TOOLS = [
|
|
|
3484
3596
|
required: ["query"],
|
|
3485
3597
|
},
|
|
3486
3598
|
},
|
|
3599
|
+
{
|
|
3600
|
+
name: "recall_context",
|
|
3601
|
+
description:
|
|
3602
|
+
"MERGED recall in ONE round-trip: the re-ranked global ∪ active-project union of " +
|
|
3603
|
+
"your long-term MEMORY (private working state) AND the shared KNOWLEDGE wiki for " +
|
|
3604
|
+
"`query` — instead of calling memory_recall and knowledge_context/knowledge_search " +
|
|
3605
|
+
"separately and merging by hand (#411 Gap 2). Both sides merge global with the " +
|
|
3606
|
+
"active project: pass project_id to include that project's rows alongside global " +
|
|
3607
|
+
"ones on BOTH sides (another project's rows are excluded); omit it for global-only. " +
|
|
3608
|
+
"project_id is a PARTITION key, NOT isolation — scope (tenant/subject) is resolved " +
|
|
3609
|
+
"server-side from your key. Returns `data` (merged, each item tagged source: " +
|
|
3610
|
+
"memory|knowledge, sorted by a heuristically-comparable score DESC) PLUS the " +
|
|
3611
|
+
"untouched per-source `memory` and `knowledge` envelopes so you can re-rank. " +
|
|
3612
|
+
"Cross-source scores are heuristic, not calibrated. If the knowledge search " +
|
|
3613
|
+
"degrades (embedding unavailable) or errors, the memory side is still returned and " +
|
|
3614
|
+
"meta.degraded is true — never a hard failure.",
|
|
3615
|
+
inputSchema: {
|
|
3616
|
+
type: "object",
|
|
3617
|
+
properties: {
|
|
3618
|
+
query: {
|
|
3619
|
+
type: "string",
|
|
3620
|
+
description: "Text to embed / match against on BOTH the memory and knowledge sides.",
|
|
3621
|
+
},
|
|
3622
|
+
project_id: {
|
|
3623
|
+
type: "string",
|
|
3624
|
+
format: "uuid",
|
|
3625
|
+
description:
|
|
3626
|
+
"Optional: partition scope. Present → both sides return global ∪ that project; " +
|
|
3627
|
+
"omit → global-only.",
|
|
3628
|
+
},
|
|
3629
|
+
limit: {
|
|
3630
|
+
type: "integer",
|
|
3631
|
+
description:
|
|
3632
|
+
"Optional: overall merged page size, clamped to [1, 50] (default 10).",
|
|
3633
|
+
},
|
|
3634
|
+
},
|
|
3635
|
+
required: ["query"],
|
|
3636
|
+
},
|
|
3637
|
+
},
|
|
3487
3638
|
{
|
|
3488
3639
|
name: "memory_list",
|
|
3489
3640
|
description:
|
|
@@ -3562,6 +3713,45 @@ const TOOLS = [
|
|
|
3562
3713
|
required: ["session_id"],
|
|
3563
3714
|
},
|
|
3564
3715
|
},
|
|
3716
|
+
{
|
|
3717
|
+
name: "memory_graduate",
|
|
3718
|
+
description:
|
|
3719
|
+
"Graduate ONE of your long-term memories into a durable Knowledge Wiki article — the " +
|
|
3720
|
+
"explicit, on-demand version of the hourly graduation sweep. Use when a private memory " +
|
|
3721
|
+
"has proven valuable enough to become durable, curated knowledge. IMPORTANT: the " +
|
|
3722
|
+
"graduated article stays OWNER-VISIBLE (metadata.visibility=owner, keyed to your " +
|
|
3723
|
+
"subject) — discoverable by YOU, NOT peer-readable; graduation does NOT share a memory " +
|
|
3724
|
+
"with teammates, and re_scope only widens project scope, not visibility. Scope " +
|
|
3725
|
+
"(tenant_id/subject_id) is resolved server-side from your API key: you can only " +
|
|
3726
|
+
"graduate your OWN memory; a foreign/unknown memory_id returns 404 (no cross-subject " +
|
|
3727
|
+
"leak). By default the article inherits the memory's project scope; pass " +
|
|
3728
|
+
're_scope: "global" to promote a PROJECT memory to a tenant-wide (global) article — ' +
|
|
3729
|
+
"only valid on the memory's FIRST graduation (re_scope: global on an already-graduated " +
|
|
3730
|
+
"PROJECT memory returns 409 already_graduated; on an already-graduated GLOBAL memory it " +
|
|
3731
|
+
"is an idempotent no-op → 200). The article is DEDUPED by the novelty gate: the " +
|
|
3732
|
+
'response `data.verdict` is "created" (novel → published) or "gated_to_draft" ' +
|
|
3733
|
+
'(near-duplicate → review draft) with a new article, or "duplicate"/"deduplicated" ' +
|
|
3734
|
+
"(content already represented → the canonical article, nothing created). Returns 503 " +
|
|
3735
|
+
"gate_unavailable if the embedding backend is down — retry later.",
|
|
3736
|
+
inputSchema: {
|
|
3737
|
+
type: "object",
|
|
3738
|
+
properties: {
|
|
3739
|
+
memory_id: {
|
|
3740
|
+
type: "string",
|
|
3741
|
+
description: "UUID of your own long-term memory to graduate into a knowledge article.",
|
|
3742
|
+
},
|
|
3743
|
+
re_scope: {
|
|
3744
|
+
type: "string",
|
|
3745
|
+
enum: ["inherit", "global"],
|
|
3746
|
+
description:
|
|
3747
|
+
'Article scope. "inherit" (default) keeps the memory\'s project scope; ' +
|
|
3748
|
+
'"global" promotes a PROJECT memory to a tenant-wide (global) article ' +
|
|
3749
|
+
"(only on its first graduation).",
|
|
3750
|
+
},
|
|
3751
|
+
},
|
|
3752
|
+
required: ["memory_id"],
|
|
3753
|
+
},
|
|
3754
|
+
},
|
|
3565
3755
|
|
|
3566
3756
|
// Knowledge Management Tools (orchestrator key)
|
|
3567
3757
|
{
|
|
@@ -4694,9 +4884,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4694
4884
|
case "list_projects":
|
|
4695
4885
|
return await listProjects(args);
|
|
4696
4886
|
|
|
4887
|
+
case "resolve_project":
|
|
4888
|
+
return await resolveProject(args);
|
|
4889
|
+
|
|
4697
4890
|
case "create_project":
|
|
4698
4891
|
return await createProject(args);
|
|
4699
4892
|
|
|
4893
|
+
case "create_kb_scope":
|
|
4894
|
+
return await createKbScope(args);
|
|
4895
|
+
|
|
4700
4896
|
case "delete_project":
|
|
4701
4897
|
return await deleteProject(args);
|
|
4702
4898
|
|
|
@@ -4833,6 +5029,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4833
5029
|
|
|
4834
5030
|
case "memory_recall":
|
|
4835
5031
|
return await memoryRecall(args);
|
|
5032
|
+
case "recall_context":
|
|
5033
|
+
return await recallContext(args);
|
|
4836
5034
|
|
|
4837
5035
|
case "memory_list":
|
|
4838
5036
|
return await memoryList(args);
|
|
@@ -4843,6 +5041,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4843
5041
|
case "memory_promote":
|
|
4844
5042
|
return await memoryPromote(args);
|
|
4845
5043
|
|
|
5044
|
+
case "memory_graduate":
|
|
5045
|
+
return await memoryGraduate(args);
|
|
5046
|
+
|
|
4846
5047
|
// Knowledge Management Tools
|
|
4847
5048
|
case "knowledge_publish":
|
|
4848
5049
|
return await knowledgePublish(args);
|