@seekrit/mcp 0.8.0 → 0.8.1
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/dist/index.js +248 -31
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1136,6 +1136,16 @@ z.object({
|
|
|
1136
1136
|
*/
|
|
1137
1137
|
encryptedPrivateKey: z.string().min(1)
|
|
1138
1138
|
});
|
|
1139
|
+
z.object({
|
|
1140
|
+
/** Base64url WebAuthn credential id — what `allowCredentials` is built from. */
|
|
1141
|
+
credentialId: z.string().min(1).max(512),
|
|
1142
|
+
/** Human label for the device, e.g. "MacBook Touch ID". Display only. */
|
|
1143
|
+
label: z.string().min(1).max(64),
|
|
1144
|
+
/** Base64url PRF evaluation input for this credential; not a secret. */
|
|
1145
|
+
prfInput: z.string().min(1).max(256),
|
|
1146
|
+
/** The private key wrapped to this passkey's PRF output. */
|
|
1147
|
+
encryptedPrivateKey: z.string().min(1).max(8192).refine((v) => v.startsWith("pk2."), { message: "expected a pk2. passkey wrap" })
|
|
1148
|
+
});
|
|
1139
1149
|
const grantEnvironmentKeySchema = z.object({
|
|
1140
1150
|
principalType: principalTypeSchema,
|
|
1141
1151
|
principalId: z.string().min(1),
|
|
@@ -1438,7 +1448,9 @@ z.enum([
|
|
|
1438
1448
|
"bunnyshell",
|
|
1439
1449
|
"github-actions",
|
|
1440
1450
|
"gcp-secret-manager",
|
|
1441
|
-
"langgraph-platform"
|
|
1451
|
+
"langgraph-platform",
|
|
1452
|
+
"azure-key-vault",
|
|
1453
|
+
"huggingface-spaces"
|
|
1442
1454
|
]);
|
|
1443
1455
|
/**
|
|
1444
1456
|
* Vercel account scope. The API token itself is never here — it is wrapped to
|
|
@@ -1753,6 +1765,69 @@ const langgraphPlatformConnectionConfigSchema = z.object({
|
|
|
1753
1765
|
message: "set region for a LangChain-hosted account or baseUrl for a self-hosted one, not both",
|
|
1754
1766
|
path: ["baseUrl"]
|
|
1755
1767
|
});
|
|
1768
|
+
/**
|
|
1769
|
+
* The Azure clouds a vault can live in.
|
|
1770
|
+
*
|
|
1771
|
+
* Unlike AWS, where the China partition can be read off the region string
|
|
1772
|
+
* (`cn-…`), nothing about a tenant id or a vault name says which cloud it
|
|
1773
|
+
* belongs to — and two hosts have to agree with the answer: the Entra authority
|
|
1774
|
+
* that issues the token and the DNS suffix the vault answers on. Getting either
|
|
1775
|
+
* wrong is a failure inside an alarm with nobody watching, so it is stated.
|
|
1776
|
+
*/
|
|
1777
|
+
const AZURE_CLOUDS = [
|
|
1778
|
+
"public",
|
|
1779
|
+
"usgov",
|
|
1780
|
+
"china"
|
|
1781
|
+
];
|
|
1782
|
+
/**
|
|
1783
|
+
* A Microsoft Entra directory (tenant) or application (client) id.
|
|
1784
|
+
*
|
|
1785
|
+
* Both are GUIDs. Entra accepts a verified domain name in place of a tenant id
|
|
1786
|
+
* in the token URL, but not a client id, and taking only the GUID for both
|
|
1787
|
+
* keeps one rule — a tenant's GUID is on the same admin-center page as the
|
|
1788
|
+
* client id it pairs with, so nothing is harder to find.
|
|
1789
|
+
*/
|
|
1790
|
+
const azureGuidSchema = z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a GUID, as the Entra admin center shows it");
|
|
1791
|
+
/**
|
|
1792
|
+
* Azure account scope: which directory to authenticate against, as which app.
|
|
1793
|
+
*
|
|
1794
|
+
* The split follows AWS's, and for the same reason. A service principal's
|
|
1795
|
+
* **client secret** is the credential and is wrapped to the connection's key;
|
|
1796
|
+
* the tenant and client ids are *identifiers* — they appear in the token
|
|
1797
|
+
* request URL, in sign-in logs, and on the app registration blade. Keeping them
|
|
1798
|
+
* here lets the dashboard say which principal a connection authenticates as,
|
|
1799
|
+
* which is the first thing worth knowing when a connection starts failing after
|
|
1800
|
+
* a secret expires.
|
|
1801
|
+
*
|
|
1802
|
+
* Client secrets are the only credential kind here: a certificate or federated
|
|
1803
|
+
* credential would need a private key or a trust relationship the sync engine
|
|
1804
|
+
* has nowhere to keep, and Entra caps a client secret at 24 months, which is a
|
|
1805
|
+
* rotation the connection's `lastError` will make loud.
|
|
1806
|
+
*/
|
|
1807
|
+
const azureKeyVaultConnectionConfigSchema = z.object({
|
|
1808
|
+
provider: z.literal("azure-key-vault"),
|
|
1809
|
+
/** Entra directory (tenant) ID. */
|
|
1810
|
+
tenantId: azureGuidSchema,
|
|
1811
|
+
/** Application (client) ID of the service principal seekrit signs in as. */
|
|
1812
|
+
clientId: azureGuidSchema,
|
|
1813
|
+
/** Which Azure cloud the tenant and its vaults live in. */
|
|
1814
|
+
cloud: z.enum(AZURE_CLOUDS).default("public")
|
|
1815
|
+
});
|
|
1816
|
+
/**
|
|
1817
|
+
* Hugging Face account scope — empty, as Render's and Fly's are.
|
|
1818
|
+
*
|
|
1819
|
+
* Neither half of "which account, which thing" needs stating. A Hub user access
|
|
1820
|
+
* token belongs to one user and carries their write access to every Space they
|
|
1821
|
+
* or their organizations own; a Space is addressed by `owner/name`, which is
|
|
1822
|
+
* globally unique. So the token plus the destination is the whole address.
|
|
1823
|
+
*
|
|
1824
|
+
* There is deliberately no `baseUrl` twin of the GitHub Enterprise Server
|
|
1825
|
+
* field. `HF_ENDPOINT` exists in the Python client for Hub *mirrors*, which
|
|
1826
|
+
* serve repository content — not the settings API this connector writes, and
|
|
1827
|
+
* not something a mirror is expected to accept a write on. Adding the field
|
|
1828
|
+
* would invite pointing a connection at a host that silently swallows secrets.
|
|
1829
|
+
*/
|
|
1830
|
+
const huggingfaceSpacesConnectionConfigSchema = z.object({ provider: z.literal("huggingface-spaces") });
|
|
1756
1831
|
const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
1757
1832
|
vercelConnectionConfigSchema,
|
|
1758
1833
|
cloudflareWorkersConnectionConfigSchema,
|
|
@@ -1770,7 +1845,9 @@ const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
|
1770
1845
|
bunnyshellConnectionConfigSchema,
|
|
1771
1846
|
githubActionsConnectionConfigSchema,
|
|
1772
1847
|
gcpSecretManagerConnectionConfigSchema,
|
|
1773
|
-
langgraphPlatformConnectionConfigSchema
|
|
1848
|
+
langgraphPlatformConnectionConfigSchema,
|
|
1849
|
+
azureKeyVaultConnectionConfigSchema,
|
|
1850
|
+
huggingfaceSpacesConnectionConfigSchema
|
|
1774
1851
|
]);
|
|
1775
1852
|
const vercelDestinationSchema = z.object({
|
|
1776
1853
|
provider: z.literal("vercel"),
|
|
@@ -2478,6 +2555,77 @@ const langgraphPlatformDestinationSchema = z.object({
|
|
|
2478
2555
|
/** Deployment UUID, from the dashboard URL or `GET /v2/deployments`. */
|
|
2479
2556
|
deploymentId: z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a LangGraph Platform deployment UUID")
|
|
2480
2557
|
});
|
|
2558
|
+
/**
|
|
2559
|
+
* A Key Vault's name — the leftmost label of its DNS name, so the vault
|
|
2560
|
+
* `acme-prod` answers at `acme-prod.vault.azure.net`.
|
|
2561
|
+
*
|
|
2562
|
+
* Azure's own rule, stated here because the failure it prevents is confusing:
|
|
2563
|
+
* 3–24 characters, alphanumerics and hyphens, starting with a letter, ending
|
|
2564
|
+
* with a letter or digit, and no run of two hyphens. A name that breaks it
|
|
2565
|
+
* cannot exist, so a typo is a DNS failure rather than a 404 — an error that
|
|
2566
|
+
* says nothing about what was wrong.
|
|
2567
|
+
*
|
|
2568
|
+
* The *base URL* is not taken instead. It would let a connection be pointed at
|
|
2569
|
+
* any host, and the whole address seekrit needs is this label plus the cloud
|
|
2570
|
+
* already named on the connection.
|
|
2571
|
+
*/
|
|
2572
|
+
const azureVaultNameSchema = z.string().trim().regex(/^[A-Za-z](?!.*--)[A-Za-z0-9-]{1,22}[A-Za-z0-9]$/, "must be a Key Vault name: 3–24 letters, digits, and single hyphens, starting with a letter");
|
|
2573
|
+
/**
|
|
2574
|
+
* Which vault a binding writes to, and under what names.
|
|
2575
|
+
*
|
|
2576
|
+
* There is no layout choice as Secrets Manager has: the reason a `json-bundle`
|
|
2577
|
+
* exists there is billing — AWS charges per secret per month — and Key Vault
|
|
2578
|
+
* charges per *operation*, so fifty names cost the same stored fifty ways. One
|
|
2579
|
+
* secret per name is simply correct here.
|
|
2580
|
+
*/
|
|
2581
|
+
const azureKeyVaultDestinationSchema = z.object({
|
|
2582
|
+
provider: z.literal("azure-key-vault"),
|
|
2583
|
+
/** Vault name, e.g. `acme-prod` for `acme-prod.vault.azure.net`. */
|
|
2584
|
+
vault: azureVaultNameSchema,
|
|
2585
|
+
/**
|
|
2586
|
+
* Prepended to every secret name, e.g. `storefront-`. Key Vault has no
|
|
2587
|
+
* hierarchy — its names are flat, and `/` is not among the characters it
|
|
2588
|
+
* accepts — so unlike Parameter Store's `path` this is a naming convention
|
|
2589
|
+
* and nothing more. Worth setting in a vault that holds anything else.
|
|
2590
|
+
*/
|
|
2591
|
+
prefix: z.string().trim().max(64).regex(/^[A-Za-z0-9-]*$/, "may contain letters, digits, and hyphens").optional(),
|
|
2592
|
+
/** What to do with a name Key Vault cannot store — see {@link AZURE_KEY_VAULT_NAME_MODES}. */
|
|
2593
|
+
nameMode: z.enum(["dash", "reject"]).default("dash")
|
|
2594
|
+
});
|
|
2595
|
+
/**
|
|
2596
|
+
* The Space whose secrets a binding owns, addressed the way the Hub addresses
|
|
2597
|
+
* every repository: `owner/name`, where `owner` is a user or an organization.
|
|
2598
|
+
*
|
|
2599
|
+
* A Space has **one** secret set, shared by every replica — there is no
|
|
2600
|
+
* per-target split to state, the way Vercel and Pages have one. The Hub's
|
|
2601
|
+
* convention is that staging and production are separate Spaces
|
|
2602
|
+
* (`acme/demo`, `acme/demo-staging`), so pointing at an environment means
|
|
2603
|
+
* naming that Space, exactly as a Fly environment means naming its own app.
|
|
2604
|
+
*
|
|
2605
|
+
* Validated by shape because the two habitual slips both fail *late*: pasting
|
|
2606
|
+
* the browser URL (`https://huggingface.co/spaces/acme/demo`) or the bare name
|
|
2607
|
+
* without its owner. Either one is a 404 from the Hub inside an alarm with
|
|
2608
|
+
* nobody watching, and a 404 says nothing about which half was wrong. The
|
|
2609
|
+
* leading character is held to alphanumeric, which is also what rejects the
|
|
2610
|
+
* `.` and `..` that no repository may be called.
|
|
2611
|
+
*/
|
|
2612
|
+
const huggingfaceRepoIdSchema = z.string().trim().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/, "must be a Space ID as owner/name, e.g. acme/support-demo — not a URL");
|
|
2613
|
+
/**
|
|
2614
|
+
* One Hugging Face **Space**, whose secrets arrive in the app's container as
|
|
2615
|
+
* environment variables.
|
|
2616
|
+
*
|
|
2617
|
+
* Only secrets. A Space also has *variables*, and they are not a second lane
|
|
2618
|
+
* seekrit could use: the Hub calls them "non-sensitive configuration values",
|
|
2619
|
+
* they are "publicly accessible and viewable", and they are copied into every
|
|
2620
|
+
* Space duplicated from this one. Writing a seekrit secret there would publish
|
|
2621
|
+
* it, so this connector has no variables mode — a binding that wants one is
|
|
2622
|
+
* asking for the wrong thing.
|
|
2623
|
+
*/
|
|
2624
|
+
const huggingfaceSpacesDestinationSchema = z.object({
|
|
2625
|
+
provider: z.literal("huggingface-spaces"),
|
|
2626
|
+
/** Space ID as `owner/name`, e.g. `acme/support-demo`. */
|
|
2627
|
+
repoId: huggingfaceRepoIdSchema
|
|
2628
|
+
});
|
|
2481
2629
|
const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
2482
2630
|
vercelDestinationSchema,
|
|
2483
2631
|
cloudflareWorkersDestinationSchema,
|
|
@@ -2495,7 +2643,9 @@ const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
|
2495
2643
|
bunnyshellDestinationSchema,
|
|
2496
2644
|
githubActionsDestinationSchema,
|
|
2497
2645
|
gcpSecretManagerDestinationSchema,
|
|
2498
|
-
langgraphPlatformDestinationSchema
|
|
2646
|
+
langgraphPlatformDestinationSchema,
|
|
2647
|
+
azureKeyVaultDestinationSchema,
|
|
2648
|
+
huggingfaceSpacesDestinationSchema
|
|
2499
2649
|
]);
|
|
2500
2650
|
/**
|
|
2501
2651
|
* How seekrit secret names become destination key names. Applied in order:
|
|
@@ -3225,7 +3375,7 @@ function isServiceToken(value) {
|
|
|
3225
3375
|
}
|
|
3226
3376
|
//#endregion
|
|
3227
3377
|
//#region ../cli/package.json
|
|
3228
|
-
var version$1 = "
|
|
3378
|
+
var version$1 = "1.2.2";
|
|
3229
3379
|
const PROJECT_FILE = "seekrit.json";
|
|
3230
3380
|
function globalConfigPath() {
|
|
3231
3381
|
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "seekrit", "config.json");
|
|
@@ -3347,6 +3497,22 @@ var SeekritClient = class {
|
|
|
3347
3497
|
revokeCliSession(sessionId) {
|
|
3348
3498
|
return this.request("DELETE", `/v1/me/cli-sessions/${sessionId}`);
|
|
3349
3499
|
}
|
|
3500
|
+
/**
|
|
3501
|
+
* The passkeys enrolled to unlock this user's keyring, each with the `pk2.`
|
|
3502
|
+
* blob its PRF output decrypts. One call is everything the unlock ceremony
|
|
3503
|
+
* needs; the blobs are opaque without the authenticator.
|
|
3504
|
+
*/
|
|
3505
|
+
listMyPasskeys() {
|
|
3506
|
+
return this.request("GET", "/v1/me/passkeys");
|
|
3507
|
+
}
|
|
3508
|
+
/** File a private key already wrapped, client-side, to a passkey's PRF output. */
|
|
3509
|
+
enrollMyPasskey(input) {
|
|
3510
|
+
return this.request("POST", "/v1/me/passkeys", input);
|
|
3511
|
+
}
|
|
3512
|
+
/** Stop a passkey unlocking the keyring. The key itself is untouched. */
|
|
3513
|
+
deleteMyPasskey(passkeyId) {
|
|
3514
|
+
return this.request("DELETE", `/v1/me/passkeys/${passkeyId}`);
|
|
3515
|
+
}
|
|
3350
3516
|
/** What a pending login request is asking for — for the approval screen. */
|
|
3351
3517
|
getCliLoginRequest(code) {
|
|
3352
3518
|
return this.request("GET", `/v1/cli-login/${encodeURIComponent(code)}`);
|
|
@@ -3953,7 +4119,18 @@ function fail(message) {
|
|
|
3953
4119
|
console.error(`error: ${message}`);
|
|
3954
4120
|
process.exit(1);
|
|
3955
4121
|
}
|
|
3956
|
-
/**
|
|
4122
|
+
/**
|
|
4123
|
+
* Prompt without echoing input (for passphrases).
|
|
4124
|
+
*
|
|
4125
|
+
* Piping the answer in (`echo … | seekrit secrets get …`) is supported and
|
|
4126
|
+
* common in CI, so this reads stdin rather than insisting on a TTY. But stdin
|
|
4127
|
+
* can also close with nothing on it — `< /dev/null`, a closed pipe, an agent
|
|
4128
|
+
* spawning us with no stdin — and readline signals that by emitting `close`
|
|
4129
|
+
* without ever calling the `question` callback. Left unhandled the promise
|
|
4130
|
+
* never settles, the event loop drains, and Node exits **0** having printed
|
|
4131
|
+
* nothing: `V=$(seekrit secrets get X)` silently yields an empty value and a
|
|
4132
|
+
* success status. So treat EOF-without-an-answer as the error it is.
|
|
4133
|
+
*/
|
|
3957
4134
|
function promptHidden(question) {
|
|
3958
4135
|
const muted = new Writable({ write(_chunk, _encoding, callback) {
|
|
3959
4136
|
callback();
|
|
@@ -3964,8 +4141,15 @@ function promptHidden(question) {
|
|
|
3964
4141
|
output: muted,
|
|
3965
4142
|
terminal: true
|
|
3966
4143
|
});
|
|
3967
|
-
return new Promise((resolve) => {
|
|
4144
|
+
return new Promise((resolve, reject) => {
|
|
4145
|
+
let answered = false;
|
|
4146
|
+
rl.on("close", () => {
|
|
4147
|
+
if (answered) return;
|
|
4148
|
+
process.stderr.write("\n");
|
|
4149
|
+
reject(/* @__PURE__ */ new Error("no passphrase on stdin — set SEEKRIT_PASSPHRASE, pipe it in, or run this in a terminal"));
|
|
4150
|
+
});
|
|
3968
4151
|
rl.question("", (answer) => {
|
|
4152
|
+
answered = true;
|
|
3969
4153
|
rl.close();
|
|
3970
4154
|
process.stderr.write("\n");
|
|
3971
4155
|
resolve(answer);
|
|
@@ -3987,9 +4171,23 @@ const CLI_CLIENT = `cli/${version$1}`;
|
|
|
3987
4171
|
* `flag > env > .env` credential resolution. Empty for every command but
|
|
3988
4172
|
* `seekrit run`, which loads `.env` before authenticating.
|
|
3989
4173
|
*/
|
|
4174
|
+
/**
|
|
4175
|
+
* A `SEEKRIT_*` value, or undefined if it is absent *or blank*.
|
|
4176
|
+
*
|
|
4177
|
+
* Blank has to mean absent. An unset CI secret, a `${VAR}` that expanded to
|
|
4178
|
+
* nothing, a bare `export SEEKRIT_TOKEN=` — all arrive as `""`, and `??` only
|
|
4179
|
+
* falls back on null/undefined. Left alone, an empty `SEEKRIT_API_URL` makes
|
|
4180
|
+
* every request relative and an empty `SEEKRIT_TOKEN` authenticates as a
|
|
4181
|
+
* bearer of nothing: a 401 where the honest answer is "you have no
|
|
4182
|
+
* credentials", pointing at the API instead of at the missing variable.
|
|
4183
|
+
*/
|
|
4184
|
+
function present(value) {
|
|
4185
|
+
const trimmed = value?.trim();
|
|
4186
|
+
return trimmed ? trimmed : void 0;
|
|
4187
|
+
}
|
|
3990
4188
|
function tryBuildContext(dotenvVars = {}) {
|
|
3991
4189
|
const config = readGlobalConfig();
|
|
3992
|
-
const fromEnv = (key) => process.env[key] ?? dotenvVars[key];
|
|
4190
|
+
const fromEnv = (key) => present(process.env[key]) ?? present(dotenvVars[key]);
|
|
3993
4191
|
const apiUrl = fromEnv("SEEKRIT_API_URL") ?? config.apiUrl ?? "https://api.seekrit.dev";
|
|
3994
4192
|
const token = fromEnv("SEEKRIT_TOKEN") ?? config.token ?? config.sessionToken;
|
|
3995
4193
|
const devUser = fromEnv("SEEKRIT_DEV_USER") ?? config.devUser;
|
|
@@ -4056,11 +4254,46 @@ async function resolveOrg(ctx, orgSlug) {
|
|
|
4056
4254
|
fail("specify --org (or run `seekrit init`)");
|
|
4057
4255
|
}
|
|
4058
4256
|
/**
|
|
4257
|
+
* The environment a service token is bound to.
|
|
4258
|
+
*
|
|
4259
|
+
* The binding lives on the token row, not in the token string — `GET
|
|
4260
|
+
* /v1/resolve` is where the API publishes it, and `seekrit whoami` reads it the
|
|
4261
|
+
* same way. One round trip, and it also resolves `--branch` against the bound
|
|
4262
|
+
* environment, which is the only environment a token may name a branch of.
|
|
4263
|
+
*/
|
|
4264
|
+
async function boundEnvTarget(ctx, opts) {
|
|
4265
|
+
let scope;
|
|
4266
|
+
try {
|
|
4267
|
+
({scope} = await ctx.client.resolve(opts.branch ? { branch: opts.branch } : {}));
|
|
4268
|
+
} catch (err) {
|
|
4269
|
+
fail(`specify --env — this token has no environment of its own to fall back to (${err instanceof Error ? err.message : String(err)})`);
|
|
4270
|
+
}
|
|
4271
|
+
const env = scope.branchOf ?? {
|
|
4272
|
+
envId: scope.envId,
|
|
4273
|
+
envSlug: scope.envSlug
|
|
4274
|
+
};
|
|
4275
|
+
const label = scope.branchOf ? `${scope.appSlug}/${scope.branchOf.envSlug}#${scope.envSlug}` : `${scope.appSlug}/${scope.envSlug}`;
|
|
4276
|
+
if (opts.org && opts.org !== scope.orgSlug && opts.org !== scope.orgId) fail(`this token belongs to ${scope.orgSlug}, not "${opts.org}"`);
|
|
4277
|
+
if (opts.env && opts.env !== env.envSlug && opts.env !== env.envId) fail(`this token is bound to ${scope.appSlug}/${env.envSlug}, not "${opts.env}" — add --app (or --group) to target another environment you hold a key for`);
|
|
4278
|
+
return {
|
|
4279
|
+
orgId: scope.orgId,
|
|
4280
|
+
envId: scope.envId,
|
|
4281
|
+
label
|
|
4282
|
+
};
|
|
4283
|
+
}
|
|
4284
|
+
/**
|
|
4059
4285
|
* Resolve an environment to operate on — an application env (`--app --env`,
|
|
4060
4286
|
* or the config's app + `--env`), a branch of one (`--branch`), or a group env
|
|
4061
4287
|
* (`--group --env`).
|
|
4288
|
+
*
|
|
4289
|
+
* `--env` is optional for a service token that names no `--app`/`--group`: it is
|
|
4290
|
+
* already bound to exactly one environment, so requiring the flag made every
|
|
4291
|
+
* documented one-liner (`seekrit secrets list`) fail for the CI job and the
|
|
4292
|
+
* agent that are the point of a token. The local MCP server has always inferred
|
|
4293
|
+
* it this way; this is the same rule, in one place, for both.
|
|
4062
4294
|
*/
|
|
4063
4295
|
async function resolveEnvTarget(ctx, opts) {
|
|
4296
|
+
if (!opts.app && !opts.group && isTokenAuth(ctx)) return boundEnvTarget(ctx, opts);
|
|
4064
4297
|
const org = await resolveOrg(ctx, opts.org);
|
|
4065
4298
|
if (!opts.env) fail("specify --env");
|
|
4066
4299
|
if (opts.group) {
|
|
@@ -4636,22 +4869,6 @@ const targetShape = {
|
|
|
4636
4869
|
group: z.string().optional().describe("target a group environment instead of an app"),
|
|
4637
4870
|
env: z.string().optional().describe("environment slug or id (a service token infers its own)")
|
|
4638
4871
|
};
|
|
4639
|
-
/**
|
|
4640
|
-
* Resolve which environment a secret tool addresses. A service token with no
|
|
4641
|
-
* explicit app/group targets its own bound environment (no flags needed);
|
|
4642
|
-
* everyone else names app|group + env.
|
|
4643
|
-
*/
|
|
4644
|
-
async function resolveTargetEnv(ctx, o) {
|
|
4645
|
-
if (isTokenAuth(ctx) && !o.app && !o.group) {
|
|
4646
|
-
const { scope } = await ctx.client.resolve();
|
|
4647
|
-
return {
|
|
4648
|
-
orgId: scope.orgId,
|
|
4649
|
-
envId: scope.envId,
|
|
4650
|
-
label: `${scope.appSlug}/${scope.envSlug}`
|
|
4651
|
-
};
|
|
4652
|
-
}
|
|
4653
|
-
return resolveEnvTarget(ctx, o);
|
|
4654
|
-
}
|
|
4655
4872
|
async function runMcpServer(options = {}) {
|
|
4656
4873
|
setFailThrows(true);
|
|
4657
4874
|
await ensureM2mAdminToken();
|
|
@@ -4932,7 +5149,7 @@ async function runMcpServer(options = {}) {
|
|
|
4932
5149
|
});
|
|
4933
5150
|
tool("list_secrets", "List secret names + versions in an environment (never values).", ro, targetShape, async (o) => {
|
|
4934
5151
|
const ctx = getCtx();
|
|
4935
|
-
const { orgId, envId } = await
|
|
5152
|
+
const { orgId, envId } = await resolveEnvTarget(ctx, o);
|
|
4936
5153
|
const { secrets } = await ctx.client.listSecrets(orgId, envId);
|
|
4937
5154
|
return secrets.map((s) => ({
|
|
4938
5155
|
name: s.name,
|
|
@@ -5121,7 +5338,7 @@ async function runMcpServer(options = {}) {
|
|
|
5121
5338
|
}, async (o) => {
|
|
5122
5339
|
const ctx = getCtx();
|
|
5123
5340
|
ensureDecryptable(ctx);
|
|
5124
|
-
const { orgId, envId } = await
|
|
5341
|
+
const { orgId, envId } = await resolveEnvTarget(ctx, o);
|
|
5125
5342
|
await encryptAndSetSecret(ctx, orgId, envId, o.name, o.value);
|
|
5126
5343
|
return {
|
|
5127
5344
|
ok: true,
|
|
@@ -5136,7 +5353,7 @@ async function runMcpServer(options = {}) {
|
|
|
5136
5353
|
version: z.number().int().positive().optional().describe("an earlier version from list_secret_versions (default: current)")
|
|
5137
5354
|
}, async (o) => {
|
|
5138
5355
|
const ctx = getCtx();
|
|
5139
|
-
const { orgId, envId } = await
|
|
5356
|
+
const { orgId, envId } = await resolveEnvTarget(ctx, o);
|
|
5140
5357
|
if (!o.reveal) {
|
|
5141
5358
|
const { secrets } = await ctx.client.listSecrets(orgId, envId);
|
|
5142
5359
|
const row = secrets.find((s) => s.name === o.name);
|
|
@@ -5171,7 +5388,7 @@ async function runMcpServer(options = {}) {
|
|
|
5171
5388
|
limit: z.number().int().min(1).max(200).optional().describe("default 20")
|
|
5172
5389
|
}, async (o) => {
|
|
5173
5390
|
const ctx = getCtx();
|
|
5174
|
-
const { orgId, envId } = await
|
|
5391
|
+
const { orgId, envId } = await resolveEnvTarget(ctx, o);
|
|
5175
5392
|
const { versions, currentVersion } = await ctx.client.listSecretVersions(orgId, envId, o.name, { limit: o.limit ?? 20 });
|
|
5176
5393
|
return {
|
|
5177
5394
|
currentVersion,
|
|
@@ -5189,7 +5406,7 @@ async function runMcpServer(options = {}) {
|
|
|
5189
5406
|
version: z.number().int().positive()
|
|
5190
5407
|
}, async (o) => {
|
|
5191
5408
|
const ctx = getCtx();
|
|
5192
|
-
const { orgId, envId } = await
|
|
5409
|
+
const { orgId, envId } = await resolveEnvTarget(ctx, o);
|
|
5193
5410
|
const { secret, restoredFrom } = await ctx.client.restoreSecret(orgId, envId, o.name, o.version);
|
|
5194
5411
|
return {
|
|
5195
5412
|
ok: true,
|
|
@@ -5203,7 +5420,7 @@ async function runMcpServer(options = {}) {
|
|
|
5203
5420
|
name: z.string()
|
|
5204
5421
|
}, async (o) => {
|
|
5205
5422
|
const ctx = getCtx();
|
|
5206
|
-
const { orgId, envId } = await
|
|
5423
|
+
const { orgId, envId } = await resolveEnvTarget(ctx, o);
|
|
5207
5424
|
await ctx.client.deleteSecret(orgId, envId, o.name);
|
|
5208
5425
|
return {
|
|
5209
5426
|
ok: true,
|
|
@@ -5326,7 +5543,7 @@ async function runMcpServer(options = {}) {
|
|
|
5326
5543
|
if (Boolean(o.user) === Boolean(o.token)) throw new Error("pass exactly one of user or token");
|
|
5327
5544
|
const ctx = getCtx();
|
|
5328
5545
|
ensureDecryptable(ctx);
|
|
5329
|
-
const { orgId, envId, label } = await
|
|
5546
|
+
const { orgId, envId, label } = await resolveEnvTarget(ctx, o);
|
|
5330
5547
|
const dek = await getDek(ctx, orgId, envId);
|
|
5331
5548
|
let principalType;
|
|
5332
5549
|
let principalId;
|
|
@@ -5504,7 +5721,7 @@ async function runMcpServer(options = {}) {
|
|
|
5504
5721
|
* `seekrit mcp`. tsdown bundles the shared server source in at build time, so the
|
|
5505
5722
|
* published package is self-contained and needs no `@seekrit/cli` install.
|
|
5506
5723
|
*/
|
|
5507
|
-
runMcpServer({ version: "0.8.
|
|
5724
|
+
runMcpServer({ version: "0.8.1" }).catch((err) => {
|
|
5508
5725
|
const message = err instanceof Error ? err.message : String(err);
|
|
5509
5726
|
process.stderr.write(`seekrit-mcp: fatal: ${message}\n`);
|
|
5510
5727
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seekrit/mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "npx-able MCP server for seekrit — let Claude Code and other MCP clients provision, manage, and inject end-to-end encrypted secrets.",
|
|
5
5
|
"mcpName": "dev.seekrit/mcp",
|
|
6
6
|
"type": "module",
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"node": ">=20"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
21
|
-
"zod": "^4.4
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
21
|
+
"zod": "^4.5.4"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^26.1.0",
|
|
25
25
|
"tsdown": "^0.22.3",
|
|
26
|
-
"vitest": "^4.1.
|
|
27
|
-
"@seekrit/cli": "
|
|
26
|
+
"vitest": "^4.1.11",
|
|
27
|
+
"@seekrit/cli": "1.2.2"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsdown",
|