loopctl-mcp-server 2.35.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/index.js +185 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1690,6 +1690,71 @@ async function signup({ name, slug, email }) {
|
|
|
1690
1690
|
return toContent(result);
|
|
1691
1691
|
}
|
|
1692
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
|
+
|
|
1693
1758
|
// US-26: Signed Tree Head retrieval
|
|
1694
1759
|
async function getSth({ tenant_id }) {
|
|
1695
1760
|
const result = await apiCall("GET", `/api/v1/audit/sth/${tenant_id}`);
|
|
@@ -3892,6 +3957,114 @@ const TOOLS = [
|
|
|
3892
3957
|
required: ["name", "slug", "email"],
|
|
3893
3958
|
},
|
|
3894
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
|
+
},
|
|
3895
4068
|
{
|
|
3896
4069
|
name: "get_sth",
|
|
3897
4070
|
description: "Get the latest Signed Tree Head for a tenant's audit chain. Public — no auth required.",
|
|
@@ -4182,6 +4355,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4182
4355
|
case "signup":
|
|
4183
4356
|
return await signup(args);
|
|
4184
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
|
+
|
|
4185
4370
|
case "get_sth":
|
|
4186
4371
|
return await getSth(args);
|
|
4187
4372
|
|