@seekrit/mcp 0.2.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +59 -6
  2. package/dist/index.js +98 -5
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -18,15 +18,38 @@ it just publishes it as its own binary so no CLI install is required first.
18
18
  ## Setup
19
19
 
20
20
  Add it to any MCP client with a stdio server whose command runs this package via
21
- `npx`, passing a credential in its environment. For Claude Code:
21
+ `npx`, passing a credential in its environment.
22
+
23
+ **Fully autonomous agent (recommended).** An agent with no account can
24
+ [sign up](https://seekrit.dev/docs/guides/ai-agents) for **machine credentials**
25
+ (`POST https://mcp.seekrit.dev/signup`) and use the *same* client id + secret for
26
+ everything — the hosted metadata plane and this local crypto plane. Point this
27
+ server at them and it mints its own admin token automatically on first use
28
+ (keyless — the keypair is generated locally), so there is no token to copy:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "seekrit": {
34
+ "command": "npx",
35
+ "args": ["-y", "@seekrit/mcp"],
36
+ "env": {
37
+ "SEEKRIT_CLIENT_ID": "<your client id>",
38
+ "SEEKRIT_CLIENT_SECRET": "<your client secret>"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ **With an existing token.** If you already hold a service token, pass it directly.
46
+ For Claude Code:
22
47
 
23
48
  ```sh
24
49
  # An admin token lets the agent provision structure (apps/envs/tokens) too:
25
50
  claude mcp add seekrit --env SEEKRIT_TOKEN=skt_… -- npx -y @seekrit/mcp
26
51
  ```
27
52
 
28
- Or drop a `.mcp.json` in your project — the whole setup is this snippet:
29
-
30
53
  ```json
31
54
  {
32
55
  "mcpServers": {
@@ -43,14 +66,44 @@ Or drop a `.mcp.json` in your project — the whole setup is this snippet:
43
66
 
44
67
  Requires Node ≥ 20. Set `SEEKRIT_API_URL` too if you're not on the hosted API.
45
68
 
69
+ ## As a container
70
+
71
+ For a sandbox with no Node toolchain, the same server ships as a multi-arch
72
+ Docker image, [`seekritdev/mcp`](https://hub.docker.com/r/seekritdev/mcp).
73
+ Register a stdio server that runs `docker run -i` — the `-i` is required, since
74
+ stdin is the MCP transport:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "seekrit": {
80
+ "command": "docker",
81
+ "args": [
82
+ "run", "-i", "--rm",
83
+ "-e", "SEEKRIT_TOKEN",
84
+ "-v", "${PWD}:/work",
85
+ "seekritdev/mcp"
86
+ ]
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ The bare `-e SEEKRIT_TOKEN` forwards the token from the client's environment.
93
+ Mount a workdir at `/work` for the `run_command` tool to operate on (it runs
94
+ *inside* the container). Pin a release (`seekritdev/mcp:0.2.0`) or track `:edge`.
95
+ The container still decrypts locally — nothing plaintext leaves it.
96
+
46
97
  ## Choosing the credential
47
98
 
48
- The server authenticates exactly like the CLI — `SEEKRIT_TOKEN`, or saved
49
- config at `~/.config/seekrit/config.json`.
99
+ The server authenticates exactly like the CLI — `SEEKRIT_CLIENT_ID` +
100
+ `SEEKRIT_CLIENT_SECRET`, `SEEKRIT_TOKEN`, or saved config at
101
+ `~/.config/seekrit/config.json`.
50
102
 
51
103
  | Credential | Good for | Notes |
52
104
  | --- | --- | --- |
53
- | **Admin token** (`seekrit token create --admin`) | Provisioning: create apps/groups/envs, compose, grant, mint tokens | Org-scoped; the only headless way to create structure. |
105
+ | **Machine credentials** (`SEEKRIT_CLIENT_ID` + `SEEKRIT_CLIENT_SECRET`) | Fully autonomous agents: one credential for both planes | Auto-mints + caches an admin token on first use. Get them from `POST /signup`. |
106
+ | **Admin token** (`seekrit token create --admin`) | Provisioning: create apps/groups/envs, compose, grant, mint tokens | Org-scoped; a fixed headless credential for structure. |
54
107
  | **Runtime token** (bound to an env) | Reading/writing/injecting one environment's secrets | Self-decrypts — no passphrase. Cannot provision. |
55
108
 
56
109
  Under user auth (no token), tools that decrypt need `SEEKRIT_PASSPHRASE` in the
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import { z } from "zod";
6
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import { dirname, join, parse } from "node:path";
9
9
  import { createInterface } from "node:readline";
@@ -664,7 +664,7 @@ function isServiceToken(value) {
664
664
  }
665
665
  //#endregion
666
666
  //#region ../cli/package.json
667
- var version$1 = "0.19.0";
667
+ var version$1 = "0.20.0";
668
668
  const PROJECT_FILE = "seekrit.json";
669
669
  function globalConfigPath() {
670
670
  return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "seekrit", "config.json");
@@ -674,6 +674,26 @@ function readGlobalConfig() {
674
674
  if (!existsSync(path)) return {};
675
675
  return JSON.parse(readFileSync(path, "utf8"));
676
676
  }
677
+ function writeGlobalConfig(update) {
678
+ const path = globalConfigPath();
679
+ const merged = {
680
+ ...readGlobalConfig(),
681
+ ...update
682
+ };
683
+ mkdirSync(dirname(path), { recursive: true });
684
+ writeFileSync(path, `${JSON.stringify(merged, null, 2)}\n`, { mode: 384 });
685
+ }
686
+ /** The admin token previously auto-minted from these M2M credentials, if any. */
687
+ function readCachedM2mToken(clientId) {
688
+ return readGlobalConfig().m2mTokens?.[clientId];
689
+ }
690
+ /** Remember an auto-minted admin token so the next run reuses it. */
691
+ function cacheM2mToken(clientId, token) {
692
+ writeGlobalConfig({ m2mTokens: {
693
+ ...readGlobalConfig().m2mTokens,
694
+ [clientId]: token
695
+ } });
696
+ }
677
697
  /** Walk up from cwd looking for seekrit.json. */
678
698
  function findProjectConfig(startDir = process.cwd()) {
679
699
  let dir = startDir;
@@ -718,7 +738,8 @@ var SeekritClient = class {
718
738
  const token = await this.auth.getToken();
719
739
  if (!token) throw new SeekritApiError(401, "unauthorized", "session expired");
720
740
  headers.authorization = `Bearer ${token}`;
721
- } else headers["x-seekrit-dev-user"] = this.auth.email;
741
+ } else if (this.auth.type === "m2m") headers.authorization = `Basic ${btoa(`${this.auth.clientId}:${this.auth.clientSecret}`)}`;
742
+ else headers["x-seekrit-dev-user"] = this.auth.email;
722
743
  if (body !== void 0) headers["content-type"] = "application/json";
723
744
  const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
724
745
  method,
@@ -1296,6 +1317,77 @@ async function kmsRecoverMaterial(ctx, orgId, keyId, version) {
1296
1317
  };
1297
1318
  }
1298
1319
  //#endregion
1320
+ //#region ../cli/src/m2m.ts
1321
+ /**
1322
+ * Resolve M2M client credentials from (in order) the process environment, a
1323
+ * `.env` overlay (for `seekrit run`), then saved config. Both halves must come
1324
+ * through for the credential to be usable.
1325
+ */
1326
+ function readM2mCreds(dotenvVars = {}) {
1327
+ const config = readGlobalConfig();
1328
+ const fromEnv = (key) => process.env[key] ?? dotenvVars[key];
1329
+ const clientId = fromEnv("SEEKRIT_CLIENT_ID") ?? config.clientId;
1330
+ const clientSecret = fromEnv("SEEKRIT_CLIENT_SECRET") ?? config.clientSecret;
1331
+ if (!clientId || !clientSecret) return null;
1332
+ return {
1333
+ clientId,
1334
+ clientSecret
1335
+ };
1336
+ }
1337
+ /** True when a service/dev credential is already configured explicitly. */
1338
+ function hasExplicitCredential(dotenvVars) {
1339
+ const fromEnv = (key) => process.env[key] ?? dotenvVars[key];
1340
+ if (fromEnv("SEEKRIT_TOKEN") || fromEnv("SEEKRIT_DEV_USER")) return true;
1341
+ const config = readGlobalConfig();
1342
+ return Boolean(config.token || config.devUser);
1343
+ }
1344
+ /** Mint a fresh org-scoped admin token using M2M credentials (keyless). */
1345
+ async function mintAdminToken(apiUrl, creds) {
1346
+ const client = new SeekritClient({
1347
+ baseUrl: apiUrl,
1348
+ auth: {
1349
+ type: "m2m",
1350
+ clientId: creds.clientId,
1351
+ clientSecret: creds.clientSecret
1352
+ }
1353
+ });
1354
+ const { orgs } = await client.listOrgs();
1355
+ const org = orgs[0];
1356
+ if (!org) throw new Error("machine client has no organization — sign up first (POST /signup)");
1357
+ const created = await createServiceToken();
1358
+ await client.createToken(org.id, {
1359
+ name: "agent-admin (auto)",
1360
+ tokenId: created.tokenId,
1361
+ tokenHash: created.tokenHash,
1362
+ publicKeyJwk: created.publicKeyJwk,
1363
+ role: "admin",
1364
+ environmentId: null
1365
+ });
1366
+ return created.token;
1367
+ }
1368
+ /**
1369
+ * If M2M credentials are configured (and no explicit token/dev-user is), ensure
1370
+ * an admin token exists for them — minting and caching one on first use — and
1371
+ * expose it as `SEEKRIT_TOKEN` so the normal credential resolution
1372
+ * ({@link tryBuildContext}) picks it up. A no-op (returns null) when there is
1373
+ * nothing to bootstrap. Throws only when credentials ARE present but minting
1374
+ * fails, so the reason surfaces instead of a later opaque "no credentials".
1375
+ */
1376
+ async function ensureM2mAdminToken(dotenvVars = {}) {
1377
+ if (hasExplicitCredential(dotenvVars)) return null;
1378
+ const creds = readM2mCreds(dotenvVars);
1379
+ if (!creds) return null;
1380
+ const cached = readCachedM2mToken(creds.clientId);
1381
+ if (cached) {
1382
+ process.env.SEEKRIT_TOKEN = cached;
1383
+ return cached;
1384
+ }
1385
+ const token = await mintAdminToken(process.env.SEEKRIT_API_URL ?? dotenvVars.SEEKRIT_API_URL ?? readGlobalConfig().apiUrl ?? "https://api.seekrit.dev", creds);
1386
+ cacheM2mToken(creds.clientId, token);
1387
+ process.env.SEEKRIT_TOKEN = token;
1388
+ return token;
1389
+ }
1390
+ //#endregion
1299
1391
  //#region ../cli/src/dotenv.ts
1300
1392
  /**
1301
1393
  * Minimal `.env` parser: `KEY=VALUE`, `#` comments, an optional `export`
@@ -1416,7 +1508,7 @@ function errText(err) {
1416
1508
  /** Build the client context or throw a friendly, agent-readable error. */
1417
1509
  function getCtx() {
1418
1510
  const ctx = tryBuildContext();
1419
- if (!ctx) throw new Error("no credentials — set SEEKRIT_TOKEN (a skt_… token) or SEEKRIT_DEV_USER in the MCP server env, or run `seekrit login` first");
1511
+ if (!ctx) throw new Error("no credentials — set SEEKRIT_CLIENT_ID + SEEKRIT_CLIENT_SECRET (machine credentials from POST /signup) or SEEKRIT_TOKEN (a skt_… token) in the MCP server env, or run `seekrit login` first");
1420
1512
  return ctx;
1421
1513
  }
1422
1514
  /**
@@ -1504,6 +1596,7 @@ async function resolveTargetEnv(ctx, o) {
1504
1596
  }
1505
1597
  async function runMcpServer(options = {}) {
1506
1598
  setFailThrows(true);
1599
+ await ensureM2mAdminToken();
1507
1600
  const server = new McpServer({
1508
1601
  name: "seekrit",
1509
1602
  version: options.version ?? version$1
@@ -2221,7 +2314,7 @@ async function runMcpServer(options = {}) {
2221
2314
  * `seekrit mcp`. tsdown bundles the shared server source in at build time, so the
2222
2315
  * published package is self-contained and needs no `@seekrit/cli` install.
2223
2316
  */
2224
- runMcpServer({ version: "0.2.0" }).catch((err) => {
2317
+ runMcpServer({ version: "0.4.0" }).catch((err) => {
2225
2318
  const message = err instanceof Error ? err.message : String(err);
2226
2319
  process.stderr.write(`seekrit-mcp: fatal: ${message}\n`);
2227
2320
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekrit/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
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
  "type": "module",
6
6
  "publishConfig": {
@@ -22,7 +22,7 @@
22
22
  "devDependencies": {
23
23
  "@types/node": "^26.1.0",
24
24
  "tsdown": "^0.22.3",
25
- "@seekrit/cli": "0.19.0"
25
+ "@seekrit/cli": "0.20.0"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "tsdown",