premanmcp 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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() {
@@ -42,6 +42,62 @@ applyRepoPremanConfig();
42
42
  const BACKEND_URL = process.env.PREMAN_BACKEND || "https://api.preman.live";
43
43
  const FRONTEND_URL = process.env.PREMAN_FRONTEND || "https://app.preman.live";
44
44
  let API_KEY = process.env.PREMAN_API_KEY || "";
45
+ function detectCodingAgent() {
46
+ const forced = (process.env.PREMAN_CODING_AGENT || "").trim().toLowerCase();
47
+ if (forced)
48
+ return forced.replace("-", "_");
49
+ if (process.env.CLAUDECODE || process.env.CLAUDE_CODE)
50
+ return "claude_code";
51
+ if (process.env.CODEX_HOME || process.env.OPENAI_CODEX)
52
+ return "codex";
53
+ if (process.env.CURSOR_AGENT || process.env.CURSOR_TRACE_ID || process.env.CURSOR_SESSION_ID) {
54
+ return "cursor";
55
+ }
56
+ return "cursor";
57
+ }
58
+ /** Prove this MCP session is live to the workbench coding-agent link. */
59
+ async function heartbeatWorkbenchLink() {
60
+ if (!API_KEY)
61
+ return null;
62
+ try {
63
+ const resp = await fetch(`${BACKEND_URL}/workbench/coding-agent/heartbeat`, {
64
+ method: "POST",
65
+ headers: {
66
+ Authorization: `Bearer ${API_KEY}`,
67
+ "Content-Type": "application/json",
68
+ },
69
+ body: JSON.stringify({
70
+ pair_code: process.env.PREMAN_PAIR_CODE || undefined,
71
+ agent: detectCodingAgent(),
72
+ project_path: process.cwd(),
73
+ client_label: "premanmcp",
74
+ source: "preman_status",
75
+ }),
76
+ });
77
+ const text = await resp.text();
78
+ let data = {};
79
+ try {
80
+ data = text ? JSON.parse(text) : {};
81
+ }
82
+ catch {
83
+ data = { detail: text };
84
+ }
85
+ if (!resp.ok) {
86
+ return {
87
+ ok: false,
88
+ status: resp.status,
89
+ detail: data.detail || data.message || text,
90
+ };
91
+ }
92
+ return { ok: true, ...data };
93
+ }
94
+ catch (err) {
95
+ return {
96
+ ok: false,
97
+ detail: err instanceof Error ? err.message : String(err),
98
+ };
99
+ }
100
+ }
45
101
  const PREMAN_CONTROL_PLANE_HOSTS = new Set([
46
102
  "api.preman.live",
47
103
  "preman.live",
@@ -107,7 +163,7 @@ function normalizeFrontendBaseUrl(url) {
107
163
  /** Origin only (no path). */
108
164
  const FRONTEND_BASE = normalizeFrontendBaseUrl(FRONTEND_URL);
109
165
  /**
110
- * Static hosts (S3/CloudFront) often 404 on `/endpoints` because only `/` maps to index.html.
166
+ * Static hosts often 404 on `/endpoints` because only `/` maps to index.html.
111
167
  * Cold-load the SPA shell at `/` and pass the client route in the query so the first request always hits index.html.
112
168
  */
113
169
  const AGENT_ROUTE_PARAM = "ot_agent_route";
@@ -174,7 +230,7 @@ async function initAuth() {
174
230
  }
175
231
  const creds = await loadStoredCredentials();
176
232
  if (!creds) {
177
- console.error("[PreMan] No stored credentials. Use preman_login to authenticate.");
233
+ console.error("[PreMan] No stored credentials. Run `npm exec -y premanmcp@latest -- login` or use preman_login to authenticate.");
178
234
  return;
179
235
  }
180
236
  const check = await verifyApiKey(creds.api_key);
@@ -187,7 +243,7 @@ async function initAuth() {
187
243
  console.error("[PreMan] Backend unreachable — using stored credentials (will verify on first call)");
188
244
  }
189
245
  else {
190
- console.error("[PreMan] Stored credentials are invalid or expired. Use preman_login to re-authenticate.");
246
+ console.error("[PreMan] Stored credentials are invalid or expired. Run `npm exec -y premanmcp@latest -- login` or use preman_login to re-authenticate.");
191
247
  await clearCredentials();
192
248
  }
193
249
  }
@@ -195,7 +251,7 @@ async function initAuth() {
195
251
  // ── Backend proxy ─────────────────────────────────────────────────────
196
252
  function requireAuth() {
197
253
  if (!API_KEY) {
198
- throw new Error("Not authenticated. Run the preman_login tool first to connect your PreMan account.");
254
+ 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
255
  }
200
256
  }
201
257
  async function callBackend(toolName, args) {
@@ -273,7 +329,7 @@ function enrichEndpointsBrowserUrl(payload) {
273
329
  ui: {
274
330
  ...prevUi,
275
331
  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.",
332
+ 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
333
  },
278
334
  };
279
335
  }
@@ -290,7 +346,7 @@ function withFrontendUrl(payload, path) {
290
346
  ui: {
291
347
  ...prevUi,
292
348
  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.",
349
+ 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
350
  },
295
351
  }),
296
352
  }],
@@ -306,9 +362,9 @@ export function createServer() {
306
362
  "- **Read tools** (no side effects): get_endpoints, get_coverage, detect_drift, preman_status, list_collections, get_collection, list_runs",
307
363
  "- **Action tools** (execute tests / mutate state): test_api, generate_tests, import_collection, test_endpoint_by_id, run_tests, delete_collection",
308
364
  "- **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.",
365
+ "- **Auth (API key / PreMan)**: preman_create_api_key (JWT -> saved pm_live_ key), preman_login, preman_login_complete, preman_logout",
366
+ "- **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)",
367
+ "- **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
368
  "",
313
369
  "## Routing — pick the right tool for what the user said",
314
370
  "- '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 +379,7 @@ export function createServer() {
323
379
  '- Always use format="json" (the default) for structured data. Only use format="text" if the user explicitly asks for human-readable output.',
324
380
  "- Do NOT set open_ui=true unless the user asks to see the dashboard.",
325
381
  "- 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.",
382
+ "- 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
383
  "",
328
384
  "## Common workflows",
329
385
  "1. **Discover then test**: get_endpoints -> pick untested/failing -> test_api for each.",
@@ -332,7 +388,7 @@ export function createServer() {
332
388
  "4. **Drift detection**: detect_drift with a collection + base_url -> test_api on drifted endpoints.",
333
389
  "",
334
390
  "## Auth",
335
- "PreMan API tools (get_endpoints, test_api, …) need an pm_live_ key: run preman_login, then preman_login_complete.",
391
+ "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
392
  "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
393
  ].join("\n"),
338
394
  });
