loopctl-mcp-server 2.113.0 → 2.115.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 +2 -1
- package/index.js +38 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -199,7 +199,8 @@ tool parameter.
|
|
|
199
199
|
| `list_projects` | List all projects in the current tenant. |
|
|
200
200
|
| `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. |
|
|
201
201
|
| `create_project` | Create a new **work** project (epics/stories/custody). Requires orchestrator+ **and** a `human_anchored` tenant; an agent-rooted tenant gets `403 custody_tier_required` and should use `create_kb_scope`. |
|
|
202
|
-
| `delete_project` | **
|
|
202
|
+
| `delete_project` | **Needs a user-role key:** `LOOPCTL_USER_KEY` verbatim when set, else the default key (orchestrator role is 403 `insufficient_role`). Archives the project (status `archived`); no rows are removed and its slug stays taken. |
|
|
203
|
+
| `restore_project` | Restore an archived `kind: work` project, the reverse of `delete_project`; audited as `unarchived`. Orchestrator role or higher on a human-anchored tenant. Rejected (422) at the tenant's `max_projects` cap and for a kb scope (use `restore_kb_scope`). The only way to change a project's status back: PATCH refuses `status` with 422. |
|
|
203
204
|
| `get_progress` | Get progress summary for a project, including story counts by status. Pass `include_cost=true` for cost data. |
|
|
204
205
|
| `import_stories` | Import stories into a project from a structured payload (Epic 12 import format). Pass `merge: true` to add stories to epics that already exist (otherwise duplicates return 409). For large payloads, use `payload_path` to read JSON from disk instead of passing it inline. With `merge: true`, `report_orphans: true` adds `stories_orphaned` (the project's stories the payload does not mention; nothing is detached) — a signal only on a FULL round-trip of an export; without it the key is absent (#880). |
|
|
205
206
|
|
package/index.js
CHANGED
|
@@ -946,13 +946,23 @@ async function channelGraduate({ post_id, title, tags, category }) {
|
|
|
946
946
|
return toContent(result);
|
|
947
947
|
}
|
|
948
948
|
|
|
949
|
+
// Pinned like `userKeyApiCall` (LOOPCTL_USER_KEY verbatim when set, so a global
|
|
950
|
+
// LOOPCTL_API_KEY cannot displace it), spelled out so the route sweep sees the call.
|
|
949
951
|
async function deleteProject({ project_id }) {
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
952
|
+
const path = `/api/v1/projects/${project_id}`;
|
|
953
|
+
const key = process.env.LOOPCTL_USER_KEY;
|
|
954
|
+
|
|
955
|
+
const result = key
|
|
956
|
+
? await apiCall("DELETE", path, null, key, { exactKey: true, keyHint: "LOOPCTL_USER_KEY" })
|
|
957
|
+
: await apiCall("DELETE", path, null);
|
|
958
|
+
|
|
959
|
+
return toContent(result);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// The reverse of deleteProject for a kind: work project. Default key: the route is
|
|
963
|
+
// orchestrator+ (a user key passes too), so no role pin is needed.
|
|
964
|
+
async function restoreProject({ project_id }) {
|
|
965
|
+
const result = await apiCall("POST", `/api/v1/projects/${project_id}/restore`, {});
|
|
956
966
|
return toContent(result);
|
|
957
967
|
}
|
|
958
968
|
|
|
@@ -1125,8 +1135,8 @@ async function getStory({ story_id }) {
|
|
|
1125
1135
|
// Reads travel on the default key (any role passes). Create and update go through
|
|
1126
1136
|
// `orchestratorPinnedApiCall`: the named orchestrator or user key VERBATIM, so a global
|
|
1127
1137
|
// LOOPCTL_API_KEY of a lesser role cannot displace it, and the global key when neither is set. The deletes go through `userKeyApiCall`, pinned to LOOPCTL_USER_KEY so a
|
|
1128
|
-
// global LOOPCTL_API_KEY cannot displace it: they destroy rows.
|
|
1129
|
-
// pinned
|
|
1138
|
+
// global LOOPCTL_API_KEY cannot displace it: they destroy rows. `delete_project` (an
|
|
1139
|
+
// archive) is pinned the same way, because its gate is `role: :user`.
|
|
1130
1140
|
|
|
1131
1141
|
// ONE pinning rule for the epic and story writes: the env var `hint()` names is sent VERBATIM
|
|
1132
1142
|
// when it is set, so a global LOOPCTL_API_KEY of a lesser role cannot displace it, and with none
|
|
@@ -4239,13 +4249,28 @@ const TOOLS = [
|
|
|
4239
4249
|
{
|
|
4240
4250
|
name: "delete_project",
|
|
4241
4251
|
description:
|
|
4242
|
-
"
|
|
4252
|
+
"ARCHIVE a project (DELETE /api/v1/projects/:id sets status to archived; it removes no rows). Its epics, stories and audit entries stay, and an already-archived project is a no-op. The SLUG stays taken: slugs are unique per tenant across archived projects too, resolve_project by that slug still returns the archived project, create_project with it is refused 422, and no API call changes or releases a slug, so a new project needs a different one. The way back: restore_project for a work project, restore_kb_scope for a kb scope. Sent with LOOPCTL_USER_KEY verbatim when it is set (a global LOOPCTL_API_KEY does not displace it), else with the default key. Refusals: 403 insufficient_role below user role, 403 custody_tier_required on an agent-rooted tenant (the human-anchor gate), 404 for an id not in your tenant.",
|
|
4243
4253
|
inputSchema: {
|
|
4244
4254
|
type: "object",
|
|
4245
4255
|
properties: {
|
|
4246
4256
|
project_id: {
|
|
4247
4257
|
type: "string",
|
|
4248
|
-
description: "The UUID of the project to
|
|
4258
|
+
description: "The UUID of the project to archive.",
|
|
4259
|
+
},
|
|
4260
|
+
},
|
|
4261
|
+
required: ["project_id"],
|
|
4262
|
+
},
|
|
4263
|
+
},
|
|
4264
|
+
{
|
|
4265
|
+
name: "restore_project",
|
|
4266
|
+
description:
|
|
4267
|
+
"RESTORE an archived kind: work project (POST /api/v1/projects/:id/restore, the reverse of delete_project). Audited as unarchived; an already-active project comes back unchanged. Status cannot be changed any other way: PATCH /api/v1/projects/:id refuses a body carrying status with 422. Sent with the default key; the route needs orchestrator role or higher. Refusals: 422 when the tenant is at its max_projects cap (re-activating takes an active slot), 422 for a kind: kb scope (use restore_kb_scope), 403 insufficient_role below orchestrator role, 403 custody_tier_required on an agent-rooted tenant (the human-anchor gate), 404 for an id not in your tenant.",
|
|
4268
|
+
inputSchema: {
|
|
4269
|
+
type: "object",
|
|
4270
|
+
properties: {
|
|
4271
|
+
project_id: {
|
|
4272
|
+
type: "string",
|
|
4273
|
+
description: "The UUID of the archived work project to restore.",
|
|
4249
4274
|
},
|
|
4250
4275
|
},
|
|
4251
4276
|
required: ["project_id"],
|
|
@@ -10015,6 +10040,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
10015
10040
|
case "delete_project":
|
|
10016
10041
|
return await deleteProject(args);
|
|
10017
10042
|
|
|
10043
|
+
case "restore_project":
|
|
10044
|
+
return await restoreProject(args);
|
|
10045
|
+
|
|
10018
10046
|
case "get_progress":
|
|
10019
10047
|
return await getProgress(args);
|
|
10020
10048
|
|