@mcp-index/sdk 0.7.0 → 0.9.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/CHANGELOG.md +13 -0
- package/README.md +36 -0
- package/dist/src/cli.d.ts +7 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +57 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/login.d.ts +56 -0
- package/dist/src/login.d.ts.map +1 -0
- package/dist/src/login.js +233 -0
- package/dist/src/login.js.map +1 -0
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,19 @@ additive feature, patch = fix). Backward-compatible additions only in 0.2.0.
|
|
|
8
8
|
Staged for the next publish — `npm publish` packs the current `README.md`, so these
|
|
9
9
|
ride out automatically with the next version bump (no ad-hoc republish needed).
|
|
10
10
|
|
|
11
|
+
## 0.9.0 — 2026-07-14
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **`mcpindex login` CLI.** New `mcpindex` bin with `login` / `whoami`. `login` opens
|
|
15
|
+
the browser for GitHub OAuth (`read:user` only), captures the server-minted free API
|
|
16
|
+
key on a one-shot loopback listener bound to `127.0.0.1`, and stores it at
|
|
17
|
+
`~/.mcpindex/credentials.json` (`0600`, atomic write, never through a symlink). The
|
|
18
|
+
callback is bound to a per-session nonce carried in the loopback path (closes
|
|
19
|
+
key-fixation); the key never transits a third party. `MCPINDEX_WEB_BASE` overrides the
|
|
20
|
+
default host. `whoami` reports sign-in state without ever printing the key.
|
|
21
|
+
- **`--provider github|google`** on `mcpindex login` (default `github`) — selects the
|
|
22
|
+
identity provider; the loopback/nonce handoff is provider-agnostic.
|
|
23
|
+
|
|
11
24
|
## 0.7.0 — 2026-07-12
|
|
12
25
|
|
|
13
26
|
### Added
|
package/README.md
CHANGED
|
@@ -26,6 +26,26 @@ const guarded = wrap(session); // pins each tool's contract on first use (TOFU
|
|
|
26
26
|
// Review / Re-pin / Validate.
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Sign in (optional) — `mcpindex login`
|
|
30
|
+
|
|
31
|
+
Signs you in with GitHub and stores a free API key at `~/.mcpindex/credentials.json`
|
|
32
|
+
(mode `0600`), used to authenticate deeper trust checks. Optional — `wrap()` needs no
|
|
33
|
+
account and sends nothing on its own.
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm i -g @mcp-index/sdk # then:
|
|
37
|
+
mcpindex login # opens your browser; captures the key on a loopback listener
|
|
38
|
+
mcpindex login --provider google # sign in with Google instead of GitHub (default: github)
|
|
39
|
+
mcpindex whoami # shows sign-in state (never prints the key)
|
|
40
|
+
|
|
41
|
+
# or without a global install:
|
|
42
|
+
npx -p @mcp-index/sdk mcpindex login
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The key is minted server-side after GitHub OAuth and handed to a one-shot HTTP listener
|
|
46
|
+
bound to `127.0.0.1` — it never transits a third party. Point `MCPINDEX_WEB_BASE` at a
|
|
47
|
+
different host to override the default (`https://mcpindex.ai`).
|
|
48
|
+
|
|
29
49
|
## What it does
|
|
30
50
|
|
|
31
51
|
- Pins each MCP tool's contract trust-on-first-use (TOFU), persisted across restarts.
|
|
@@ -48,6 +68,22 @@ The verdict is a **contract-diff, not a safety oracle** — it tells you the con
|
|
|
48
68
|
*changed* versus your pin, not that the new contract is safe. It does not "block attacks"
|
|
49
69
|
or "guarantee safe." You decide what a HOLD means for your agent.
|
|
50
70
|
|
|
71
|
+
## Result-content scanning (opt-in, OFF by default)
|
|
72
|
+
|
|
73
|
+
Beyond the contract diff, the SDK can scan a tool's **returned result** (text blocks and
|
|
74
|
+
`structuredContent`) for prompt-injection / exfiltration / credential-path markers — the
|
|
75
|
+
attack where a *compromised result*, not a changed contract, tries to steer your agent. It
|
|
76
|
+
is **off by default** (zero behavior change) and enabled per-wrap:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const safe = wrap(session, { scanResults: true, onHold: (v) => { /* … */ } });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Once on, a tainted result is **withheld under `Guard` / `Strict`** and **passes through
|
|
83
|
+
under `Monitor`** (flag-only), mirroring the contract-diff postures; an optional `onHold`
|
|
84
|
+
callback receives the verdict instead of throwing. Added in 0.7.0; parity with the Python
|
|
85
|
+
client's `wrap(..., scan_results=...)`.
|
|
86
|
+
|
|
51
87
|
## Drift telemetry (opt-in, OFF by default)
|
|
52
88
|
|
|
53
89
|
The SDK can report **that** a tool's contract drifted — so mcpindex can track drift on servers
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `mcpindex` CLI. Currently one command: `login` — self-serve sign-in (GitHub or Google) that mints
|
|
4
|
+
* and stores a free api_key at ~/.mcpindex/credentials.json. See src/login.ts for the loopback flow.
|
|
5
|
+
*/
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG"}
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `mcpindex` CLI. Currently one command: `login` — self-serve sign-in (GitHub or Google) that mints
|
|
4
|
+
* and stores a free api_key at ~/.mcpindex/credentials.json. See src/login.ts for the loopback flow.
|
|
5
|
+
*/
|
|
6
|
+
import { loadApiKey, credentialsPath, runLogin, parseProvider } from "./login.js";
|
|
7
|
+
const USAGE = `mcpindex - trust-to-act gate for MCP tool calls
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
mcpindex login [--provider github|google] Sign in; mints and stores a free api_key (default github)
|
|
11
|
+
mcpindex whoami Show whether a key is stored (never prints the key)
|
|
12
|
+
mcpindex help Show this help
|
|
13
|
+
|
|
14
|
+
Env:
|
|
15
|
+
MCPINDEX_WEB_BASE Override the login host (default https://mcpindex.ai)
|
|
16
|
+
`;
|
|
17
|
+
async function main(argv) {
|
|
18
|
+
const cmd = argv[0];
|
|
19
|
+
switch (cmd) {
|
|
20
|
+
case "login": {
|
|
21
|
+
const provider = parseProvider(argv.slice(1));
|
|
22
|
+
if (provider === null) {
|
|
23
|
+
process.stderr.write(`login: --provider must be "github" or "google"\n`);
|
|
24
|
+
return 2;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
await runLogin({ provider });
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
process.stderr.write(`login failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
case "whoami": {
|
|
36
|
+
const has = loadApiKey() !== null;
|
|
37
|
+
process.stdout.write(has ? `signed in (key at ${credentialsPath()})\n` : "not signed in - run: mcpindex login\n");
|
|
38
|
+
return has ? 0 : 1;
|
|
39
|
+
}
|
|
40
|
+
case "help":
|
|
41
|
+
case "--help":
|
|
42
|
+
case "-h":
|
|
43
|
+
case undefined:
|
|
44
|
+
process.stdout.write(USAGE);
|
|
45
|
+
return 0;
|
|
46
|
+
default:
|
|
47
|
+
process.stderr.write(`unknown command: ${cmd}\n\n${USAGE}`);
|
|
48
|
+
return 2;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
main(process.argv.slice(2))
|
|
52
|
+
.then((code) => process.exit(code))
|
|
53
|
+
.catch((e) => {
|
|
54
|
+
process.stderr.write(`mcpindex: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAElF,MAAM,KAAK,GAAG;;;;;;;;;CASb,CAAC;AAEF,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;gBACzE,OAAO,CAAC,CAAC;YACX,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;gBAC7B,OAAO,CAAC,CAAC;YACX,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBACtF,OAAO,CAAC,CAAC;YACX,CAAC;QACH,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,GAAG,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC;YAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,qBAAqB,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC,uCAAuC,CAAC,CAAC;YAClH,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;QACD,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI,CAAC;QACV,KAAK,SAAS;YACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,CAAC;QACX;YACE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,OAAO,KAAK,EAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACxB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KAClC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAClF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mcpindex login` - self-serve GitHub login that mints a free api_key and stores it.
|
|
3
|
+
*
|
|
4
|
+
* Flow (the gh/vercel-login pattern): start a LOOPBACK http listener on a random port, open the
|
|
5
|
+
* browser to `<web>/api/auth/login/start?cli_callback=http://127.0.0.1:<port>`, the web callback
|
|
6
|
+
* redirects the browser back to the loopback listener with `?key=...`, we capture it, store it at
|
|
7
|
+
* `~/.mcpindex/credentials.json` (mode 0600), and tell the browser to close.
|
|
8
|
+
*
|
|
9
|
+
* The api_key never transits a third party: GitHub sees only the OAuth code; the web mints the
|
|
10
|
+
* key and hands it to THIS machine's loopback. Storage is 0600 so other users can't read it.
|
|
11
|
+
*/
|
|
12
|
+
/** Self-serve identity providers the web login supports. Default is github. */
|
|
13
|
+
export type LoginProvider = "github" | "google";
|
|
14
|
+
export declare function credentialsPath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Persist the api_key (0600). Only the key is stored; never the raw OAuth code/token.
|
|
17
|
+
* Write to a fresh temp file (`wx`, never through a pre-staged symlink) then atomic-rename into
|
|
18
|
+
* place, so the key never lands in a world-readable inode and no partial-write window exists.
|
|
19
|
+
*/
|
|
20
|
+
export declare function saveApiKey(apiKey: string, path?: string): void;
|
|
21
|
+
/** Load the stored api_key, or null if absent/unreadable. */
|
|
22
|
+
export declare function loadApiKey(path?: string): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Extract the minted key from the loopback callback URL, but only if the request path matches the
|
|
25
|
+
* session nonce (`/<state>`). The nonce binds the callback to the login this process started: a
|
|
26
|
+
* webpage the victim has open cannot forge it (it never learns the path), which closes the
|
|
27
|
+
* key-fixation window on a shared loopback port. Returns null on any mismatch/malformed input.
|
|
28
|
+
*/
|
|
29
|
+
export declare function extractKeyFromCallback(reqUrl: string, state: string): string | null;
|
|
30
|
+
/** A url-safe one-time nonce that rides in the loopback callback path. */
|
|
31
|
+
export declare function newLoginState(): string;
|
|
32
|
+
/** Parse an optional `--provider <github|google>` from the login args. Returns null on a bad value. */
|
|
33
|
+
export declare function parseProvider(args: string[]): LoginProvider | null;
|
|
34
|
+
export declare function buildStartUrl(webBase: string, port: number, state: string, provider?: LoginProvider): string;
|
|
35
|
+
export interface LoginResult {
|
|
36
|
+
apiKey: string;
|
|
37
|
+
path: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Run the interactive login. Resolves with the stored key, or rejects on timeout. `print` and
|
|
41
|
+
* `open` are injectable for tests; by default the browser opens automatically.
|
|
42
|
+
*/
|
|
43
|
+
export declare function runLogin(opts?: {
|
|
44
|
+
webBase?: string;
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
print?: (msg: string) => void;
|
|
47
|
+
open?: (url: string) => void;
|
|
48
|
+
/** Identity provider to sign in with (default github). */
|
|
49
|
+
provider?: LoginProvider;
|
|
50
|
+
/** Test seam: observe the bound address/port. Purely observational — never changes the bind. */
|
|
51
|
+
onListen?: (info: {
|
|
52
|
+
address: string;
|
|
53
|
+
port: number;
|
|
54
|
+
}) => void;
|
|
55
|
+
}): Promise<LoginResult>;
|
|
56
|
+
//# sourceMappingURL=login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEhD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,MAA0B,GAAG,IAAI,CA+BjF;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,IAAI,GAAE,MAA0B,GAAG,MAAM,GAAG,IAAI,CAO1E;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASnF;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,uGAAuG;AACvG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,GAAG,IAAI,CAMlE;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,aAAwB,GACjC,MAAM,CAOR;AAkCD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,IAAI,GAAE;IACJ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,gGAAgG;IAChG,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACzD,GACL,OAAO,CAAC,WAAW,CAAC,CAoFtB"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mcpindex login` - self-serve GitHub login that mints a free api_key and stores it.
|
|
3
|
+
*
|
|
4
|
+
* Flow (the gh/vercel-login pattern): start a LOOPBACK http listener on a random port, open the
|
|
5
|
+
* browser to `<web>/api/auth/login/start?cli_callback=http://127.0.0.1:<port>`, the web callback
|
|
6
|
+
* redirects the browser back to the loopback listener with `?key=...`, we capture it, store it at
|
|
7
|
+
* `~/.mcpindex/credentials.json` (mode 0600), and tell the browser to close.
|
|
8
|
+
*
|
|
9
|
+
* The api_key never transits a third party: GitHub sees only the OAuth code; the web mints the
|
|
10
|
+
* key and hands it to THIS machine's loopback. Storage is 0600 so other users can't read it.
|
|
11
|
+
*/
|
|
12
|
+
import { randomBytes } from "node:crypto";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import { createServer } from "node:http";
|
|
15
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { homedir, platform } from "node:os";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
const DEFAULT_WEB_BASE = "https://mcpindex.ai";
|
|
19
|
+
export function credentialsPath() {
|
|
20
|
+
return join(homedir(), ".mcpindex", "credentials.json");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Persist the api_key (0600). Only the key is stored; never the raw OAuth code/token.
|
|
24
|
+
* Write to a fresh temp file (`wx`, never through a pre-staged symlink) then atomic-rename into
|
|
25
|
+
* place, so the key never lands in a world-readable inode and no partial-write window exists.
|
|
26
|
+
*/
|
|
27
|
+
export function saveApiKey(apiKey, path = credentialsPath()) {
|
|
28
|
+
const dir = dirname(path);
|
|
29
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
30
|
+
try {
|
|
31
|
+
chmodSync(dir, 0o700); // tighten even if the dir pre-existed at a wider mode (mkdir mode only applies on create)
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* best-effort */
|
|
35
|
+
}
|
|
36
|
+
const tmp = `${path}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
37
|
+
try {
|
|
38
|
+
unlinkSync(tmp);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* no stale temp - expected */
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
// `wx` = O_CREAT|O_EXCL: fails rather than following a pre-staged symlink/file at `tmp`.
|
|
45
|
+
writeFileSync(tmp, JSON.stringify({ api_key: apiKey }) + "\n", { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
46
|
+
try {
|
|
47
|
+
chmodSync(tmp, 0o600);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* best-effort on platforms without POSIX modes */
|
|
51
|
+
}
|
|
52
|
+
renameSync(tmp, path); // atomic; replaces any pre-existing file/symlink at the destination name
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
try {
|
|
56
|
+
unlinkSync(tmp); // never leave a key-bearing temp behind on a failed write/rename
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* nothing to clean */
|
|
60
|
+
}
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Load the stored api_key, or null if absent/unreadable. */
|
|
65
|
+
export function loadApiKey(path = credentialsPath()) {
|
|
66
|
+
try {
|
|
67
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
68
|
+
return typeof raw.api_key === "string" && raw.api_key ? raw.api_key : null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extract the minted key from the loopback callback URL, but only if the request path matches the
|
|
76
|
+
* session nonce (`/<state>`). The nonce binds the callback to the login this process started: a
|
|
77
|
+
* webpage the victim has open cannot forge it (it never learns the path), which closes the
|
|
78
|
+
* key-fixation window on a shared loopback port. Returns null on any mismatch/malformed input.
|
|
79
|
+
*/
|
|
80
|
+
export function extractKeyFromCallback(reqUrl, state) {
|
|
81
|
+
try {
|
|
82
|
+
const u = new URL(reqUrl, "http://127.0.0.1");
|
|
83
|
+
if (!state || u.pathname !== `/${state}`)
|
|
84
|
+
return null;
|
|
85
|
+
const key = u.searchParams.get("key");
|
|
86
|
+
return typeof key === "string" && key.startsWith("mcpk_") ? key : null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** A url-safe one-time nonce that rides in the loopback callback path. */
|
|
93
|
+
export function newLoginState() {
|
|
94
|
+
return randomBytes(24).toString("base64url");
|
|
95
|
+
}
|
|
96
|
+
/** Parse an optional `--provider <github|google>` from the login args. Returns null on a bad value. */
|
|
97
|
+
export function parseProvider(args) {
|
|
98
|
+
const i = args.indexOf("--provider");
|
|
99
|
+
if (i === -1)
|
|
100
|
+
return "github"; // default
|
|
101
|
+
const v = args[i + 1];
|
|
102
|
+
if (v === "github" || v === "google")
|
|
103
|
+
return v;
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
export function buildStartUrl(webBase, port, state, provider = "github") {
|
|
107
|
+
const cb = encodeURIComponent(`http://127.0.0.1:${port}/${state}`);
|
|
108
|
+
let url = `${webBase.replace(/\/+$/, "")}/api/auth/login/start?cli_callback=${cb}`;
|
|
109
|
+
// The web defaults to github when the param is absent; only append for the non-default provider
|
|
110
|
+
// so the github URL stays byte-for-byte identical (backward-compat).
|
|
111
|
+
if (provider === "google")
|
|
112
|
+
url += `&provider=google`;
|
|
113
|
+
return url;
|
|
114
|
+
}
|
|
115
|
+
/** True only for a well-formed http(s) URL. Guards against a malformed MCPINDEX_WEB_BASE. */
|
|
116
|
+
function isHttpUrl(s) {
|
|
117
|
+
try {
|
|
118
|
+
const u = new URL(s);
|
|
119
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function openBrowser(url) {
|
|
126
|
+
let cmd;
|
|
127
|
+
let args;
|
|
128
|
+
if (platform() === "darwin") {
|
|
129
|
+
cmd = "open";
|
|
130
|
+
args = [url];
|
|
131
|
+
}
|
|
132
|
+
else if (platform() === "win32") {
|
|
133
|
+
// rundll32 FileProtocolHandler opens the default browser WITHOUT routing the URL through
|
|
134
|
+
// cmd.exe's parser (which would interpret &, ^, |, % in the URL). No shell on any platform.
|
|
135
|
+
cmd = "rundll32";
|
|
136
|
+
args = ["url.dll,FileProtocolHandler", url];
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
cmd = "xdg-open";
|
|
140
|
+
args = [url];
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
/* headless / no browser -> the caller prints the URL to open manually */
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Run the interactive login. Resolves with the stored key, or rejects on timeout. `print` and
|
|
151
|
+
* `open` are injectable for tests; by default the browser opens automatically.
|
|
152
|
+
*/
|
|
153
|
+
export function runLogin(opts = {}) {
|
|
154
|
+
const webBase = opts.webBase ?? process.env.MCPINDEX_WEB_BASE ?? DEFAULT_WEB_BASE;
|
|
155
|
+
const provider = opts.provider ?? "github";
|
|
156
|
+
const timeoutMs = opts.timeoutMs ?? 180_000;
|
|
157
|
+
const print = opts.print ?? ((m) => process.stderr.write(m + "\n"));
|
|
158
|
+
const open = opts.open ?? openBrowser;
|
|
159
|
+
if (!isHttpUrl(webBase)) {
|
|
160
|
+
return Promise.reject(new Error(`invalid login web base (MCPINDEX_WEB_BASE): ${webBase}`));
|
|
161
|
+
}
|
|
162
|
+
const state = newLoginState();
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
let settled = false; // one-shot: guards resolve/reject and the credential write against re-entry
|
|
165
|
+
const server = createServer((req, res) => {
|
|
166
|
+
const key = extractKeyFromCallback(req.url ?? "", state);
|
|
167
|
+
if (!key) {
|
|
168
|
+
res.writeHead(400, { "content-type": "text/plain" });
|
|
169
|
+
res.end("no key");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (settled) {
|
|
173
|
+
// A second valid callback must NOT overwrite the stored key or double-resolve.
|
|
174
|
+
res.writeHead(409, { "content-type": "text/plain" });
|
|
175
|
+
res.end("already completed");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
settled = true;
|
|
179
|
+
// Save BEFORE claiming success, so a storage failure surfaces as an error page + non-zero
|
|
180
|
+
// exit rather than a "Signed in" page that contradicts a failed CLI.
|
|
181
|
+
try {
|
|
182
|
+
saveApiKey(key);
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
cleanup();
|
|
186
|
+
res.writeHead(500, { "content-type": "text/html; charset=utf-8" });
|
|
187
|
+
res.end("<!doctype html><p>Could not store your key. Return to your terminal.</p>");
|
|
188
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
192
|
+
res.end("<!doctype html><p>Signed in. You can close this tab and return to your terminal.</p>");
|
|
193
|
+
cleanup();
|
|
194
|
+
print("Signed in. Your mcpindex key is stored.");
|
|
195
|
+
resolve({ apiKey: key, path: credentialsPath() });
|
|
196
|
+
});
|
|
197
|
+
const timer = setTimeout(() => {
|
|
198
|
+
if (settled)
|
|
199
|
+
return;
|
|
200
|
+
settled = true;
|
|
201
|
+
cleanup();
|
|
202
|
+
reject(new Error("login timed out"));
|
|
203
|
+
}, timeoutMs);
|
|
204
|
+
function cleanup() {
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
server.close();
|
|
207
|
+
}
|
|
208
|
+
server.on("error", (e) => {
|
|
209
|
+
if (settled)
|
|
210
|
+
return;
|
|
211
|
+
settled = true;
|
|
212
|
+
cleanup();
|
|
213
|
+
reject(e);
|
|
214
|
+
});
|
|
215
|
+
// Loopback ONLY: binding all interfaces would make the key listener reachable off-host.
|
|
216
|
+
server.listen(0, "127.0.0.1", () => {
|
|
217
|
+
const addr = server.address();
|
|
218
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
219
|
+
const address = typeof addr === "object" && addr ? addr.address : "";
|
|
220
|
+
if (!port) {
|
|
221
|
+
settled = true;
|
|
222
|
+
cleanup();
|
|
223
|
+
reject(new Error("could not bind a loopback port"));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
opts.onListen?.({ address, port });
|
|
227
|
+
const url = buildStartUrl(webBase, port, state, provider);
|
|
228
|
+
print(`Opening your browser to sign in:\n ${url}\nIf it does not open, paste that URL into your browser.`);
|
|
229
|
+
open(url);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/login.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-index/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Pre-flight MCP tool-contract drift interceptor — one-line wrap() over an MCP client session that HOLDs a tool call before your agent runs it when the contract drifted from your pin. No credential handling.",
|
|
5
5
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
6
6
|
"author": "Bhartis LLC",
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"type": "module",
|
|
14
14
|
"main": "./dist/src/index.js",
|
|
15
15
|
"types": "./dist/src/index.d.ts",
|
|
16
|
+
"bin": {
|
|
17
|
+
"mcpindex": "./dist/src/cli.js"
|
|
18
|
+
},
|
|
16
19
|
"exports": {
|
|
17
20
|
".": {
|
|
18
21
|
"types": "./dist/src/index.d.ts",
|