loopctl-mcp-server 2.53.1 → 2.55.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 +14 -3
- package/index.js +173 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,6 +16,14 @@ Or run directly with npx:
|
|
|
16
16
|
npx loopctl-mcp-server
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
### Requirements
|
|
20
|
+
|
|
21
|
+
- **Node.js >= 20.6.0.** The server makes all outbound HTTPS calls through Node's
|
|
22
|
+
global `fetch` (undici). Node 20.6.0 is the first release where that transport
|
|
23
|
+
honors `NODE_EXTRA_CA_CERTS` (the setting used for a custom/self-signed loopctl
|
|
24
|
+
CA -- see Troubleshooting below). On older runtimes that env var is silently
|
|
25
|
+
ignored, so the floor is enforced via `engines` in `package.json`.
|
|
26
|
+
|
|
19
27
|
## Configuration
|
|
20
28
|
|
|
21
29
|
Add to your `.mcp.json` (Claude Code) or equivalent MCP config:
|
|
@@ -121,7 +129,7 @@ REST endpoint (`PATCH /api/v1/tenants/me/llm-config`), and the docs — so you (
|
|
|
121
129
|
autonomous agent) can self-remediate without a human. Full agent-tenant lifecycle:
|
|
122
130
|
[`docs/onboarding-agent-tenant.md`](../docs/onboarding-agent-tenant.md).
|
|
123
131
|
|
|
124
|
-
## Tools
|
|
132
|
+
## Tools
|
|
125
133
|
|
|
126
134
|
> Plus **per-tenant generated Context Retriever tools** (`cr_*`) appended
|
|
127
135
|
> dynamically at runtime — see [Dynamic per-tenant Context Retriever
|
|
@@ -297,7 +305,7 @@ it is enforced server-side and a no-op for a non-superadmin key — see below.)
|
|
|
297
305
|
| Tool | Description |
|
|
298
306
|
|---|---|
|
|
299
307
|
| `llm_config` | **Check your onboarding status.** Get the tenant's BYO LLM config: per-operation models and whether each key is set — `has_api_key` (Anthropic) / `has_embedding_key` (OpenAI embedding) — plus masked last-4 hints. Never returns a key. Requires **user** key. |
|
|
300
|
-
| `set_llm_config` | **First-time setup (do once).** Set/rotate the tenant's OWN **Anthropic `api_key`** (powers ingest) AND **OpenAI `embedding_api_key`** (powers semantic search) — both stored encrypted, never returned — plus the per-operation models (`extraction_model`/`classification_model`/`merge_model`/`embedding_model`). Any subset; partial-merge (omitting a key leaves it untouched). See [First-time setup](#first-time-setup--provision-your-byo-llm-keys). Requires **user** key. |
|
|
308
|
+
| `set_llm_config` | **First-time setup (do once).** Set/rotate the tenant's OWN **Anthropic `api_key`** (powers ingest) AND **OpenAI `embedding_api_key`** (powers semantic search) — both stored encrypted, never returned — plus the per-operation models (`extraction_model`/`classification_model`/`merge_model`/`embedding_model`). Any subset; partial-merge (omitting a key leaves it untouched). **Private tier (US-41.3):** also sets `chat_provider` (`anthropic` | `openai_compatible`), `chat_base_url`, `extraction_model` (required on that provider) and `chat_api_key` (optional — omit it for a keyless local server) so ingest extraction / classification / merge / memory promotion run against YOUR OWN OpenAI-compatible endpoint and document text never leaves your boundary. Every model the config resolves to is PROBED with a trivial completion before it is saved (a 422 persists nothing); the probe re-runs on any chat-surface change (provider, base_url, key rotation, model), a private-range host is refused by the SSRF guard, and moving to a NEW host needs a matching `chat_api_key` or `acknowledge_key_transmission: true`. See [First-time setup](#first-time-setup--provision-your-byo-llm-keys). Requires **user** key. |
|
|
301
309
|
| `knowledge_llm_usage` | Per-tenant LLM token-usage summary, grouped by operation + model + source_type + day over an optional `from`/`to` range (defaults to a 90-day lookback; effective window echoed in `meta.from`/`meta.to`), with `limit`/`offset` pagination. Record-only (no budget enforcement). Requires orchestrator key. |
|
|
302
310
|
|
|
303
311
|
### Knowledge Analytics Tools (orchestrator key)
|
|
@@ -324,6 +332,8 @@ nothing changes until a scope opts in.
|
|
|
324
332
|
| `declare_trusted_endpoint` | Declare a host you attest is YOUR OWN so a `local_only` scope can reach it. **This is an unverified tenant attestation, not network locality** — labelled as such everywhere. Three enforced constraints: PUBLIC addresses only (write time AND pin time), PURPOSE-SCOPED (`inference` / `webhook` / `ingest`), vendor hosts excluded. Carves nothing out of the SSRF denylist. Requires **user** key. |
|
|
325
333
|
| `revoke_trusted_endpoint` | Revoke a declaration. Invalidation is IMMEDIATE and cluster-wide — a revoked declaration does not keep working for the remainder of the pin TTL. Requires **user** key. |
|
|
326
334
|
| `egress_repin` | Recover from a `:pin_stale` error (your box got a new DHCP lease and the pinned address set changed — DISTINCT from `egress_blocked`). Re-resolves and re-pins the host. **Agent** key by design: requiring a human user-role write to recover would contradict loopctl's agent-native, no-UI design. |
|
|
335
|
+
| `custody_claim` | The recorded **egress custody claim** for one article or memory row: the append-only sequence of per-operation postures (create, each embed, each re-embed, each classification, each merge) with the endpoints resolved for THAT operation and their verdicts, plus the aggregate. Rides the existing hash-chained audit log + signed tree heads — each entry carries a `chain_position` and the leaf's `chain_entry_hash`, so `GET /api/v1/audit/sth/{tenant_id}/inclusion/{position}` proves inclusion of *that* leaf, and the leaf's payload names this row by a recomputable `posture_digest`. THREE states, only one an attestation: `no_claim_recorded`, `claim_pending`, `claim_recorded` (`complete` / `partial_history` / `incomplete`). Completeness is measured against a persisted per-row high-water mark, so losing the tail of a sequence is a gap, not a clean claim. `third_party_egress_on_covered_paths` is `false` only for NETWORK-local endpoints; a tenant-declared (unverified) endpoint yields `"tenant_declared_unverified"`. Attests ONLY to the endpoints loopctl called on the paths in `coverage` — never to what those endpoints did afterwards. **Agent** key. |
|
|
336
|
+
| `custody_failures` | Custody posture entries whose chain append was DROPPED after exhausting retries, plus `stale_pending` entries stranded by a flush that died outside its own final-attempt branch. Surfaced rather than silently absent: each `data` entry degrades its row's claim to `incomplete`, and a stranded entry would otherwise read as an in-flight claim forever. **Agent** key. |
|
|
327
337
|
|
|
328
338
|
### Discovery Tools
|
|
329
339
|
|
|
@@ -519,7 +529,8 @@ clean one-time bootstrap (no cross-key collision).
|
|
|
519
529
|
|
|
520
530
|
- Verify `LOOPCTL_SERVER` is set and reachable
|
|
521
531
|
- Check that the server URL includes the protocol (`https://`)
|
|
522
|
-
- If
|
|
532
|
+
- If your loopctl server uses a custom or self-signed CA, set `NODE_EXTRA_CA_CERTS=/path/to/ca.pem` in your environment so Node trusts that CA while keeping TLS certificate verification enabled. This requires **Node >= 20.6.0** (or the >= 18.19.0 backport): this server issues all requests through Node's global `fetch` (undici), which only began honoring `NODE_EXTRA_CA_CERTS` in those releases. On older runtimes the variable is silently ignored and you will still see `unable to verify the first certificate` / self-signed-cert errors -- upgrade Node rather than disabling verification.
|
|
533
|
+
- Do NOT set `NODE_TLS_REJECT_UNAUTHORIZED=0` -- it disables certificate verification for the entire Node process (all outbound TLS), exposing every connection to MITM, not just the loopctl one
|
|
523
534
|
|
|
524
535
|
### Authentication errors (401)
|
|
525
536
|
|
package/index.js
CHANGED
|
@@ -1852,6 +1852,10 @@ async function setLlmConfig({
|
|
|
1852
1852
|
merge_model,
|
|
1853
1853
|
embedding_api_key,
|
|
1854
1854
|
embedding_model,
|
|
1855
|
+
chat_provider,
|
|
1856
|
+
chat_base_url,
|
|
1857
|
+
chat_api_key,
|
|
1858
|
+
acknowledge_key_transmission,
|
|
1855
1859
|
}) {
|
|
1856
1860
|
const body = {};
|
|
1857
1861
|
if (api_key != null) body.api_key = api_key;
|
|
@@ -1860,6 +1864,15 @@ async function setLlmConfig({
|
|
|
1860
1864
|
if (merge_model !== undefined) body.merge_model = merge_model;
|
|
1861
1865
|
if (embedding_api_key != null) body.embedding_api_key = embedding_api_key;
|
|
1862
1866
|
if (embedding_model !== undefined) body.embedding_model = embedding_model;
|
|
1867
|
+
// US-41.3: pluggable OpenAI-compatible chat endpoint. The server PROBES the
|
|
1868
|
+
// endpoint with a trivial completion before persisting, and refuses an endpoint
|
|
1869
|
+
// change that carries neither a matching chat_api_key nor an explicit
|
|
1870
|
+
// acknowledge_key_transmission.
|
|
1871
|
+
if (chat_provider !== undefined) body.chat_provider = chat_provider;
|
|
1872
|
+
if (chat_base_url !== undefined) body.chat_base_url = chat_base_url;
|
|
1873
|
+
if (chat_api_key != null) body.chat_api_key = chat_api_key;
|
|
1874
|
+
if (acknowledge_key_transmission === true)
|
|
1875
|
+
body.acknowledge_key_transmission = true;
|
|
1863
1876
|
// PATCH (partial-merge) + EXACT user key (review #12, #13).
|
|
1864
1877
|
const result = await apiCall(
|
|
1865
1878
|
"PATCH",
|
|
@@ -1889,6 +1902,33 @@ async function egressPosture() {
|
|
|
1889
1902
|
return toContent(result);
|
|
1890
1903
|
}
|
|
1891
1904
|
|
|
1905
|
+
// --- US-41.7: witnessed custody claim ---------------------------------------
|
|
1906
|
+
//
|
|
1907
|
+
// A READ, at agent role for the same reason egress_posture is: verifying a
|
|
1908
|
+
// harvest AFTER the fact must work with the key an agent already holds.
|
|
1909
|
+
async function custodyClaim({ subject_type, subject_id } = {}) {
|
|
1910
|
+
if (!subject_type || !subject_id) {
|
|
1911
|
+
throw new Error("subject_type and subject_id are required");
|
|
1912
|
+
}
|
|
1913
|
+
const result = await apiCall(
|
|
1914
|
+
"GET",
|
|
1915
|
+
`/api/v1/custody/claims/${encodeURIComponent(subject_type)}/${encodeURIComponent(subject_id)}`,
|
|
1916
|
+
null,
|
|
1917
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
1918
|
+
);
|
|
1919
|
+
return toContent(result);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
async function custodyFailures() {
|
|
1923
|
+
const result = await apiCall(
|
|
1924
|
+
"GET",
|
|
1925
|
+
"/api/v1/custody/failures",
|
|
1926
|
+
null,
|
|
1927
|
+
process.env.LOOPCTL_AGENT_KEY,
|
|
1928
|
+
);
|
|
1929
|
+
return toContent(result);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1892
1932
|
async function setLocalOnly({ project_id, acknowledge } = {}) {
|
|
1893
1933
|
const body = {};
|
|
1894
1934
|
if (project_id != null) body.project_id = project_id;
|
|
@@ -4949,7 +4989,8 @@ const TOOLS = [
|
|
|
4949
4989
|
name: "egress_posture",
|
|
4950
4990
|
description:
|
|
4951
4991
|
"VERIFY BEFORE YOU HARVEST. Reports this instance's egress posture for YOUR tenant: " +
|
|
4952
|
-
"the resolved embedding and chat endpoints,
|
|
4992
|
+
"the resolved embedding and chat endpoints, EVERY webhook destination " +
|
|
4993
|
+
"(webhook_destinations), a locality VERDICT for each " +
|
|
4953
4994
|
"(network-local / 'tenant-declared (unverified attestation), not network-local' / " +
|
|
4954
4995
|
"non-local), your declared trusted endpoints with their purposes, per-scope " +
|
|
4955
4996
|
"local_only status, and any named posture defects. Endpoints are shown; KEYS NEVER " +
|
|
@@ -4958,10 +4999,70 @@ const TOOLS = [
|
|
|
4958
4999
|
"key you already have. NOTE the deployment allowlist CONTENTS are operator " +
|
|
4959
5000
|
"infrastructure and are NOT disclosed at agent role: you get only a boolean per " +
|
|
4960
5001
|
"endpoint saying whether its verdict came from the allowlist (contents at user+). " +
|
|
5002
|
+
"Webhook destinations follow the same split: HOST plus verdict plus " +
|
|
5003
|
+
"blocked_by_local_only at agent role, the FULL destination URL (endpoint) only at " +
|
|
5004
|
+
"user+ — a webhook path is frequently the credential. " +
|
|
4961
5005
|
"SCOPE OF THE GUARANTEE: fail-closed enforcement covers every outbound HTTP call " +
|
|
4962
|
-
"made by loopctl application code on
|
|
4963
|
-
"
|
|
4964
|
-
"separate mcp-server codebase, are outside the static
|
|
5006
|
+
"made by loopctl application code on every CONTENT-CARRYING path — model-provider " +
|
|
5007
|
+
"calls, the ingestion fetch, and webhook delivery (US-41.5). HTTP performed inside " +
|
|
5008
|
+
"a dependency, plus this separate mcp-server codebase, are outside the static " +
|
|
5009
|
+
"chokepoint check; the remaining non-content outbound paths are triaged in " +
|
|
5010
|
+
"docs/egress-guard.md.",
|
|
5011
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
5012
|
+
},
|
|
5013
|
+
{
|
|
5014
|
+
name: "custody_claim",
|
|
5015
|
+
description:
|
|
5016
|
+
"The recorded EGRESS CUSTODY CLAIM for one article or memory row: the append-only " +
|
|
5017
|
+
"sequence of per-operation postures (create, each embedding, each re-embed, each " +
|
|
5018
|
+
"classification/merge) with the endpoint loopctl resolved for THAT operation and its " +
|
|
5019
|
+
"locality verdict, plus the aggregate over them. Each claim rides the existing " +
|
|
5020
|
+
"hash-chained audit log and its signed tree heads — every recorded entry carries the " +
|
|
5021
|
+
"chain_position you can fetch an inclusion proof for at " +
|
|
5022
|
+
"GET /api/v1/audit/sth/{tenant_id}/inclusion/{position} and check against the public " +
|
|
5023
|
+
"STH with the tenant's published audit key. THREE STATES, and only one of them is an " +
|
|
5024
|
+
"attestation: 'no_claim_recorded' (no operation sequence was ever assigned — the row " +
|
|
5025
|
+
"predates recording or its scope is not marked local_only; this asserts NOTHING in " +
|
|
5026
|
+
"either direction), 'claim_pending' (sequences assigned, batch append not yet " +
|
|
5027
|
+
"flushed), and 'claim_recorded', itself 'complete', 'partial_history' or " +
|
|
5028
|
+
"'incomplete'. An INCOMPLETE sequence (a gap, a lost tail, or a dropped append) is " +
|
|
5029
|
+
"never reported as no-third-party-egress: an unrecorded operation may have called " +
|
|
5030
|
+
"any endpoint. Completeness is measured against a PERSISTED per-row high-water mark, " +
|
|
5031
|
+
"not against the rows that happen to survive, so truncating the sequence cannot " +
|
|
5032
|
+
"restore a satisfied claim. 'partial_history' means operation 0 is not this row's " +
|
|
5033
|
+
"creation — recording began after the row already existed (typically the scope was " +
|
|
5034
|
+
"marked local_only later), so loopctl has no record of how it was produced. " +
|
|
5035
|
+
"third_party_egress_on_covered_paths is `false` ONLY when every recorded endpoint " +
|
|
5036
|
+
"was NETWORK-LOCAL; when the sequence leans on a TENANT-DECLARED endpoint (a public " +
|
|
5037
|
+
"host the tenant merely attested is its own, which loopctl never verified) the value " +
|
|
5038
|
+
"is the string 'tenant_declared_unverified', not false. " +
|
|
5039
|
+
"SCOPE, precisely: the claim attests ONLY to the endpoints loopctl called for the " +
|
|
5040
|
+
"recorded operations on this row, on the egress paths enumerated in the `coverage` " +
|
|
5041
|
+
"field. It makes NO statement about what those endpoints did with the data " +
|
|
5042
|
+
"afterwards, and none about a path listed as uncovered. READ tool, AGENT role.",
|
|
5043
|
+
inputSchema: {
|
|
5044
|
+
type: "object",
|
|
5045
|
+
properties: {
|
|
5046
|
+
subject_type: {
|
|
5047
|
+
type: "string",
|
|
5048
|
+
enum: ["article", "memory"],
|
|
5049
|
+
description: "The kind of row the claim is bound to.",
|
|
5050
|
+
},
|
|
5051
|
+
subject_id: { type: "string", description: "The row's UUID." },
|
|
5052
|
+
},
|
|
5053
|
+
required: ["subject_type", "subject_id"],
|
|
5054
|
+
},
|
|
5055
|
+
},
|
|
5056
|
+
{
|
|
5057
|
+
name: "custody_failures",
|
|
5058
|
+
description:
|
|
5059
|
+
"Custody posture entries whose audit-chain append was DROPPED after exhausting " +
|
|
5060
|
+
"retries, plus (under `stale_pending`) entries that have been in flight longer than " +
|
|
5061
|
+
"the stale window. A recording failure is surfaced here rather than silently absent, " +
|
|
5062
|
+
"because a missing claim must never read as a satisfied one: every entry under " +
|
|
5063
|
+
"`data` degrades its row's claim to 'incomplete', and a stranded `stale_pending` " +
|
|
5064
|
+
"entry is one a flush stamped and then died on — it would otherwise read as an " +
|
|
5065
|
+
"in-flight claim indefinitely. READ tool, AGENT role.",
|
|
4965
5066
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
4966
5067
|
},
|
|
4967
5068
|
{
|
|
@@ -4972,10 +5073,13 @@ const TOOLS = [
|
|
|
4972
5073
|
"everywhere; nothing changes until a scope opts in. Scope resolution is " +
|
|
4973
5074
|
"MOST-RESTRICTIVE-WINS (project OR tenant) and a project can NEVER relax a tenant " +
|
|
4974
5075
|
"marking. MANDATORY PRE-FLIGHT: the call is REFUSED with 409 would_block_endpoints, " +
|
|
4975
|
-
"naming every endpoint that would become egress_blocked
|
|
5076
|
+
"naming every endpoint that would become egress_blocked — including every WEBHOOK " +
|
|
5077
|
+
"SUBSCRIPTION whose destination would be refused (kind: 'webhook', US-41.5) — " +
|
|
5078
|
+
"unless you pass " +
|
|
4976
5079
|
"acknowledge: true — because on a tenant still using vendor default endpoints this " +
|
|
4977
5080
|
"instantly stops embedding, extraction, classification and merge, and only a " +
|
|
4978
|
-
"human user-role key can undo it.
|
|
5081
|
+
"human user-role key can undo it. Subscriptions are never silently disabled: they " +
|
|
5082
|
+
"are either the reason for the refusal, or reported back to you on acknowledgement. Requires an ORCHESTRATOR key: tightening is safe " +
|
|
4979
5083
|
"to automate. CLEARING is a different tool (clear_local_only) and is user-only.",
|
|
4980
5084
|
inputSchema: {
|
|
4981
5085
|
type: "object",
|
|
@@ -5114,7 +5218,11 @@ const TOOLS = [
|
|
|
5114
5218
|
"(extraction_model / classification_model / merge_model / embedding_model) are " +
|
|
5115
5219
|
"free-form (any model the key permits) and default server-side when omitted. Typical " +
|
|
5116
5220
|
'onboarding call: set_llm_config({api_key: "sk-ant-...", embedding_api_key: "sk-..."}). ' +
|
|
5117
|
-
"
|
|
5221
|
+
"PRIVATE TIER: set chat_provider 'openai_compatible' + chat_base_url + " +
|
|
5222
|
+
"extraction_model (+ chat_api_key unless your server is keyless) to run " +
|
|
5223
|
+
"extraction/classification/merge against YOUR OWN endpoint so document text " +
|
|
5224
|
+
"never leaves your boundary; every model it resolves to is probed before it is saved. " +
|
|
5225
|
+
"Verify anytime with llm_config (has_api_key / has_embedding_key / chat_provider). REQUIRES your " +
|
|
5118
5226
|
"user-role key LOOPCTL_USER_KEY (minted by the human-anchored signup ceremony); if it " +
|
|
5119
5227
|
"is unset the tool fails fast telling you to set it.",
|
|
5120
5228
|
inputSchema: {
|
|
@@ -5128,7 +5236,14 @@ const TOOLS = [
|
|
|
5128
5236
|
},
|
|
5129
5237
|
extraction_model: {
|
|
5130
5238
|
type: "string",
|
|
5131
|
-
description:
|
|
5239
|
+
description:
|
|
5240
|
+
"Model id for knowledge extraction (null → server default). REQUIRED with " +
|
|
5241
|
+
"chat_provider 'openai_compatible': the server default is an Anthropic model " +
|
|
5242
|
+
"id your endpoint cannot serve, so there is no safe fallback. It is also the " +
|
|
5243
|
+
"fallback for classification_model / merge_model on that provider. " +
|
|
5244
|
+
"HuggingFace repo ids are valid (e.g. 'meta-llama/Meta-Llama-3-8B-Instruct') " +
|
|
5245
|
+
"— it is sent verbatim as the OpenAI `model` field, so it must match the name " +
|
|
5246
|
+
"your server actually serves.",
|
|
5132
5247
|
},
|
|
5133
5248
|
classification_model: {
|
|
5134
5249
|
type: "string",
|
|
@@ -5150,6 +5265,48 @@ const TOOLS = [
|
|
|
5150
5265
|
description:
|
|
5151
5266
|
"Embedding model id (null → server default text-embedding-3-small).",
|
|
5152
5267
|
},
|
|
5268
|
+
chat_provider: {
|
|
5269
|
+
type: "string",
|
|
5270
|
+
enum: ["anthropic", "openai_compatible"],
|
|
5271
|
+
description:
|
|
5272
|
+
"Which provider serves the CHAT surface (ingest extraction, classification, " +
|
|
5273
|
+
"merge synthesis, memory promotion). Omit or 'anthropic' keeps the hardcoded " +
|
|
5274
|
+
"Anthropic endpoint and identical behaviour. 'openai_compatible' routes that " +
|
|
5275
|
+
"surface — the largest and most sensitive payload in the pipeline, your " +
|
|
5276
|
+
"documents' full text — to YOUR OWN endpoint instead.",
|
|
5277
|
+
},
|
|
5278
|
+
chat_base_url: {
|
|
5279
|
+
type: "string",
|
|
5280
|
+
description:
|
|
5281
|
+
"API base of your OpenAI-compatible server, e.g. " +
|
|
5282
|
+
"'https://llm.example.internal/v1' (the client appends /chat/completions). " +
|
|
5283
|
+
"Required with chat_provider 'openai_compatible'. PROBED with a trivial " +
|
|
5284
|
+
"completion per resolved model BEFORE it is saved: an unreachable host, a " +
|
|
5285
|
+
"rejected credential or a non-OpenAI-compatible response is a 422 and NOTHING " +
|
|
5286
|
+
"is persisted. A host resolving into a private/loopback/link-local range is " +
|
|
5287
|
+
"refused outright unless the OPERATOR allowlisted it (SSRF guard). Must be a " +
|
|
5288
|
+
"BARE base: no query string, no fragment, no user:pass@ credentials (this " +
|
|
5289
|
+
"column is NOT encrypted — anything in the URL is stored and echoed back). " +
|
|
5290
|
+
"Plaintext http is accepted ONLY for a host the egress policy classifies " +
|
|
5291
|
+
"network-local; a public http:// endpoint is refused because the request " +
|
|
5292
|
+
"carries your key and your documents' full text in cleartext.",
|
|
5293
|
+
},
|
|
5294
|
+
chat_api_key: {
|
|
5295
|
+
type: "string",
|
|
5296
|
+
description:
|
|
5297
|
+
"Credential for chat_base_url. Write-only; stored encrypted, never returned. " +
|
|
5298
|
+
"SEPARATE from api_key — your Anthropic key is never sent to your endpoint. " +
|
|
5299
|
+
"OPTIONAL: a local server that serves /chat/completions with no auth is " +
|
|
5300
|
+
"configured by omitting this, and no authorization header is then sent.",
|
|
5301
|
+
},
|
|
5302
|
+
acknowledge_key_transmission: {
|
|
5303
|
+
type: "boolean",
|
|
5304
|
+
description:
|
|
5305
|
+
"Required when CHANGING chat_base_url without supplying a matching " +
|
|
5306
|
+
"chat_api_key: explicitly acknowledges that the already-stored key will be " +
|
|
5307
|
+
"transmitted to the new host. The probe never ships an existing credential to " +
|
|
5308
|
+
"a new host silently. Not persisted.",
|
|
5309
|
+
},
|
|
5153
5310
|
},
|
|
5154
5311
|
required: [],
|
|
5155
5312
|
},
|
|
@@ -5157,7 +5314,7 @@ const TOOLS = [
|
|
|
5157
5314
|
{
|
|
5158
5315
|
name: "knowledge_llm_usage",
|
|
5159
5316
|
description:
|
|
5160
|
-
"Per-tenant LLM token-usage summary, grouped by operation + model + source_type + " +
|
|
5317
|
+
"Per-tenant LLM token-usage summary, grouped by operation + model + provider + source_type + " +
|
|
5161
5318
|
"day over an optional date range, newest day first, with offset/limit pagination " +
|
|
5162
5319
|
"over meta.total_count. When `from` is omitted it defaults to a 90-day lookback; the " +
|
|
5163
5320
|
"EFFECTIVE window is echoed in meta.from/meta.to so you can detect that older usage " +
|
|
@@ -5909,6 +6066,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5909
6066
|
case "egress_posture":
|
|
5910
6067
|
return await egressPosture();
|
|
5911
6068
|
|
|
6069
|
+
// US-41.7 — witnessed custody claim
|
|
6070
|
+
case "custody_claim":
|
|
6071
|
+
return await custodyClaim(args);
|
|
6072
|
+
|
|
6073
|
+
case "custody_failures":
|
|
6074
|
+
return await custodyFailures();
|
|
6075
|
+
|
|
5912
6076
|
case "set_local_only":
|
|
5913
6077
|
return await setLocalOnly(args);
|
|
5914
6078
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loopctl-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.55.0",
|
|
4
4
|
"description": "MCP server for loopctl \u2014 structural trust for AI development loops",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"exports": "./index.js",
|
|
32
32
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
33
|
+
"node": ">=20.6.0"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"index.js",
|