@mcp-index/sdk 0.8.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 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.
@@ -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"}
@@ -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.8.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",