@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.
- package/README.md +59 -6
- package/dist/index.js +98 -5
- 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.
|
|
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 — `
|
|
49
|
-
config at
|
|
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
|
-
| **
|
|
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.
|
|
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
|
|
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)
|
|
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.
|
|
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.
|
|
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.
|
|
25
|
+
"@seekrit/cli": "0.20.0"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsdown",
|