@@ -668,6 +724,92 @@ export function createServer() {
668
724
  });
669
725
  }
670
726
  });
727
+ // ── preman_create_api_key ──────────────────────────────────────────
728
+ // Lets agents complete signup/login entirely in the IDE:
729
+ // user_auth_* returns a JWT, then this tool mints and stores the pm_live_ key.
730
+ 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.", {
731
+ access_token: z.string().describe("JWT returned by user_auth_login, user_auth_verify_otp, or user_auth_set_password"),
732
+ name: z.string().optional().describe("API key name. Defaults to this MCP device name."),
733
+ }, async (args) => {
734
+ try {
735
+ const name = args.name || `${os.hostname()} MCP`;
736
+ const keyResp = await fetch(`${BACKEND_URL}/api-keys`, {
737
+ method: "POST",
738
+ headers: {
739
+ "Content-Type": "application/json",
740
+ Authorization: `Bearer ${args.access_token}`,
741
+ },
742
+ body: JSON.stringify({ name }),
743
+ });
744
+ const keyText = await keyResp.text();
745
+ let keyData;
746
+ try {
747
+ keyData = keyText ? JSON.parse(keyText) : {};
748
+ }
749
+ catch {
750
+ keyData = { raw: keyText };
751
+ }
752
+ if (!keyResp.ok) {
753
+ throw new Error(`Create API key failed: ${keyResp.status} ${String(keyData.detail ?? keyData.raw ?? keyText)}`);
754
+ }
755
+ const apiKey = String(keyData.key || "");
756
+ if (!apiKey.startsWith("pm_live_")) {
757
+ throw new Error("Create API key response did not include a valid pm_live_ key.");
758
+ }
759
+ API_KEY = apiKey;
760
+ let email;
761
+ try {
762
+ const meResp = await fetch(`${BACKEND_URL}/auth/me`, {
763
+ headers: { Authorization: `Bearer ${args.access_token}` },
764
+ });
765
+ if (meResp.ok) {
766
+ const me = await meResp.json();
767
+ email = typeof me.email === "string" ? me.email : undefined;
768
+ }
769
+ }
770
+ catch {
771
+ // Email is nice to have; the API key is the important credential.
772
+ }
773
+ await saveCredentials({
774
+ api_key: apiKey,
775
+ backend_url: BACKEND_URL,
776
+ user_email: email,
777
+ device_name: name,
778
+ created_at: new Date().toISOString(),
779
+ });
780
+ return {
781
+ content: [{
782
+ type: "text",
783
+ text: JSON.stringify({
784
+ status: "authenticated",
785
+ email,
786
+ api_key: apiKey,
787
+ key_prefix: keyData.key_prefix,
788
+ key_id: keyData.id,
789
+ saved_to: CREDENTIALS_FILE,
790
+ message: "PreMan account and MCP credentials are ready. You can now scan endpoints, preview, deploy, test, and list hosted MCPs from the agent.",
791
+ _agent_hints: {
792
+ next_actions: [
793
+ "Call discover_endpoints_from_codebase to scan this project.",
794
+ "Call verify_endpoints_live if you have a base URL to test against.",
795
+ "Call mcp_preview, then mcp_deploy to create a hosted MCP.",
796
+ ],
797
+ related_tools: ["discover_endpoints_from_codebase", "verify_endpoints_live", "mcp_preview", "mcp_deploy"],
798
+ },
799
+ }),
800
+ }],
801
+ };
802
+ }
803
+ catch (e) {
804
+ return toolError(e.message, inferErrorCode(e.message), {
805
+ next_actions: [
806
+ "Get a fresh JWT by calling user_auth_login, user_auth_verify_otp, or user_auth_set_password.",
807
+ "Or run `npm exec -y premanmcp@latest -- login` in the terminal.",
808
+ ],
809
+ related_tools: ["user_auth_login", "user_auth_verify_otp", "user_auth_set_password"],
810
+ });
811
+ }
812
+ });
671
813
  // ── preman_login ─────────────────────────────────────────────────
