premanmcp 0.3.4 → 0.3.5

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 CHANGED
@@ -7,19 +7,24 @@ PreMan is agent-first API infrastructure. It lets backend teams expose endpoints
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npx premanmcp install
10
+ npm exec -y premanmcp@latest -- install
11
11
  ```
12
12
 
13
- This writes an `preman` MCP server into `~/.cursor/mcp.json`:
13
+ Local development form:
14
+
15
+ ```bash
16
+ node bin/cli.js install
17
+ ```
18
+
19
+ First-time users are prompted for email, OTP, and password directly in the terminal. PreMan creates or connects the account, generates an API key, saves it to `~/.preman/credentials.json`, then writes a `preman` MCP server into `~/.cursor/mcp.json`:
14
20
 
15
21
  ```json
16
22
  {
17
23
  "mcpServers": {
18
24
  "preman": {
19
- "command": "npx",
20
- "args": ["-y", "premanmcp"],
25
+ "command": "npm",
26
+ "args": ["exec", "-y", "--package", "premanmcp@latest", "--", "premanmcp"],
21
27
  "env": {
22
- "PREMAN_API_KEY": "pm_live_YOUR_API_KEY_HERE",
23
28
  "PREMAN_BACKEND": "https://api.preman.live",
24
29
  "PREMAN_FRONTEND": "https://app.preman.live"
25
30
  }
@@ -28,23 +33,30 @@ This writes an `preman` MCP server into `~/.cursor/mcp.json`:
28
33
  }
29
34
  ```
30
35
 
31
- Replace `pm_live_YOUR_API_KEY_HERE` with an API key from [PreMan settings](https://app.preman.live/settings/), then restart Cursor or toggle the MCP server off/on.
36
+ Restart Cursor or toggle the MCP server off/on after install. The MCP server loads the saved key automatically.
37
+
38
+ You can also create or connect your account before install:
39
+
40
+ ```bash
41
+ npm exec -y premanmcp@latest -- login
42
+ ```
32
43
 
33
44
  You can also pass the key directly:
34
45
 
35
46
  ```bash
36
- npx premanmcp install --api-key pm_live_xxx
47
+ npm exec -y premanmcp@latest -- install --api-key pm_live_xxx
37
48
  ```
38
49
 
39
50
  For project-local installation:
40
51
 
41
52
  ```bash
42
- npx premanmcp install --project
53
+ npm exec -y premanmcp@latest -- install --project
43
54
  ```
44
55
 
45
56
  ## What It Does
46
57
 
47
58
  - Converts API endpoints into agent-callable MCP tools.
59
+ - Creates/connects a PreMan account from the terminal or IDE agent.
48
60
  - Lets agents test real backend endpoints from the IDE.
49
61
  - Syncs endpoint inventory across backend and frontend workflows.
50
62
  - Adds API-key auth for PreMan MCP access.
@@ -56,7 +68,11 @@ npx premanmcp install --project
56
68
  After installing, ask your coding agent:
57
69
 
58
70
  ```text
59
- Use the PreMan MCP to convert my login endpoint into an MCP tool.
71
+ Use the PreMan MCP to scan this codebase, deploy a hosted MCP, and give me the install command.
72
+ ```
73
+
74
+ ```text
75
+ Use PreMan to sign me up, verify my email OTP, create my API key, then scan this project.
60
76
  ```
61
77
 
62
78
  ```text
@@ -76,7 +92,7 @@ Show me the audit log for this hosted MCP.
76
92
  The installer targets Cursor by default:
77
93
 
78
94
  ```bash
79
- npx premanmcp install
95
+ npm exec -y premanmcp@latest -- install
80
96
  ```
81
97
 
82
98
  Manual Cursor config:
@@ -85,11 +101,8 @@ Manual Cursor config:
85
101
  {
86
102
  "mcpServers": {
87
103
  "preman": {
88
- "command": "npx",
89
- "args": ["-y", "premanmcp"],
90
- "env": {
91
- "PREMAN_API_KEY": "pm_live_YOUR_API_KEY_HERE"
92
- }
104
+ "command": "npm",
105
+ "args": ["exec", "-y", "--package", "premanmcp@latest", "--", "premanmcp"]
93
106
  }
94
107
  }
95
108
  }
@@ -98,17 +111,18 @@ Manual Cursor config:
98
111
  ## Claude Code
99
112
 
100
113
  ```bash
101
- claude mcp add preman -- npx -y premanmcp
114
+ claude mcp add preman -- npm exec -y --package premanmcp@latest -- premanmcp
102
115
  ```
103
116
 
104
117
  ## CLI
105
118
 
106
119
  ```bash
107
- npx premanmcp # Start the MCP server
108
- npx premanmcp install # Install Cursor MCP config
109
- npx premanmcp install --print # Print config without writing
110
- npx premanmcp install --project # Write .cursor/mcp.json in current repo
111
- npx premanmcp install --api-key KEY # Install with your PreMan API key
120
+ npm exec -y premanmcp@latest -- # Start the MCP server
121
+ npm exec -y premanmcp@latest -- login # Create/login and generate a PreMan API key
122
+ npm exec -y premanmcp@latest -- install # Install Cursor MCP config
123
+ npm exec -y premanmcp@latest -- install --project # Write .cursor/mcp.json in current repo
124
+ npm exec -y premanmcp@latest -- install --print # Print config without writing
125
+ npm exec -y premanmcp@latest -- install --api-key KEY # Install with your PreMan API key
112
126
  ```
113
127
 
114
128
  Options:
@@ -118,6 +132,7 @@ Options:
118
132
  - `--frontend <url>`: PreMan frontend URL. Defaults to `https://app.preman.live`.
119
133
  - `--name <name>`: MCP server name. Defaults to `preman`.
120
134
  - `--project`: Write `.cursor/mcp.json` in the current project.
135
+ - `--skip-login`: Install config without interactive terminal auth.
121
136
  - `--print`: Print the generated MCP config without writing it.
122
137
 
123
138
  ## Environment Variables
@@ -126,6 +141,8 @@ Options:
126
141
  - `PREMAN_BACKEND`: PreMan backend URL.
127
142
  - `PREMAN_FRONTEND`: PreMan frontend URL.
128
143
 
144
+ If `PREMAN_API_KEY` is omitted, the MCP server loads credentials from `~/.preman/credentials.json`.
145
+
129
146
  ## Product
130
147
 
131
148
  PreMan helps teams make APIs usable by agents without giving up control. Developers can turn endpoints into MCP servers, hand customers an install snippet or hosted MCP URL, and audit every tool call through PreMan.
package/bin/cli.js CHANGED
@@ -4,22 +4,25 @@
4
4
  * PreMan CLI.
5
5
  *
6
6
  * Usage:
7
- * npx premanmcp install
8
- * npx premanmcp install --api-key pm_live_xxx
9
- * npx premanmcp
7
+ * npm exec -y premanmcp@latest -- login
8
+ * npm exec -y premanmcp@latest -- install
9
+ * npm exec -y premanmcp@latest -- install --api-key pm_live_xxx
10
+ * npm exec -y premanmcp@latest --
10
11
  */
11
12
 
12
13
  import { spawn } from "node:child_process";
13
14
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
14
15
  import os from "node:os";
15
16
  import path from "node:path";
17
+ import { createInterface } from "node:readline/promises";
16
18
  import { fileURLToPath } from "node:url";
17
19
 
18
20
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
21
  const ROOT = path.join(__dirname, "..");
20
22
  const DEFAULT_BACKEND = "https://api.preman.live";
21
23
  const DEFAULT_FRONTEND = "https://app.preman.live";
22
- const API_KEY_PLACEHOLDER = "pm_live_YOUR_API_KEY_HERE";
24
+ const CREDENTIALS_DIR = path.join(os.homedir(), ".preman");
25
+ const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
23
26
 
24
27
  const args = process.argv.slice(2);
25
28
  const command = args[0] === "--help" || args[0] === "-h"
@@ -43,22 +46,29 @@ function printHelp() {
43
46
  process.stdout.write(`PreMan MCP
44
47
 
45
48
  Usage:
46
- npx premanmcp install [options] Install PreMan into Cursor MCP config
47
- npx premanmcp Start the PreMan MCP server
49
+ npm exec -y premanmcp@latest -- login Create/login to PreMan from the terminal
50
+ npm exec -y premanmcp@latest -- install [options] Install PreMan into Cursor MCP config
51
+ npm exec -y premanmcp@latest -- Start the PreMan MCP server
52
+
53
+ Login options:
54
+ --email <email> Pre-fill the email prompt
55
+ --backend <url> PreMan backend URL. Defaults to ${DEFAULT_BACKEND}
48
56
 
49
57
  Install options:
50
- --api-key <key> PreMan API key. Defaults to ${API_KEY_PLACEHOLDER}
58
+ --api-key <key> PreMan API key. If omitted, stored CLI credentials are used
51
59
  --backend <url> PreMan backend URL. Defaults to ${DEFAULT_BACKEND}
52
60
  --frontend <url> PreMan frontend URL. Defaults to ${DEFAULT_FRONTEND}
53
61
  --name <name> MCP server name. Defaults to preman
54
62
  --project Write .cursor/mcp.json in the current project
55
63
  --global Write ~/.cursor/mcp.json (default)
64
+ --skip-login Install config without interactive terminal auth
56
65
  --print Print the config instead of writing it
57
66
 
58
67
  Examples:
59
- npx premanmcp install
60
- npx premanmcp install --api-key pm_live_xxx
61
- npx premanmcp install --project --backend http://127.0.0.1:8000
68
+ npm exec -y premanmcp@latest -- login
69
+ npm exec -y premanmcp@latest -- install
70
+ npm exec -y premanmcp@latest -- install --api-key pm_live_xxx
71
+ npm exec -y premanmcp@latest -- install --project --backend http://127.0.0.1:8000
62
72
  `);
63
73
  }
64
74
 
@@ -73,25 +83,280 @@ function readJsonFile(filePath) {
73
83
  }
74
84
  }
75
85
 
86
+ function readStoredCredentials() {
87
+ try {
88
+ const raw = readFileSync(CREDENTIALS_FILE, "utf8").trim();
89
+ if (!raw) return null;
90
+ const creds = JSON.parse(raw);
91
+ if (creds && typeof creds.api_key === "string" && creds.api_key.startsWith("pm_live_")) {
92
+ return creds;
93
+ }
94
+ } catch {
95
+ // No stored credentials yet.
96
+ }
97
+ return null;
98
+ }
99
+
100
+ function saveStoredCredentials(creds) {
101
+ mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
102
+ writeFileSync(CREDENTIALS_FILE, `${JSON.stringify(creds, null, 2)}\n`, { mode: 0o600 });
103
+ }
104
+
105
+ function backendUrl() {
106
+ return argValue("--backend", process.env.PREMAN_BACKEND || DEFAULT_BACKEND).replace(/\/+$/, "");
107
+ }
108
+
109
+ async function promptText(question) {
110
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
111
+ try {
112
+ return (await rl.question(question)).trim();
113
+ } finally {
114
+ rl.close();
115
+ }
116
+ }
117
+
118
+ async function promptSecret(question) {
119
+ if (!process.stdin.isTTY || !process.stdin.setRawMode) {
120
+ return promptText(question);
121
+ }
122
+
123
+ return new Promise((resolve) => {
124
+ const stdin = process.stdin;
125
+ const stdout = process.stdout;
126
+ const wasRaw = stdin.isRaw;
127
+ let value = "";
128
+
129
+ function cleanup() {
130
+ stdin.off("data", onData);
131
+ stdin.setRawMode(Boolean(wasRaw));
132
+ stdin.pause();
133
+ }
134
+
135
+ function onData(chunk) {
136
+ const text = String(chunk);
137
+ if (text === "\u0003") {
138
+ stdout.write("\n");
139
+ cleanup();
140
+ process.exit(130);
141
+ }
142
+ if (text === "\r" || text === "\n" || text === "\u0004") {
143
+ stdout.write("\n");
144
+ cleanup();
145
+ resolve(value);
146
+ return;
147
+ }
148
+ if (text === "\u007f" || text === "\b") {
149
+ if (value.length) {
150
+ value = value.slice(0, -1);
151
+ stdout.write("\b \b");
152
+ }
153
+ return;
154
+ }
155
+ value += text;
156
+ stdout.write("*");
157
+ }
158
+
159
+ stdout.write(question);
160
+ stdin.setRawMode(true);
161
+ stdin.resume();
162
+ stdin.setEncoding("utf8");
163
+ stdin.on("data", onData);
164
+ });
165
+ }
166
+
167
+ async function promptPasswordTwice() {
168
+ const password = await promptSecret("Create password: ");
169
+ if (!password || password.length < 6) {
170
+ throw new Error("Password must be at least 6 characters.");
171
+ }
172
+ const confirm = await promptSecret("Confirm password: ");
173
+ if (password !== confirm) {
174
+ throw new Error("Passwords do not match.");
175
+ }
176
+ return password;
177
+ }
178
+
179
+ async function callBackendJson(method, routePath, { json, token, query } = {}) {
180
+ const url = new URL(routePath.replace(/^\/+/, ""), `${backendUrl()}/`);
181
+ if (query) {
182
+ for (const [key, value] of Object.entries(query)) {
183
+ if (value != null && value !== "") url.searchParams.set(key, String(value));
184
+ }
185
+ }
186
+
187
+ const headers = { Accept: "application/json" };
188
+ const hasBody = json !== undefined && json !== null;
189
+ if (hasBody) headers["Content-Type"] = "application/json";
190
+ if (token) headers.Authorization = `Bearer ${token}`;
191
+
192
+ const resp = await fetch(url, {
193
+ method,
194
+ headers,
195
+ body: hasBody ? JSON.stringify(json) : undefined,
196
+ });
197
+ const text = await resp.text();
198
+ let body = {};
199
+ try {
200
+ body = text ? JSON.parse(text) : {};
201
+ } catch {
202
+ body = { raw: text };
203
+ }
204
+ return {
205
+ status_code: resp.status,
206
+ ok: resp.ok,
207
+ ...body,
208
+ };
209
+ }
210
+
211
+ function assertOk(result, action) {
212
+ if (result.ok) return;
213
+ const detail = result.detail || result.message || result.raw || `${action} failed`;
214
+ throw new Error(`${action} failed: ${result.status_code} ${detail}`);
215
+ }
216
+
217
+ async function verifyUnconfirmedAccount(email) {
218
+ process.stdout.write("This email exists but is not verified. Sending a new OTP.\n");
219
+ const resend = await callBackendJson("POST", "/auth/resend-otp", { json: { email } });
220
+ assertOk(resend, "resend OTP");
221
+ const otp = await promptText("Verification code: ");
222
+ const verified = await callBackendJson("POST", "/auth/verify-otp", { json: { email, otp } });
223
+ assertOk(verified, "verify OTP");
224
+ return verified.access_token;
225
+ }
226
+
227
+ async function authenticateTerminal() {
228
+ const explicitKey = argValue("--api-key", process.env.PREMAN_API_KEY || "");
229
+ if (explicitKey && explicitKey.startsWith("pm_live_")) {
230
+ const creds = {
231
+ api_key: explicitKey,
232
+ backend_url: backendUrl(),
233
+ user_email: argValue("--email", ""),
234
+ device_name: os.hostname(),
235
+ created_at: new Date().toISOString(),
236
+ };
237
+ saveStoredCredentials(creds);
238
+ return creds;
239
+ }
240
+
241
+ const email = (argValue("--email", "") || await promptText("Email: ")).trim().toLowerCase();
242
+ if (!email) throw new Error("Email is required.");
243
+
244
+ process.stdout.write(`Checking PreMan account for ${email}...\n`);
245
+ const account = await callBackendJson("GET", "/auth/needs-password", { query: { email } });
246
+ assertOk(account, "check account");
247
+
248
+ let accessToken = "";
249
+
250
+ if (!account.exists) {
251
+ process.stdout.write("No PreMan account found. Creating one now.\n");
252
+ const signup = await callBackendJson("POST", "/auth/start-signup", {
253
+ json: { email },
254
+ });
255
+ assertOk(signup, "start signup");
256
+ process.stdout.write("Verification code sent. Check your email.\n");
257
+ const otp = await promptText("Verification code: ");
258
+ const password = await promptPasswordTwice();
259
+ const setPassword = await callBackendJson("POST", "/auth/set-password", {
260
+ json: { email, otp, new_password: password },
261
+ });
262
+ assertOk(setPassword, "set password");
263
+ accessToken = String(setPassword.access_token || "");
264
+ } else if (account.needs_password) {
265
+ process.stdout.write("This account needs a password. Sending an OTP first.\n");
266
+ const resend = await callBackendJson("POST", "/auth/resend-otp", { json: { email } });
267
+ assertOk(resend, "resend OTP");
268
+ const otp = await promptText("Verification code: ");
269
+ const password = await promptPasswordTwice();
270
+ const setPassword = await callBackendJson("POST", "/auth/set-password", {
271
+ json: { email, otp, new_password: password },
272
+ });
273
+ assertOk(setPassword, "set password");
274
+ accessToken = String(setPassword.access_token || "");
275
+ } else {
276
+ const password = await promptSecret("Password: ");
277
+ const login = await callBackendJson("POST", "/auth/login", {
278
+ json: { email, password },
279
+ });
280
+ if (!login.ok && login.status_code === 403 && String(login.detail || "").toLowerCase().includes("not verified")) {
281
+ accessToken = await verifyUnconfirmedAccount(email);
282
+ } else {
283
+ assertOk(login, "login");
284
+ accessToken = String(login.access_token || "");
285
+ }
286
+ }
287
+
288
+ if (!accessToken) throw new Error("PreMan did not return an access token.");
289
+
290
+ const keyName = `PreMan MCP CLI (${os.hostname()})`;
291
+ const key = await callBackendJson("POST", "/api-keys", {
292
+ token: accessToken,
293
+ json: { name: keyName },
294
+ });
295
+ assertOk(key, "create API key");
296
+ if (!key.key || !String(key.key).startsWith("pm_live_")) {
297
+ throw new Error("PreMan did not return a valid API key.");
298
+ }
299
+
300
+ const creds = {
301
+ api_key: String(key.key),
302
+ backend_url: backendUrl(),
303
+ user_email: email,
304
+ device_name: os.hostname(),
305
+ created_at: new Date().toISOString(),
306
+ };
307
+ saveStoredCredentials(creds);
308
+ return creds;
309
+ }
310
+
76
311
  function buildServerConfig() {
77
- const apiKey = argValue("--api-key", process.env.PREMAN_API_KEY || API_KEY_PLACEHOLDER);
312
+ const apiKey = argValue("--api-key", process.env.PREMAN_API_KEY || "");
78
313
  const backend = argValue("--backend", process.env.PREMAN_BACKEND || DEFAULT_BACKEND);
79
314
  const frontend = argValue("--frontend", process.env.PREMAN_FRONTEND || DEFAULT_FRONTEND);
315
+ const env = {
316
+ PREMAN_BACKEND: backend,
317
+ PREMAN_FRONTEND: frontend,
318
+ };
319
+ if (apiKey) {
320
+ env.PREMAN_API_KEY = apiKey;
321
+ }
80
322
 
81
323
  return {
82
- command: "npx",
83
- args: ["-y", "premanmcp"],
84
- env: {
85
- PREMAN_API_KEY: apiKey,
86
- PREMAN_BACKEND: backend,
87
- PREMAN_FRONTEND: frontend,
88
- },
324
+ command: "npm",
325
+ args: ["exec", "-y", "--package", "premanmcp@latest", "--", "premanmcp"],
326
+ env,
89
327
  };
90
328
  }
91
329
 
92
- function installCursorMcp() {
330
+ async function loginCommand() {
331
+ const creds = await authenticateTerminal();
332
+ process.stdout.write(`PreMan account ready.
333
+
334
+ Email: ${creds.user_email || "unknown"}
335
+ Backend: ${creds.backend_url}
336
+ API key: ${creds.api_key}
337
+ Saved to: ${CREDENTIALS_FILE}
338
+
339
+ You can now run:
340
+ npm exec -y premanmcp@latest -- install
341
+ `);
342
+ }
343
+
344
+ async function installCursorMcp() {
93
345
  const serverName = argValue("--name", "preman");
94
346
  const projectInstall = hasFlag("--project");
347
+
348
+ if (
349
+ !hasFlag("--print") &&
350
+ !hasFlag("--skip-login") &&
351
+ !argValue("--api-key", "") &&
352
+ !process.env.PREMAN_API_KEY &&
353
+ !readStoredCredentials()
354
+ ) {
355
+ process.stdout.write("First, let's create or connect your PreMan account.\n");
356
+ await authenticateTerminal();
357
+ process.stdout.write("\n");
358
+ }
359
+
95
360
  const serverConfig = buildServerConfig();
96
361
 
97
362
  if (hasFlag("--print")) {
@@ -115,16 +380,17 @@ function installCursorMcp() {
115
380
  mkdirSync(path.dirname(configPath), { recursive: true });
116
381
  writeFileSync(configPath, rendered, { mode: 0o600 });
117
382
 
118
- const usedPlaceholder = config.mcpServers[serverName].env.PREMAN_API_KEY === API_KEY_PLACEHOLDER;
383
+ const hasInlineKey = Boolean(config.mcpServers[serverName].env.PREMAN_API_KEY);
384
+ const hasStoredKey = Boolean(readStoredCredentials());
119
385
  process.stdout.write(`PreMan MCP installed in ${configPath}
120
386
 
121
387
  Server name: ${serverName}
122
388
  Backend: ${config.mcpServers[serverName].env.PREMAN_BACKEND}
123
389
 
124
390
  Next steps:
125
- 1. ${usedPlaceholder ? `Replace ${API_KEY_PLACEHOLDER} with your PreMan API key in ${configPath}. Find or create one at https://app.preman.live/settings/.` : "Your PreMan API key was written to the MCP config."}
391
+ 1. ${hasInlineKey ? "Your PreMan API key was written to the MCP config." : hasStoredKey ? `Your PreMan API key is saved in ${CREDENTIALS_FILE}; the MCP server will load it automatically.` : "Run npm exec -y premanmcp@latest -- login to create/connect your account and generate an API key."}
126
392
  2. Restart Cursor or toggle the PreMan MCP server off/on.
127
- 3. Ask your agent: "Use the PreMan MCP to convert my login endpoint into an MCP tool." Then open https://app.preman.live/ to see the session and generated MCP.
393
+ 3. Ask your agent: "Use the PreMan MCP to scan this codebase, deploy a hosted MCP, and give me the install command."
128
394
  `);
129
395
  }
130
396
 
@@ -155,14 +421,24 @@ function startServer() {
155
421
  });
156
422
  }
157
423
 
158
- if (command === "install") {
159
- installCursorMcp();
160
- } else if (command === "help" || command === "--help" || command === "-h") {
161
- printHelp();
162
- } else if (command === "start") {
163
- startServer();
164
- } else {
165
- process.stderr.write(`Unknown command: ${command}\n\n`);
166
- printHelp();
167
- process.exit(1);
424
+ async function main() {
425
+ if (command === "login") {
426
+ await loginCommand();
427
+ } else if (command === "install") {
428
+ await installCursorMcp();
429
+ } else if (command === "help" || command === "--help" || command === "-h") {
430
+ printHelp();
431
+ } else if (command === "start") {
432
+ startServer();
433
+ } else {
434
+ process.stderr.write(`Unknown command: ${command}\n\n`);
435
+ printHelp();
436
+ process.exit(1);
437
+ }
168
438
  }
439
+
440
+ main().catch((error) => {
441
+ const message = error instanceof Error ? error.message : String(error);
442
+ process.stderr.write(`[preman] ${message}\n`);
443
+ process.exit(1);
444
+ });
@@ -18,6 +18,8 @@ export type ShareAuthFlowResult = {
18
18
  session_id: string;
19
19
  url: string;
20
20
  endpoint_count: number;
21
+ user_id: number | null;
22
+ auto_discoverable: boolean;
21
23
  upstream_base_url: string;
22
24
  endpoints: AuthFlowEndpoint[];
23
25
  ui: {
@@ -32,4 +34,5 @@ export declare function shareAuthFlowToUi(opts: {
32
34
  upstreamBaseUrl?: string;
33
35
  sessionId?: string;
34
36
  intent?: string;
37
+ apiKey?: string;
35
38
  }): Promise<ShareAuthFlowResult>;
@@ -110,6 +110,10 @@ export function buildAuthFlowEndpoints() {
110
110
  export async function shareAuthFlowToUi(opts) {
111
111
  const backend = opts.backendUrl.replace(/\/+$/, "");
112
112
  const frontend = opts.frontendUrl.replace(/\/+$/, "");
113
+ const apiKey = opts.apiKey?.trim();
114
+ if (!apiKey) {
115
+ throw new Error("PreMan authentication required. Run preman_login first so auth-flow sessions can stream to your dashboard.");
116
+ }
113
117
  const upstream = (opts.upstreamBaseUrl || backend).replace(/\/+$/, "") || backend;
114
118
  const sessionId = opts.sessionId?.trim() || crypto.randomUUID();
115
119
  const endpoints = buildAuthFlowEndpoints().map((ep) => ({
@@ -118,11 +122,16 @@ export async function shareAuthFlowToUi(opts) {
118
122
  }));
119
123
  const resp = await fetch(`${backend}/agent-sessions/${encodeURIComponent(sessionId)}/endpoints`, {
120
124
  method: "POST",
121
- headers: { "Content-Type": "application/json", Accept: "application/json" },
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ Accept: "application/json",
128
+ Authorization: `Bearer ${apiKey}`,
129
+ },
122
130
  body: JSON.stringify({
123
131
  endpoints,
124
132
  upstream_base_url: upstream,
125
133
  intent: opts.intent || "Auth flow: signup, verify OTP, login, resend OTP",
134
+ client_label: "premanmcp",
126
135
  }),
127
136
  });
128
137
  const text = await resp.text();
@@ -142,11 +151,13 @@ export async function shareAuthFlowToUi(opts) {
142
151
  session_id: sid,
143
152
  url,
144
153
  endpoint_count: Number(body.endpoint_count ?? endpoints.length),
154
+ user_id: typeof body.user_id === "number" ? body.user_id : null,
155
+ auto_discoverable: Boolean(body.auto_discoverable),
145
156
  upstream_base_url: upstream,
146
157
  endpoints: buildAuthFlowEndpoints(),
147
158
  ui: {
148
159
  url,
149
- note: "Open in Cursor Agent Browser. Test signup → verify-otp → login, or resend-otp. " +
160
+ note: "Open in Cursor Agent Browser or the Playground session list. Test signup → verify-otp → login, or resend-otp. " +
150
161
  "Schemas are prefilled from routes/auth Pydantic models.",
151
162
  },
152
163
  related_tools: [
package/dist/server.js CHANGED
@@ -11,7 +11,7 @@ import { randomUUID } from "node:crypto";
11
11
  import { MCP_PREVIEW_RESOURCE_URI, RESOURCE_URI_META_KEY, buildConversionPanelHtml, writeMcpPreviewFile, } from "./mcp-preview-panel.js";
12
12
  /**
13
13
  * Load ``PREMAN_*`` from repo JSON so project URLs win over stale **global** Cursor MCP env
14
- * (which often sets the wrong ``PREMAN_BACKEND``, e.g. Flow port instead of API port).
14
+ * (which often sets the wrong ``PREMAN_BACKEND``, e.g. app port instead of API port).
15
15
  * First match wins: ``.cursor/preman-mcp.config.json``, then ``preman-mcp.config.json`` (cwd).
16
16
  */
17
17
  function applyRepoPremanConfig() {
@@ -107,7 +107,7 @@ function normalizeFrontendBaseUrl(url) {
107
107
  /** Origin only (no path). */
108
108
  const FRONTEND_BASE = normalizeFrontendBaseUrl(FRONTEND_URL);
109
109
  /**
110
- * Static hosts (S3/CloudFront) often 404 on `/endpoints` because only `/` maps to index.html.
110
+ * Static hosts often 404 on `/endpoints` because only `/` maps to index.html.
111
111
  * Cold-load the SPA shell at `/` and pass the client route in the query so the first request always hits index.html.
112
112
  */
113
113
  const AGENT_ROUTE_PARAM = "ot_agent_route";
@@ -174,7 +174,7 @@ async function initAuth() {
174
174
  }
175
175
  const creds = await loadStoredCredentials();
176
176
  if (!creds) {
177
- console.error("[PreMan] No stored credentials. Use preman_login to authenticate.");
177
+ console.error("[PreMan] No stored credentials. Run `npm exec -y premanmcp@latest -- login` or use preman_login to authenticate.");
178
178
  return;
179
179
  }
180
180
  const check = await verifyApiKey(creds.api_key);
@@ -187,7 +187,7 @@ async function initAuth() {
187
187
  console.error("[PreMan] Backend unreachable — using stored credentials (will verify on first call)");
188
188
  }
189
189
  else {
190
- console.error("[PreMan] Stored credentials are invalid or expired. Use preman_login to re-authenticate.");
190
+ console.error("[PreMan] Stored credentials are invalid or expired. Run `npm exec -y premanmcp@latest -- login` or use preman_login to re-authenticate.");
191
191
  await clearCredentials();
192
192
  }
193
193
  }
@@ -195,7 +195,7 @@ async function initAuth() {
195
195
  // ── Backend proxy ─────────────────────────────────────────────────────
196
196
  function requireAuth() {
197
197
  if (!API_KEY) {
198
- throw new Error("Not authenticated. Run the preman_login tool first to connect your PreMan account.");
198
+ throw new Error("Not authenticated. Run `npm exec -y premanmcp@latest -- login`, or use user_auth_* then preman_create_api_key, or run preman_login.");
199
199
  }
200
200
  }
201
201
  async function callBackend(toolName, args) {
@@ -273,7 +273,7 @@ function enrichEndpointsBrowserUrl(payload) {
273
273
  ui: {
274
274
  ...prevUi,
275
275
  url: buildAgentDashboardUrl("/endpoints"),
276
- note: "Use Agent Browser: browser_navigate to `ui.url` to open the Flow Endpoints page (sign in if prompted). Prefer this over the site homepage.",
276
+ note: "Use Agent Browser: browser_navigate to `ui.url` to open the PreMan Endpoints page (sign in if prompted). Prefer this over the site homepage.",
277
277
  },
278
278
  };
279
279
  }
@@ -290,7 +290,7 @@ function withFrontendUrl(payload, path) {
290
290
  ui: {
291
291
  ...prevUi,
292
292
  url: buildAgentDashboardUrl(p),
293
- note: "Open this URL in Agent Browser (browser_navigate) to see Flow Endpoints page first (sign in if prompted). Prefer this link over the site homepage.",
293
+ note: "Open this URL in Agent Browser (browser_navigate) to see the PreMan Endpoints page first (sign in if prompted). Prefer this link over the site homepage.",
294
294
  },
295
295
  }),
296
296
  }],
@@ -306,9 +306,9 @@ export function createServer() {
306
306
  "- **Read tools** (no side effects): get_endpoints, get_coverage, detect_drift, preman_status, list_collections, get_collection, list_runs",
307
307
  "- **Action tools** (execute tests / mutate state): test_api, generate_tests, import_collection, test_endpoint_by_id, run_tests, delete_collection",
308
308
  "- **MCP conversion tools**: discover_endpoints_from_codebase, verify_endpoints_live, mcp_preview (returns inline two-pane panel), mcp_deploy, mcp_list_deployed, mcp_mint_consumer_token, mcp_revoke_consumer_token",
309
- "- **Auth (API key / PreMan)**: preman_login, preman_login_complete, preman_logout",
310
- "- **Auth (app JWT — email/OTP/password on your API)**: user_auth_signup, user_auth_verify_otp, user_auth_login, user_auth_needs_password, user_auth_resend_otp, user_auth_forgot_password, user_auth_set_password, user_auth_me, user_auth_change_password, user_auth_delete_account (HTTP to PREMAN_BACKEND /auth/*; no pm_live_ key required)",
311
- "- **Auth Flow UI**: share_user_auth_flow_with_ui pushes signup, verify-otp, login, resend-otp with JSON schemas to the Playground (no API key). Open ui.url in Agent Browser.",
309
+ "- **Auth (API key / PreMan)**: preman_create_api_key (JWT -> saved pm_live_ key), preman_login, preman_login_complete, preman_logout",
310
+ "- **Auth (app JWT — email/OTP/password on your API)**: user_auth_start_signup, user_auth_signup, user_auth_verify_otp, user_auth_login, user_auth_needs_password, user_auth_resend_otp, user_auth_forgot_password, user_auth_set_password, user_auth_me, user_auth_change_password, user_auth_delete_account (HTTP to PREMAN_BACKEND /auth/*; no pm_live_ key required)",
311
+ "- **Auth to PreMan Playground**: share_user_auth_flow_with_ui pushes signup, verify-otp, login, resend-otp with JSON schemas to the signed-in Playground. Requires preman_login / PREMAN_API_KEY so the session appears in the user's dashboard.",
312
312
  "",
313
313
  "## Routing — pick the right tool for what the user said",
314
314
  "- 'scan / discover / find / list my endpoints' (codebase): use discover_endpoints_from_codebase. NEVER use get_endpoints for this — that returns inventory already saved in PreMan, not source code.",
@@ -323,7 +323,7 @@ export function createServer() {
323
323
  '- Always use format="json" (the default) for structured data. Only use format="text" if the user explicitly asks for human-readable output.',
324
324
  "- Do NOT set open_ui=true unless the user asks to see the dashboard.",
325
325
  "- endpoints_dashboard is DEPRECATED. Use get_endpoints(include_sessions=true, include_collections=true) instead.",
326
- "- When opening the Flow web app in a browser, use **ui.url** from tool results (SPA entry `/?agent_session=…&ot_agent_route=/endpoints`, not a bare `/endpoints` path on static hosting). Do **not** start at the site homepage without `ot_agent_route` unless the user explicitly asked for it.",
326
+ "- When opening the PreMan app in a browser, use **ui.url** from tool results (SPA entry `/?agent_session=...&ot_agent_route=/endpoints`, not a bare `/endpoints` path on static hosting). Do **not** start at the site homepage without `ot_agent_route` unless the user explicitly asked for it.",
327
327
  "",
328
328
  "## Common workflows",
329
329
  "1. **Discover then test**: get_endpoints -> pick untested/failing -> test_api for each.",
@@ -332,7 +332,7 @@ export function createServer() {
332
332
  "4. **Drift detection**: detect_drift with a collection + base_url -> test_api on drifted endpoints.",
333
333
  "",
334
334
  "## Auth",
335
- "PreMan API tools (get_endpoints, test_api, …) need an pm_live_ key: run preman_login, then preman_login_complete.",
335
+ "PreMan API tools (get_endpoints, test_api, …) need an pm_live_ key. Preferred no-browser flow: user_auth_start_signup -> user_auth_set_password -> preman_create_api_key, or user_auth_login -> preman_create_api_key. Browser fallback: preman_login -> preman_login_complete.",
336
336
  "To drive the backend's **email/JWT** auth (signup, OTP, login, /auth/me), use the user_auth_* tools — they call PREMAN_BACKEND /auth/* directly and do not use the API key.",
337
337
  ].join("\n"),
338
338
  });
@@ -668,6 +668,92 @@ export function createServer() {
668
668
  });
669
669
  }
670
670
  });
671
+ // ── preman_create_api_key ──────────────────────────────────────────
672
+ // Lets agents complete signup/login entirely in the IDE:
673
+ // user_auth_* returns a JWT, then this tool mints and stores the pm_live_ key.
674
+ server.tool("preman_create_api_key", "Mint and save a PreMan pm_live_ API key using a JWT from user_auth_login, user_auth_verify_otp, or user_auth_set_password. Use this to finish account setup without opening the website.", {
675
+ access_token: z.string().describe("JWT returned by user_auth_login, user_auth_verify_otp, or user_auth_set_password"),
676
+ name: z.string().optional().describe("API key name. Defaults to this MCP device name."),
677
+ }, async (args) => {
678
+ try {
679
+ const name = args.name || `${os.hostname()} MCP`;
680
+ const keyResp = await fetch(`${BACKEND_URL}/api-keys`, {
681
+ method: "POST",
682
+ headers: {
683
+ "Content-Type": "application/json",
684
+ Authorization: `Bearer ${args.access_token}`,
685
+ },
686
+ body: JSON.stringify({ name }),
687
+ });
688
+ const keyText = await keyResp.text();
689
+ let keyData;
690
+ try {
691
+ keyData = keyText ? JSON.parse(keyText) : {};
692
+ }
693
+ catch {
694
+ keyData = { raw: keyText };
695
+ }
696
+ if (!keyResp.ok) {
697
+ throw new Error(`Create API key failed: ${keyResp.status} ${String(keyData.detail ?? keyData.raw ?? keyText)}`);
698
+ }
699
+ const apiKey = String(keyData.key || "");
700
+ if (!apiKey.startsWith("pm_live_")) {
701
+ throw new Error("Create API key response did not include a valid pm_live_ key.");
702
+ }
703
+ API_KEY = apiKey;
704
+ let email;
705
+ try {
706
+ const meResp = await fetch(`${BACKEND_URL}/auth/me`, {
707
+ headers: { Authorization: `Bearer ${args.access_token}` },
708
+ });
709
+ if (meResp.ok) {
710
+ const me = await meResp.json();
711
+ email = typeof me.email === "string" ? me.email : undefined;
712
+ }
713
+ }
714
+ catch {
715
+ // Email is nice to have; the API key is the important credential.
716
+ }
717
+ await saveCredentials({
718
+ api_key: apiKey,
719
+ backend_url: BACKEND_URL,
720
+ user_email: email,
721
+ device_name: name,
722
+ created_at: new Date().toISOString(),
723
+ });
724
+ return {
725
+ content: [{
726
+ type: "text",
727
+ text: JSON.stringify({
728
+ status: "authenticated",
729
+ email,
730
+ api_key: apiKey,
731
+ key_prefix: keyData.key_prefix,
732
+ key_id: keyData.id,
733
+ saved_to: CREDENTIALS_FILE,
734
+ message: "PreMan account and MCP credentials are ready. You can now scan endpoints, preview, deploy, test, and list hosted MCPs from the agent.",
735
+ _agent_hints: {
736
+ next_actions: [
737
+ "Call discover_endpoints_from_codebase to scan this project.",
738
+ "Call verify_endpoints_live if you have a base URL to test against.",
739
+ "Call mcp_preview, then mcp_deploy to create a hosted MCP.",
740
+ ],
741
+ related_tools: ["discover_endpoints_from_codebase", "verify_endpoints_live", "mcp_preview", "mcp_deploy"],
742
+ },
743
+ }),
744
+ }],
745
+ };
746
+ }
747
+ catch (e) {
748
+ return toolError(e.message, inferErrorCode(e.message), {
749
+ next_actions: [
750
+ "Get a fresh JWT by calling user_auth_login, user_auth_verify_otp, or user_auth_set_password.",
751
+ "Or run `npm exec -y premanmcp@latest -- login` in the terminal.",
752
+ ],
753
+ related_tools: ["user_auth_login", "user_auth_verify_otp", "user_auth_set_password"],
754
+ });
755
+ }
756
+ });
671
757
  // ── preman_login ─────────────────────────────────────────────────
672
758
  server.tool("preman_login", "Authenticate with PreMan. Starts a one-time device authorization flow and returns a verification_url. Use Cursor Agent Browser (browser_navigate to verification_url) or open it manually to approve. Then run preman_login_complete with device_code.", {
673
759
  device_name: z.string().optional().describe("Friendly name for this device (e.g. 'My MacBook')"),
@@ -774,7 +860,7 @@ export function createServer() {
774
860
  endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
775
861
  _agent_hints: {
776
862
  next_actions: [
777
- `browser_navigate to the URL in endpoints_page_url to open Flow on the Endpoints page (sign in to the web app if needed).`,
863
+ `browser_navigate to the URL in endpoints_page_url to open PreMan on the Endpoints page (sign in to the web app if needed).`,
778
864
  "Call get_endpoints to see your registered API endpoints.",
779
865
  "Call test_api to test an endpoint.",
780
866
  "Call import_collection to import a Postman/OpenAPI spec.",
@@ -814,7 +900,7 @@ export function createServer() {
814
900
  backend_url: BACKEND_URL,
815
901
  frontend_base_url: FRONTEND_BASE,
816
902
  endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
817
- message: "Not authenticated. Run preman_login to connect your account.",
903
+ message: "Not authenticated. Run `npm exec -y premanmcp@latest -- login`, or use user_auth_* then preman_create_api_key, or run preman_login.",
818
904
  }),
819
905
  }],
820
906
  };
@@ -829,7 +915,7 @@ export function createServer() {
829
915
  backend_url: BACKEND_URL,
830
916
  frontend_base_url: FRONTEND_BASE,
831
917
  endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
832
- message: "Stored API key is no longer valid. Run preman_login to re-authenticate.",
918
+ message: "Stored API key is no longer valid. Run `npm exec -y premanmcp@latest -- login`, or use user_auth_* then preman_create_api_key, or run preman_login.",
833
919
  }),
834
920
  }],
835
921
  };
@@ -846,7 +932,7 @@ export function createServer() {
846
932
  endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
847
933
  _agent_hints: {
848
934
  next_actions: [
849
- "browser_navigate to `endpoints_page_url` (SPA shell + ot_agent_route) to open Flow on the Endpoints page (sign in if prompted), then call get_endpoints.",
935
+ "browser_navigate to `endpoints_page_url` (SPA shell + ot_agent_route) to open PreMan on the Endpoints page (sign in if prompted), then call get_endpoints.",
850
936
  "Call get_endpoints to see registered API endpoints.",
851
937
  "Call test_api to test an endpoint.",
852
938
  ],
@@ -876,7 +962,7 @@ export function createServer() {
876
962
  });
877
963
  // ── App user auth (JWT) — same server as preman-local / PREMAN_BACKEND
878
964
  registerUserAuthFlowTools(server, BACKEND_URL);
879
- server.tool("share_user_auth_flow_with_ui", "Stream signup, verify-otp, login, and resend-otp (send OTP) into the Flow Playground with request/response JSON schemas. No API key required. Use after discover or when the user asks to see auth endpoints in the UI. Pair with user_auth_* tools to execute flows.", {
965
+ server.tool("share_user_auth_flow_with_ui", "Stream signup, verify-otp, login, and resend-otp (send OTP) into the signed-in PreMan Playground with request/response JSON schemas. Requires preman_login or PREMAN_API_KEY so the session appears in the user's dashboard immediately. Pair with user_auth_* tools to execute flows.", {
880
966
  upstream_base_url: z
881
967
  .string()
882
968
  .optional()
@@ -888,6 +974,7 @@ export function createServer() {
888
974
  intent: z.string().optional().describe("Label shown in the Playground session list."),
889
975
  }, async (args) => {
890
976
  try {
977
+ requireAuth();
891
978
  const result = await shareAuthFlowToUi({
892
979
  backendUrl: BACKEND_URL,
893
980
  frontendUrl: FRONTEND_BASE,
@@ -896,6 +983,7 @@ export function createServer() {
896
983
  : undefined,
897
984
  sessionId: typeof args.session_id === "string" ? args.session_id : undefined,
898
985
  intent: typeof args.intent === "string" ? args.intent : undefined,
986
+ apiKey: API_KEY || undefined,
899
987
  });
900
988
  return {
901
989
  content: [{ type: "text", text: JSON.stringify(result) }],
@@ -948,7 +1036,7 @@ export function createServer() {
948
1036
  });
949
1037
  }
950
1038
  });
951
- server.tool("share_endpoints_with_ui", "Push discovered or verified endpoints into the PreMan Flow Playground so the user can see them, test them, and convert selected endpoints into hosted MCP tools. Use this after verify_endpoints_live or when the user explicitly asks to stream endpoints to the UI.", {
1039
+ server.tool("share_endpoints_with_ui", "Push discovered or verified endpoints into the PreMan Playground so the user can see them, test them, and convert selected endpoints into hosted MCP tools. Use this after verify_endpoints_live or when the user explicitly asks to stream endpoints to the UI.", {
952
1040
  endpoints: z.array(z.any()).describe("Endpoints to push. Each item should include method plus path/path_template/url; include schemas when available."),
953
1041
  upstream_base_url: z.string().optional().describe("Default upstream base URL for testing and MCP generation, e.g. http://127.0.0.1:8000 or https://api.example.com"),
954
1042
  intent: z.string().optional().describe("Short label for the session, e.g. 'Login endpoint' or 'Auth endpoints'"),
@@ -962,13 +1050,13 @@ export function createServer() {
962
1050
  return toolError(e.message, inferErrorCode(e.message), {
963
1051
  next_actions: [
964
1052
  "Make sure PREMAN_API_KEY is set or run preman_login, then retry.",
965
- "If the Flow UI opens but is empty, confirm PREMAN_BACKEND points to the same backend that received this push.",
1053
+ "If the PreMan Playground opens but is empty, confirm PREMAN_BACKEND points to the same backend that received this push.",
966
1054
  ],
967
1055
  related_tools: ["verify_endpoints_live", "mcp_preview", "preman_status"],
968
1056
  });
969
1057
  }
970
1058
  });
971
- server.tool("mcp_preview", "Pick endpoints matching an intent, generate a tool-schema preview, and automatically share the selected endpoints into the PreMan Flow Playground session so the user can see/test/deploy them in the UI. Also writes the same two-pane HTML to preman-mcp/mcp-preview-last.html and returns preview_file_url use Cursor Simple Browser or `node preman-mcp/scripts/open-cursor-preview.mjs` if the inline MCP app panel does not appear. LLM selection when OPENAI_API_KEY is set on the backend; else keyword match.", {
1059
+ server.tool("mcp_preview", "Pick endpoints matching an intent, generate a tool-schema preview, and automatically share the selected endpoints into the PreMan Playground session so the user can see/test/deploy them in the UI. Also writes the same two-pane HTML to preman-mcp/mcp-preview-last.html and returns preview_file_url; use Cursor Simple Browser or `node preman-mcp/scripts/open-cursor-preview.mjs` if the inline MCP app panel does not appear. Endpoint selection uses deterministic keyword matching.", {
972
1060
  intent: z.string().describe("Free text like 'auth endpoints' or 'everything related to orders'"),
973
1061
  endpoints: z.array(z.any()).describe("Candidate endpoints (typically the confirmed bucket from verify_endpoints_live)"),
974
1062
  upstream_base_url: z.string().optional().describe("Target business API base URL. Usually inferred from verify_endpoints_live results. Do not use PREMAN_BACKEND / api.preman.live unless the user's API is actually the PreMan backend."),
@@ -1014,7 +1102,7 @@ export function createServer() {
1014
1102
  : buildAgentDashboardUrl("/endpoints"),
1015
1103
  },
1016
1104
  _how_to_see_the_ui: [
1017
- "The selected endpoints were automatically shared into the Flow Playground session. Open ui.url or _agent_session.dashboard_url to see them.",
1105
+ "The selected endpoints were automatically shared into the PreMan Playground session. Open ui.url or _agent_session.dashboard_url to see them.",
1018
1106
  "Cursor may not show the embedded MCP app (no new tab is opened by default).",
1019
1107
  "Run `node preman-mcp/scripts/open-cursor-preview.mjs` from the repo root; it copies the http:// URL to the clipboard (macOS) and tries cursor:// + vscode:// Simple Browser handlers.",
1020
1108
  "If no tab appears: Cmd+Shift+P → “Simple Browser: Show” → paste the http://127.0.0.1:… URL from the script output. In the integrated terminal, Cmd+Click that URL may open the in-editor browser.",
@@ -58,6 +58,28 @@ async function callAuthJson(base, method, path, opts) {
58
58
  */
59
59
  export function registerUserAuthFlowTools(server, backendUrl) {
60
60
  const base = normalizeBase(backendUrl);
61
+ server.tool("user_auth_start_signup", "Start signup with email only. Sends an OTP; next call user_auth_set_password with email, OTP, and new password. Uses POST /auth/start-signup on PREMAN_BACKEND (no API key).", {
62
+ email: z.string().describe("User email"),
63
+ }, async (args) => {
64
+ try {
65
+ const r = await callAuthJson(base, "POST", "/auth/start-signup", {
66
+ json: { email: args.email },
67
+ });
68
+ if (!r.ok) {
69
+ return toolError(String(r.detail ?? r.message ?? "start signup failed"), r.status_code === 400 ? "invalid_input" : "backend_error", {
70
+ next_actions: ["If email exists, use user_auth_login or user_auth_resend_otp."],
71
+ related_tools: ["user_auth_set_password", "user_auth_login"],
72
+ });
73
+ }
74
+ return jsonOk(r);
75
+ }
76
+ catch (e) {
77
+ const m = e instanceof Error ? e.message : String(e);
78
+ return toolError(m, "backend_error", {
79
+ next_actions: ["Ensure PREMAN_BACKEND is running and JWT_SECRET is set on the server."],
80
+ });
81
+ }
82
+ });
61
83
  server.tool("user_auth_signup", "Register with email and password. Sends an OTP; next call user_auth_verify_otp. Uses POST /auth/signup on PREMAN_BACKEND (no API key).", {
62
84
  email: z.string().describe("User email"),
63
85
  password: z.string().describe("Password (min 6 characters on the server)"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "premanmcp",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Turn APIs into agent-callable MCP tools with auth, testing, and audit logs",
5
5
  "type": "module",
6
6
  "bin": {