loopctl-mcp-server 2.34.0 → 2.36.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 +15 -5
- package/index.js +292 -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 73 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
|
|
|
@@ -84,9 +84,18 @@ per-operation model overrides (`extraction_model`, `classification_model`,
|
|
|
84
84
|
|
|
85
85
|
### The smooth path (once, at onboarding)
|
|
86
86
|
|
|
87
|
-
1. **Sign up** and obtain your keys.
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
1. **Sign up** and obtain your keys. loopctl has two trust tiers (US-26.7.1):
|
|
88
|
+
- **Human-anchored (unlocks work-breakdown / chain-of-custody too):** visit
|
|
89
|
+
`https://loopctl.com/signup` and enroll a hardware authenticator (WebAuthn) —
|
|
90
|
+
the one human touch. This mints your **user-role** API key; also grab your
|
|
91
|
+
**agent** and **orchestrator** keys for day-to-day work.
|
|
92
|
+
- **Agent-rooted (KB-tier only, fully automated):** call the `signup` MCP tool
|
|
93
|
+
(or `POST /api/v1/signup` directly) with `name`/`slug`/`email` — no human, no
|
|
94
|
+
hardware key. This mints a one-time `role: user` API key with the FULL
|
|
95
|
+
knowledge-wiki surface (ingest/search/curate, BYO LLM config, agent
|
|
96
|
+
registration), but it **cannot** perform work-breakdown / chain-of-custody
|
|
97
|
+
operations. The `raw_key` in the response is shown ONCE — save it
|
|
98
|
+
immediately (it cannot be retrieved again).
|
|
90
99
|
2. **Set the env** in your `.mcp.json` (see [Configuration](#configuration)):
|
|
91
100
|
`LOOPCTL_SERVER`, `LOOPCTL_USER_KEY` (needed for this step), plus `LOOPCTL_AGENT_KEY`
|
|
92
101
|
/ `LOOPCTL_ORCH_KEY`.
|
|
@@ -112,7 +121,7 @@ REST endpoint (`PATCH /api/v1/tenants/me/llm-config`), and the docs — so you (
|
|
|
112
121
|
autonomous agent) can self-remediate without a human. Full agent-tenant lifecycle:
|
|
113
122
|
[`docs/onboarding-agent-tenant.md`](../docs/onboarding-agent-tenant.md).
|
|
114
123
|
|
|
115
|
-
## Tools (
|
|
124
|
+
## Tools (73)
|
|
116
125
|
|
|
117
126
|
### Project Tools
|
|
118
127
|
|
|
@@ -258,6 +267,7 @@ Key distribution for the dispatch pattern (Epic 26): per-dispatch ephemeral keys
|
|
|
258
267
|
|
|
259
268
|
| Tool | Description |
|
|
260
269
|
|---|---|
|
|
270
|
+
| `signup` | **US-26.7.1.** Create a NEW **agent-rooted (KB-tier)** tenant and mint its one-time root API key — entirely through this call, no human operator, no hardware authenticator, no existing API key required. The tenant gets the FULL knowledge-wiki surface but **cannot** perform work-breakdown / chain-of-custody operations (those require a separate human-anchored tenant via the WebAuthn ceremony at `https://loopctl.com/signup`). Rate-limited per client IP (<= 5/hour). The `raw_key` is shown ONCE — save it immediately (e.g. as `LOOPCTL_USER_KEY`). Required: `name`, `slug`, `email`. |
|
|
261
271
|
| `dispatch` | Mint an ephemeral, scoped api_key for a sub-agent dispatch, carrying its lineage path. The `raw_key` is returned ONCE — pass it to the sub-agent's launch args, never store it in env vars; it expires after `expires_in_seconds` (default 3600, max 14400). Required: `role` (`agent`/`orchestrator`), `agent_id`. Optional: `parent_dispatch_id`, `story_id`. |
|
|
262
272
|
| `recover_cap` | Re-mint a capability token for a story you're assigned to, after a session crash lost your cap. Required: `story_id`. Optional: `cap_type` (`start_cap`/`report_cap`, default `start_cap`), `lineage`. |
|
|
263
273
|
| `get_sth` | Get the latest Signed Tree Head for a tenant's tamper-evident audit chain. Public — no auth required. Required: `tenant_id`. |
|
package/index.js
CHANGED
|
@@ -189,6 +189,75 @@ async function apiCall(method, path, body, keyOverride, { exactKey = false } = {
|
|
|
189
189
|
return responseBody;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Unauthenticated request helper for genuinely public endpoints (currently
|
|
194
|
+
* only POST /api/v1/signup). No Authorization header is sent — the public
|
|
195
|
+
* signup POST carries no witness header either (US-26.7.1), so this
|
|
196
|
+
* bypasses `witnessClientFor` entirely and calls `fetch` directly. Response
|
|
197
|
+
* parsing mirrors `apiCall`'s so callers can pass the result straight to
|
|
198
|
+
* `toContent`/`toContentCompact`.
|
|
199
|
+
*/
|
|
200
|
+
async function publicApiCall(method, path, body) {
|
|
201
|
+
const url = `${getBaseUrl()}${path}`;
|
|
202
|
+
const headers = { "Content-Type": "application/json", Accept: "application/json" };
|
|
203
|
+
const serializedBody =
|
|
204
|
+
body !== undefined && body !== null ? JSON.stringify(body) : undefined;
|
|
205
|
+
|
|
206
|
+
let response;
|
|
207
|
+
try {
|
|
208
|
+
response = await fetch(url, {
|
|
209
|
+
method,
|
|
210
|
+
headers,
|
|
211
|
+
body: serializedBody,
|
|
212
|
+
signal: AbortSignal.timeout(30_000),
|
|
213
|
+
});
|
|
214
|
+
} catch (err) {
|
|
215
|
+
if (err.name === "TimeoutError") {
|
|
216
|
+
return { error: true, status: 0, body: "Request timed out after 30s" };
|
|
217
|
+
}
|
|
218
|
+
const cause = err.cause?.message ? ` (${err.cause.message})` : "";
|
|
219
|
+
return { error: true, status: 0, body: `Network error: ${err.message}${cause}` };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (response.status === 204) {
|
|
223
|
+
return { ok: true };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let responseBody;
|
|
227
|
+
const contentType = response.headers.get("content-type") || "";
|
|
228
|
+
if (contentType.includes("application/json")) {
|
|
229
|
+
const raw = await response.text();
|
|
230
|
+
const outcome = parseJsonResponseBody(raw, response.status);
|
|
231
|
+
if (outcome.error) {
|
|
232
|
+
return outcome;
|
|
233
|
+
}
|
|
234
|
+
responseBody = outcome.parsed;
|
|
235
|
+
} else {
|
|
236
|
+
const text = await response.text();
|
|
237
|
+
try {
|
|
238
|
+
responseBody = JSON.parse(text);
|
|
239
|
+
} catch {
|
|
240
|
+
responseBody = text;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
let errorBody = responseBody;
|
|
246
|
+
if (typeof errorBody === "string" && errorBody.length > 500) {
|
|
247
|
+
errorBody = errorBody
|
|
248
|
+
.replace(/<[^>]+>/g, " ")
|
|
249
|
+
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
250
|
+
.replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ")
|
|
251
|
+
.replace(/\s+/g, " ")
|
|
252
|
+
.trim()
|
|
253
|
+
.slice(0, 500) + "... (truncated)";
|
|
254
|
+
}
|
|
255
|
+
return { error: true, status: response.status, body: errorBody };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return responseBody;
|
|
259
|
+
}
|
|
260
|
+
|
|
192
261
|
function toContent(result) {
|
|
193
262
|
const isErr = result && result.error === true;
|
|
194
263
|
return {
|
|
@@ -1611,6 +1680,81 @@ async function createDispatch({
|
|
|
1611
1680
|
return toContent(result);
|
|
1612
1681
|
}
|
|
1613
1682
|
|
|
1683
|
+
// US-26.7.1: public, agent-rooted (KB-tier) self-signup. No API key required —
|
|
1684
|
+
// this creates the tenant AND the key. The resulting tenant is KB-tier only
|
|
1685
|
+
// (knowledge ingest/search/curate on the caller's own BYO LLM keys); the
|
|
1686
|
+
// work-breakdown / chain-of-custody surface requires a separate,
|
|
1687
|
+
// human-anchored WebAuthn signup ceremony at https://loopctl.com/signup.
|
|
1688
|
+
async function signup({ name, slug, email }) {
|
|
1689
|
+
const result = await publicApiCall("POST", "/api/v1/signup", { name, slug, email });
|
|
1690
|
+
return toContent(result);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// US-26.7.2: opt-in WebAuthn trust-tier upgrade ceremony (agent_rooted ->
|
|
1694
|
+
// human_anchored) + authenticator revocation. All four require the EXACT
|
|
1695
|
+
// user-role key (LOOPCTL_USER_KEY) + ownership of `tenant_id` — mirroring
|
|
1696
|
+
// set_llm_config's exactKey:true pattern, since these mutate the tenant's
|
|
1697
|
+
// root of trust. NOT headless: completing enrollment/revocation requires an
|
|
1698
|
+
// INTERACTIVE WebAuthn client (a browser or a native FIDO2 library) with a
|
|
1699
|
+
// human present to touch the hardware authenticator — an agent alone cannot
|
|
1700
|
+
// produce a valid attestation or assertion, by design (see
|
|
1701
|
+
// docs/chain-of-custody-v2.md §9).
|
|
1702
|
+
async function requestAuthenticatorChallenge({ tenant_id }) {
|
|
1703
|
+
const result = await apiCall(
|
|
1704
|
+
"POST",
|
|
1705
|
+
`/api/v1/tenants/${tenant_id}/authenticators/challenge`,
|
|
1706
|
+
{},
|
|
1707
|
+
process.env.LOOPCTL_USER_KEY,
|
|
1708
|
+
{ exactKey: true },
|
|
1709
|
+
);
|
|
1710
|
+
return toContent(result);
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
async function enrollAuthenticator({
|
|
1714
|
+
tenant_id,
|
|
1715
|
+
challenge_id,
|
|
1716
|
+
attestation_object,
|
|
1717
|
+
client_data_json,
|
|
1718
|
+
credential_id,
|
|
1719
|
+
friendly_name,
|
|
1720
|
+
reauth_assertion,
|
|
1721
|
+
}) {
|
|
1722
|
+
const body = { challenge_id, attestation_object, client_data_json, credential_id };
|
|
1723
|
+
if (friendly_name != null) body.friendly_name = friendly_name;
|
|
1724
|
+
if (reauth_assertion != null) body.reauth_assertion = reauth_assertion;
|
|
1725
|
+
|
|
1726
|
+
const result = await apiCall(
|
|
1727
|
+
"POST",
|
|
1728
|
+
`/api/v1/tenants/${tenant_id}/authenticators`,
|
|
1729
|
+
body,
|
|
1730
|
+
process.env.LOOPCTL_USER_KEY,
|
|
1731
|
+
{ exactKey: true },
|
|
1732
|
+
);
|
|
1733
|
+
return toContent(result);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
async function requestAuthenticatorRevokeChallenge({ tenant_id }) {
|
|
1737
|
+
const result = await apiCall(
|
|
1738
|
+
"POST",
|
|
1739
|
+
`/api/v1/tenants/${tenant_id}/authenticators/revoke-challenge`,
|
|
1740
|
+
{},
|
|
1741
|
+
process.env.LOOPCTL_USER_KEY,
|
|
1742
|
+
{ exactKey: true },
|
|
1743
|
+
);
|
|
1744
|
+
return toContent(result);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
async function revokeAuthenticator({ tenant_id, authenticator_id, webauthn_assertion }) {
|
|
1748
|
+
const result = await apiCall(
|
|
1749
|
+
"DELETE",
|
|
1750
|
+
`/api/v1/tenants/${tenant_id}/authenticators/${authenticator_id}`,
|
|
1751
|
+
{ webauthn_assertion },
|
|
1752
|
+
process.env.LOOPCTL_USER_KEY,
|
|
1753
|
+
{ exactKey: true },
|
|
1754
|
+
);
|
|
1755
|
+
return toContent(result);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1614
1758
|
// US-26: Signed Tree Head retrieval
|
|
1615
1759
|
async function getSth({ tenant_id }) {
|
|
1616
1760
|
const result = await apiCall("GET", `/api/v1/audit/sth/${tenant_id}`);
|
|
@@ -3788,6 +3932,139 @@ const TOOLS = [
|
|
|
3788
3932
|
},
|
|
3789
3933
|
|
|
3790
3934
|
// Chain of Custody v2 tools
|
|
3935
|
+
{
|
|
3936
|
+
name: "signup",
|
|
3937
|
+
description:
|
|
3938
|
+
"Create a NEW agent-rooted (KB-tier) tenant and mint its one-time root API key — " +
|
|
3939
|
+
"entirely through this call, no human operator and no hardware authenticator required. " +
|
|
3940
|
+
"Public — no existing API key needed to call this tool. The returned tenant can " +
|
|
3941
|
+
"immediately use the FULL knowledge-wiki surface (ingest/search/context/curate, BYO " +
|
|
3942
|
+
"LLM key config, agent registration) but CANNOT perform work-breakdown / " +
|
|
3943
|
+
"chain-of-custody operations (create projects/epics/stories, claim/verify/report, " +
|
|
3944
|
+
"dispatch, etc.) — those require a separate, human-anchored tenant created via the " +
|
|
3945
|
+
"WebAuthn ceremony at https://loopctl.com/signup. Rate-limited per client IP " +
|
|
3946
|
+
"(<= 5 signups/hour). The raw_key in the response is shown ONCE — save it immediately " +
|
|
3947
|
+
"(e.g. as LOOPCTL_USER_KEY) since it cannot be retrieved again. Next steps after " +
|
|
3948
|
+
"signup: configure your BYO LLM keys with set_llm_config, then register an agent " +
|
|
3949
|
+
"identity, then ingest/search the wiki.",
|
|
3950
|
+
inputSchema: {
|
|
3951
|
+
type: "object",
|
|
3952
|
+
properties: {
|
|
3953
|
+
name: { type: "string", description: "Tenant display name.", maxLength: 120 },
|
|
3954
|
+
slug: { type: "string", description: "URL-safe unique tenant slug.", maxLength: 64 },
|
|
3955
|
+
email: { type: "string", description: "Contact email for the tenant." },
|
|
3956
|
+
},
|
|
3957
|
+
required: ["name", "slug", "email"],
|
|
3958
|
+
},
|
|
3959
|
+
},
|
|
3960
|
+
{
|
|
3961
|
+
name: "request_authenticator_challenge",
|
|
3962
|
+
description:
|
|
3963
|
+
"Step 1 of the opt-in WebAuthn trust-tier upgrade ceremony (US-26.7.2): issues a " +
|
|
3964
|
+
"registration challenge for enrolling a hardware authenticator against an EXISTING " +
|
|
3965
|
+
"agent_rooted (KB-tier) tenant, promoting it to human_anchored on success. " +
|
|
3966
|
+
"IMPORTANT: completing this ceremony requires an INTERACTIVE WebAuthn client " +
|
|
3967
|
+
"(a browser calling navigator.credentials.create(), or a native FIDO2 library) " +
|
|
3968
|
+
"with a HUMAN present to physically touch the authenticator — an agent alone " +
|
|
3969
|
+
"cannot produce a valid attestation, by design. Use this tool to fetch the " +
|
|
3970
|
+
"challenge/rp/user/pubKeyCredParams payload, drive the WebAuthn ceremony in your " +
|
|
3971
|
+
"interactive client, then call enroll_authenticator with the result. If the " +
|
|
3972
|
+
"tenant is already human_anchored, the response includes reauth_required: true " +
|
|
3973
|
+
"plus a reauth_challenge — enroll_authenticator will need a fresh assertion from " +
|
|
3974
|
+
"an EXISTING authenticator too (see enroll_authenticator's description). Requires " +
|
|
3975
|
+
"LOOPCTL_USER_KEY (user role) bound to tenant_id.",
|
|
3976
|
+
inputSchema: {
|
|
3977
|
+
type: "object",
|
|
3978
|
+
properties: {
|
|
3979
|
+
tenant_id: { type: "string", description: "Tenant UUID to enroll an authenticator for." },
|
|
3980
|
+
},
|
|
3981
|
+
required: ["tenant_id"],
|
|
3982
|
+
},
|
|
3983
|
+
},
|
|
3984
|
+
{
|
|
3985
|
+
name: "enroll_authenticator",
|
|
3986
|
+
description:
|
|
3987
|
+
"Step 2 of the opt-in WebAuthn trust-tier upgrade ceremony: completes enrollment " +
|
|
3988
|
+
"with the attestation produced by navigator.credentials.create() against the " +
|
|
3989
|
+
"challenge from request_authenticator_challenge. On a tenant's FIRST enrollment " +
|
|
3990
|
+
"(zero prior authenticators) this call FLIPS the tenant from agent_rooted to " +
|
|
3991
|
+
"human_anchored, unlocking the work-breakdown / chain-of-custody surface " +
|
|
3992
|
+
"(projects, stories, dispatch, ...). On a SUBSEQUENT (backup) enrollment for an " +
|
|
3993
|
+
"already human_anchored tenant, `reauth_assertion` (a fresh WebAuthn assertion " +
|
|
3994
|
+
"from an EXISTING enrolled authenticator, from the challenge's reauth_challenge) " +
|
|
3995
|
+
"is REQUIRED — omitting it when required returns 401 reauth_required. All " +
|
|
3996
|
+
"binary fields are base64url encoded. Requires LOOPCTL_USER_KEY (user role) " +
|
|
3997
|
+
"bound to tenant_id. Cannot be completed by an agent alone — see " +
|
|
3998
|
+
"request_authenticator_challenge.",
|
|
3999
|
+
inputSchema: {
|
|
4000
|
+
type: "object",
|
|
4001
|
+
properties: {
|
|
4002
|
+
tenant_id: { type: "string", description: "Tenant UUID." },
|
|
4003
|
+
challenge_id: {
|
|
4004
|
+
type: "string",
|
|
4005
|
+
description: "challenge_id from request_authenticator_challenge.",
|
|
4006
|
+
},
|
|
4007
|
+
attestation_object: { type: "string", description: "Base64url attestation object." },
|
|
4008
|
+
client_data_json: { type: "string", description: "Base64url raw client data JSON." },
|
|
4009
|
+
credential_id: { type: "string", description: "Base64url credential id." },
|
|
4010
|
+
friendly_name: {
|
|
4011
|
+
type: "string",
|
|
4012
|
+
description: "Operator-supplied label (1..120 chars, default \"Authenticator\").",
|
|
4013
|
+
},
|
|
4014
|
+
reauth_assertion: {
|
|
4015
|
+
type: "object",
|
|
4016
|
+
description:
|
|
4017
|
+
"Required ONLY when enrolling a backup authenticator on an already " +
|
|
4018
|
+
"human_anchored tenant: a fresh assertion (challenge_id, credential_id, " +
|
|
4019
|
+
"authenticator_data, signature, client_data_json — all base64url) from an " +
|
|
4020
|
+
"EXISTING enrolled authenticator, bound to the reauth_challenge returned by " +
|
|
4021
|
+
"request_authenticator_challenge.",
|
|
4022
|
+
},
|
|
4023
|
+
},
|
|
4024
|
+
required: ["tenant_id", "challenge_id", "attestation_object", "client_data_json", "credential_id"],
|
|
4025
|
+
},
|
|
4026
|
+
},
|
|
4027
|
+
{
|
|
4028
|
+
name: "request_authenticator_revoke_challenge",
|
|
4029
|
+
description:
|
|
4030
|
+
"Issues a fresh-assertion challenge to authorize revoking one of a tenant's " +
|
|
4031
|
+
"enrolled WebAuthn authenticators. Requires an INTERACTIVE WebAuthn client + " +
|
|
4032
|
+
"human touch to produce the assertion revoke_authenticator needs — an agent " +
|
|
4033
|
+
"alone cannot authorize a revocation. Requires LOOPCTL_USER_KEY (user role) " +
|
|
4034
|
+
"bound to tenant_id.",
|
|
4035
|
+
inputSchema: {
|
|
4036
|
+
type: "object",
|
|
4037
|
+
properties: {
|
|
4038
|
+
tenant_id: { type: "string", description: "Tenant UUID." },
|
|
4039
|
+
},
|
|
4040
|
+
required: ["tenant_id"],
|
|
4041
|
+
},
|
|
4042
|
+
},
|
|
4043
|
+
{
|
|
4044
|
+
name: "revoke_authenticator",
|
|
4045
|
+
description:
|
|
4046
|
+
"Revokes an enrolled authenticator using the assertion from " +
|
|
4047
|
+
"request_authenticator_revoke_challenge (navigator.credentials.get() against an " +
|
|
4048
|
+
"EXISTING authenticator — human touch required). Refuses (409 last_authenticator) " +
|
|
4049
|
+
"to remove a human_anchored tenant's LAST authenticator — there is no " +
|
|
4050
|
+
"auto-downgrade; losing every authenticator is a fatal, human-recovery-only event " +
|
|
4051
|
+
"(docs/chain-of-custody-v2.md §9.3). Requires LOOPCTL_USER_KEY (user role) bound " +
|
|
4052
|
+
"to tenant_id.",
|
|
4053
|
+
inputSchema: {
|
|
4054
|
+
type: "object",
|
|
4055
|
+
properties: {
|
|
4056
|
+
tenant_id: { type: "string", description: "Tenant UUID." },
|
|
4057
|
+
authenticator_id: { type: "string", description: "UUID of the authenticator to revoke." },
|
|
4058
|
+
webauthn_assertion: {
|
|
4059
|
+
type: "object",
|
|
4060
|
+
description:
|
|
4061
|
+
"The assertion (challenge_id, credential_id, authenticator_data, signature, " +
|
|
4062
|
+
"client_data_json — all base64url) bound to the revoke-challenge.",
|
|
4063
|
+
},
|
|
4064
|
+
},
|
|
4065
|
+
required: ["tenant_id", "authenticator_id", "webauthn_assertion"],
|
|
4066
|
+
},
|
|
4067
|
+
},
|
|
3791
4068
|
{
|
|
3792
4069
|
name: "get_sth",
|
|
3793
4070
|
description: "Get the latest Signed Tree Head for a tenant's audit chain. Public — no auth required.",
|
|
@@ -4075,6 +4352,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4075
4352
|
case "dispatch":
|
|
4076
4353
|
return await createDispatch(args);
|
|
4077
4354
|
|
|
4355
|
+
case "signup":
|
|
4356
|
+
return await signup(args);
|
|
4357
|
+
|
|
4358
|
+
case "request_authenticator_challenge":
|
|
4359
|
+
return await requestAuthenticatorChallenge(args);
|
|
4360
|
+
|
|
4361
|
+
case "enroll_authenticator":
|
|
4362
|
+
return await enrollAuthenticator(args);
|
|
4363
|
+
|
|
4364
|
+
case "request_authenticator_revoke_challenge":
|
|
4365
|
+
return await requestAuthenticatorRevokeChallenge(args);
|
|
4366
|
+
|
|
4367
|
+
case "revoke_authenticator":
|
|
4368
|
+
return await revokeAuthenticator(args);
|
|
4369
|
+
|
|
4078
4370
|
case "get_sth":
|
|
4079
4371
|
return await getSth(args);
|
|
4080
4372
|
|