672
814
  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
815
  device_name: z.string().optional().describe("Friendly name for this device (e.g. 'My MacBook')"),
@@ -774,7 +916,7 @@ export function createServer() {
774
916
  endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
775
917
  _agent_hints: {
776
918
  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).`,
919
+ `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
920
  "Call get_endpoints to see your registered API endpoints.",
779
921
  "Call test_api to test an endpoint.",
780
922
  "Call import_collection to import a Postman/OpenAPI spec.",
@@ -814,7 +956,7 @@ export function createServer() {
814
956
  backend_url: BACKEND_URL,
815
957
  frontend_base_url: FRONTEND_BASE,
816
958
  endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
817
- message: "Not authenticated. Run preman_login to connect your account.",
959
+ message: "Not authenticated. Run `npm exec -y premanmcp@latest -- login`, or use user_auth_* then preman_create_api_key, or run preman_login.",
818
960
  }),
819
961
  }],
820
962
  };
@@ -829,11 +971,12 @@ export function createServer() {
829
971
  backend_url: BACKEND_URL,
830
972
  frontend_base_url: FRONTEND_BASE,
831
973
  endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
832
- message: "Stored API key is no longer valid. Run preman_login to re-authenticate.",
974
+ 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
975
  }),
834
976
  }],
835
977
  };
836
978
  }
979
+ const workbench = await heartbeatWorkbenchLink();
837
980
  return {
838
981
  content: [{
839
982
  type: "text",
@@ -844,13 +987,17 @@ export function createServer() {
844
987
  backend_url: BACKEND_URL,
845
988
  frontend_base_url: FRONTEND_BASE,
846
989
  endpoints_page_url: buildAgentDashboardUrl("/endpoints"),
990
+ coding_agent: workbench,
847
991
  _agent_hints: {
848
992
  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.",
993
+ workbench && workbench.connected
994
+ ? "Coding agent is linked to PreMan workbench — call discover_endpoints_from_codebase or get_endpoints."
995
+ : "If PreMan workbench shows pairing, set PREMAN_PAIR_CODE then call preman_status again.",
996
+ "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
997
  "Call get_endpoints to see registered API endpoints.",
851
998
  "Call test_api to test an endpoint.",
852
999
  ],
853
- related_tools: ["get_endpoints", "test_api", "import_collection"],
1000
+ related_tools: ["get_endpoints", "test_api", "import_collection", "discover_endpoints_from_codebase"],
854
1001
  },
855
1002
  }),
856
1003
  }],
@@ -876,7 +1023,7 @@ export function createServer() {
876
1023
  });
877
1024
  // ── App user auth (JWT) — same server as preman-local / PREMAN_BACKEND
878
1025
  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.", {
1026
+ 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
1027
  upstream_base_url: z
881
1028
  .string()
882
1029
  .optional()
@@ -888,6 +1035,7 @@ export function createServer() {
888
1035
  intent: z.string().optional().describe("Label shown in the Playground session list."),
889
1036
  }, async (args) => {
890
1037
  try {
1038
+ requireAuth();
891
1039
  const result = await shareAuthFlowToUi({
892
1040
  backendUrl: BACKEND_URL,
893
1041
  frontendUrl: FRONTEND_BASE,
@@ -896,6 +1044,7 @@ export function createServer() {
896
1044
  : undefined,
897
1045
  sessionId: typeof args.session_id === "string" ? args.session_id : undefined,
898
1046
  intent: typeof args.intent === "string" ? args.intent : undefined,
1047
+ apiKey: API_KEY || undefined,
899
1048
  });
900
1049
  return {
901
1050
  content: [{ type: "text", text: JSON.stringify(result) }],
@@ -948,7 +1097,7 @@ export function createServer() {
948
1097
  });
949
1098
  }
950
1099
  });
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.", {
1100
+ 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
1101
  endpoints: z.array(z.any()).describe("Endpoints to push. Each item should include method plus path/path_template/url; include schemas when available."),
953
1102
  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
1103
  intent: z.string().optional().describe("Short label for the session, e.g. 'Login endpoint' or 'Auth endpoints'"),
@@ -962,13 +1111,13 @@ export function createServer() {
962
1111
  return toolError(e.message, inferErrorCode(e.message), {
963
1112
  next_actions: [
964
1113
  "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.",
1114
+ "If the PreMan Playground opens but is empty, confirm PREMAN_BACKEND points to the same backend that received this push.",
966
1115
  ],
967
1116
  related_tools: ["verify_endpoints_live", "mcp_preview", "preman_status"],
968
1117
  });
969
1118
  }
970
1119
  });
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.", {
1120
+ 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
1121
  intent: z.string().describe("Free text like 'auth endpoints' or 'everything related to orders'"),
973
1122
  endpoints: z.array(z.any()).describe("Candidate endpoints (typically the confirmed bucket from verify_endpoints_live)"),
974
1123
  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 +1163,7 @@ export function createServer() {
1014
1163
  : buildAgentDashboardUrl("/endpoints"),
1015
1164
  },
1016
1165
  _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.",
1166
+ "The selected endpoints were automatically shared into the PreMan Playground session. Open ui.url or _agent_session.dashboard_url to see them.",
1018
1167
  "Cursor may not show the embedded MCP app (no new tab is opened by default).",
1019
1168
  "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
1169
  "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.",
@@ -1118,6 +1267,54 @@ export function createServer() {
1118
1267
  return toolError(e.message, inferErrorCode(e.message));
1119
1268
  }
1120
1269
  });
1270
+ // ── preman_get_fix_task ───────────────────────────────────────────
1271
+ server.tool("preman_get_fix_task", "Pull pending coding-agent fix tasks built from fired PreMan alerts. Each task packages a failing endpoint: title, expected vs actual, failure stats, and a reproducible curl (package.repro.curl). Fix the endpoint using the curl, then call preman_complete_fix_task with the fix_task_id. Check package.auto_pr: when eligible is true, push your fix on the branch it names and call preman_open_fix_pr.", {
1272
+ status: z.enum(["open", "delivered", "resolved"]).optional().default("open").describe("'open' (default) hands out new tasks and marks them delivered; 'delivered' re-fetches ones already pulled; 'resolved' for history"),
1273
+ limit: z.number().optional().default(5).describe("Max tasks to return (capped at 20)"),
1274
+ }, async (args) => {
1275
+ try {
1276
+ const result = await callBackend("preman_get_fix_task", args);
1277
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1278
+ }
1279
+ catch (e) {
1280
+ return toolError(e.message, inferErrorCode(e.message), {
1281
+ next_actions: ["No open fix tasks means no alerts have been handed off. Create a handoff from a fired alert in the dashboard."],
1282
+ });
1283
+ }
1284
+ });
1285
+ // ── preman_complete_fix_task ──────────────────────────────────────
1286
+ server.tool("preman_complete_fix_task", "Mark a fix task resolved once its endpoint failure is fixed.", {
1287
+ fix_task_id: z.string().describe("The id from a preman_get_fix_task result"),
1288
+ resolution_note: z.string().optional().default("").describe("Optional note on what was fixed"),
1289
+ }, async (args) => {
1290
+ try {
1291
+ const result = await callBackend("preman_complete_fix_task", args);
1292
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1293
+ }
1294
+ catch (e) {
1295
+ return toolError(e.message, inferErrorCode(e.message), {
1296
+ related_tools: ["preman_get_fix_task"],
1297
+ });
1298
+ }
1299
+ });
1300
+ // ── preman_open_fix_pr ────────────────────────────────────────────
1301
+ server.tool("preman_open_fix_pr", "Open a pull request for a fix branch you already pushed (Auto-PR, Tier 2). Only for fix tasks whose package.auto_pr.eligible is true. Patch and push the preman/fix-* branch with your own git credentials first — PreMan never pushes code. PreMan then verifies the branch exists, re-checks the endpoint in production, and opens a PR with that evidence. PreMan never merges: a human reviews and merges.", {
1302
+ fix_task_id: z.string().describe("The id from a preman_get_fix_task result"),
1303
+ branch: z.string().describe("The branch you pushed — must match package.auto_pr.branch"),
1304
+ summary: z.string().optional().default("").describe("Short description of the fix, included in the PR body"),
1305
+ local_rerun: z.string().optional().default("").describe("Your local test/re-run output, included as agent-reported evidence"),
1306
+ }, async (args) => {
1307
+ try {
1308
+ const result = await callBackend("preman_open_fix_pr", args);
1309
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1310
+ }
1311
+ catch (e) {
1312
+ return toolError(e.message, inferErrorCode(e.message), {
1313
+ related_tools: ["preman_get_fix_task", "preman_complete_fix_task"],
1314
+ next_actions: ["Auto-PR requires the repo to have opted in and the fix task to be an API_BUG failure mapped to that repo."],
1315
+ });
1316
+ }
1317
+ });
1121
1318
  return server;
1122
1319
  }
1123
1320
  // ── Start ─────────────────────────────────────────────────────────────
@@ -58,6 +58,34 @@ 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). If the backend does not support this endpoint yet, use user_auth_signup instead.", {
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
+ if (r.status_code === 404) {
70
+ return toolError("This backend does not support email-only signup yet. Use user_auth_signup with email and password, then user_auth_verify_otp.", "backend_error", {
71
+ next_actions: ["Call user_auth_signup with email and password.", "Then call user_auth_verify_otp with the email code."],
72
+ related_tools: ["user_auth_signup", "user_auth_verify_otp"],
73
+ });
74
+ }
75
+ return toolError(String(r.detail ?? r.message ?? "start signup failed"), r.status_code === 400 ? "invalid_input" : "backend_error", {
76
+ next_actions: ["If email exists, use user_auth_login or user_auth_resend_otp."],
77
+ related_tools: ["user_auth_set_password", "user_auth_login"],
78
+ });
79
+ }
80
+ return jsonOk(r);
81
+ }
82
+ catch (e) {
83
+ const m = e instanceof Error ? e.message : String(e);
84
+ return toolError(m, "backend_error", {
85
+ next_actions: ["Ensure PREMAN_BACKEND is running and JWT_SECRET is set on the server."],
86
+ });
87
+ }
88
+ });
61
89
  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
90
  email: z.string().describe("User email"),
63
91
  password: z.string().describe("Password (min 6 characters on the server)"),
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "premanmcp",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Turn APIs into agent-callable MCP tools with auth, testing, and audit logs",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "premanmcp": "bin/cli.js",
8
- "preman-mcp": "bin/cli.js"
8
+ "preman-mcp": "bin/cli.js",
9
+ "preman": "bin/cli.js"
9
10
  },
10
11
  "scripts": {
11
12
  "build": "tsc -p tsconfig.server.json",