@stackwright-pro/mcp 0.2.0-alpha.112 → 0.2.0-alpha.113

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
@@ -580,7 +580,7 @@ function registerDashboardTools(server2) {
580
580
  yamlLines.push(` header: Status`);
581
581
  yamlLines.push(` type: badge`);
582
582
  }
583
- const yaml3 = yamlLines.join("\n");
583
+ const yaml4 = yamlLines.join("\n");
584
584
  return {
585
585
  content: [
586
586
  {
@@ -590,7 +590,7 @@ function registerDashboardTools(server2) {
590
590
  Layout: ${layout}
591
591
 
592
592
  \`\`\`yaml
593
- ${yaml3}
593
+ ${yaml4}
594
594
  \`\`\`
595
595
 
596
596
  \u{1F4A1} Add this to pages/dashboard/content.yml and validate with stackwright_validate_pages.`
@@ -671,7 +671,7 @@ ${yaml3}
671
671
  yamlLines.push(" action: navigate");
672
672
  yamlLines.push(` href: "/${entity}"`);
673
673
  yamlLines.push(" style: secondary");
674
- const yaml3 = yamlLines.join("\n");
674
+ const yaml4 = yamlLines.join("\n");
675
675
  return {
676
676
  content: [
677
677
  {
@@ -679,7 +679,7 @@ ${yaml3}
679
679
  text: `\u{1F4C4} Generated Detail Page for: ${entity}
680
680
 
681
681
  \`\`\`yaml
682
- ${yaml3}
682
+ ${yaml4}
683
683
  \`\`\`
684
684
 
685
685
  \u{1F4A1} Add this to pages/${entity}/[${slugField}]/content.yml and validate.`
@@ -2834,21 +2834,40 @@ function readAllManifestPages(artifactsDir) {
2834
2834
  return allPages;
2835
2835
  }
2836
2836
  for (const entry of entries) {
2837
- if (!entry.endsWith("-manifest.json")) continue;
2838
- const filePath = (0, import_path6.join)(artifactsDir, entry);
2839
- try {
2840
- const raw = JSON.parse((0, import_fs6.readFileSync)(filePath, "utf-8"));
2841
- if (!Array.isArray(raw.pages)) continue;
2842
- for (const page of raw.pages) {
2843
- if (typeof page.slug !== "string" || !page.slug) continue;
2837
+ if (entry.endsWith("-manifest.json")) {
2838
+ const filePath = (0, import_path6.join)(artifactsDir, entry);
2839
+ try {
2840
+ const raw = JSON.parse((0, import_fs6.readFileSync)(filePath, "utf-8"));
2841
+ if (!Array.isArray(raw.pages)) continue;
2842
+ for (const page of raw.pages) {
2843
+ if (typeof page.slug !== "string" || !page.slug) continue;
2844
+ allPages.push({
2845
+ slug: page.slug,
2846
+ authRequired: page.authRequired,
2847
+ requiredRoles: page.requiredRoles,
2848
+ source: entry
2849
+ });
2850
+ }
2851
+ } catch {
2852
+ }
2853
+ continue;
2854
+ }
2855
+ if (entry === "workflow-config.json" || entry.startsWith("workflow-config-")) {
2856
+ const filePath = (0, import_path6.join)(artifactsDir, entry);
2857
+ try {
2858
+ const raw = JSON.parse((0, import_fs6.readFileSync)(filePath, "utf-8"));
2859
+ const route = raw.workflow?.route;
2860
+ if (typeof route !== "string" || !route) continue;
2861
+ const slug = route.startsWith("/") ? route.slice(1) : route;
2844
2862
  allPages.push({
2845
- slug: page.slug,
2846
- authRequired: page.authRequired,
2847
- requiredRoles: page.requiredRoles,
2863
+ slug,
2864
+ authRequired: true,
2865
+ requiredRoles: void 0,
2848
2866
  source: entry
2849
2867
  });
2868
+ } catch {
2850
2869
  }
2851
- } catch {
2870
+ continue;
2852
2871
  }
2853
2872
  }
2854
2873
  return allPages;
@@ -2856,7 +2875,7 @@ function readAllManifestPages(artifactsDir) {
2856
2875
  function registerReadAuthManifestsTool(server2) {
2857
2876
  server2.tool(
2858
2877
  "stackwright_pro_read_auth_manifests",
2859
- "Read ALL *-manifest.json files from the pipeline artifacts directory (pages-manifest.json, dashboard-manifest.json, geo-manifest.json, etc.) and deterministically derive protectedRoutes and uncoveredSlugs. Uses the both-forms invariant (swp-n7kk): every authRequired route registers BOTH /{slug} (exact) AND /{slug}/:path* (sub-path). Call this in Step 1 of auth configuration instead of manually reading each manifest file. Fixes the Bead C issue where dashboard-manifest.json and geo-manifest.json were silently missed.",
2878
+ "Read ALL route-producing artifacts from the pipeline artifacts directory: *-manifest.json files (pages, dashboard, geo, form-wizard) AND workflow-config.json / workflow-config-*.json (different shape: workflow.route). Deterministically derives protectedRoutes and uncoveredSlugs. Uses the both-forms invariant (swp-n7kk): every authRequired route registers BOTH /{slug} (exact) AND /{slug}/:path* (sub-path). Workflow routes appear in uncoveredSlugs (no requiredRoles declared) for auth-reconcile to resolve via cross-cutting rules from auth-config.json. Extended by swp-hng0 to cover all four manifest types (was: pages+dashboard only).",
2860
2879
  {
2861
2880
  rbacRoles: import_zod11.z.array(import_zod11.z.string()).describe(
2862
2881
  'RBAC role hierarchy in descending privilege order, e.g. ["ESF8_COORDINATOR", "HOSPITAL_EM", "OBSERVER"]. Used to compute the lowestRole for each authRequired page.'
@@ -2990,7 +3009,7 @@ function enrichErrors(issues, synonyms) {
2990
3009
  });
2991
3010
  }
2992
3011
  function handleValidateYamlFragment(input) {
2993
- const { schemaName, yaml: yaml3 } = input;
3012
+ const { schemaName, yaml: yaml4 } = input;
2994
3013
  if (!SUPPORTED_SCHEMA_NAMES.includes(schemaName)) {
2995
3014
  return {
2996
3015
  valid: false,
@@ -3000,7 +3019,7 @@ function handleValidateYamlFragment(input) {
3000
3019
  const name = schemaName;
3001
3020
  let parsed;
3002
3021
  try {
3003
- parsed = (0, import_js_yaml2.load)(yaml3);
3022
+ parsed = (0, import_js_yaml2.load)(yaml4);
3004
3023
  } catch (err) {
3005
3024
  return {
3006
3025
  valid: false,
@@ -3026,8 +3045,8 @@ function registerValidateYamlFragmentTool(server2) {
3026
3045
  ),
3027
3046
  yaml: import_zod12.z.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
3028
3047
  },
3029
- async ({ schemaName, yaml: yaml3 }) => {
3030
- const result = handleValidateYamlFragment({ schemaName, yaml: yaml3 });
3048
+ async ({ schemaName, yaml: yaml4 }) => {
3049
+ const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
3031
3050
  return {
3032
3051
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
3033
3052
  };
@@ -3240,6 +3259,9 @@ var MANIFEST_NAME_TO_PHASE = {
3240
3259
  "stackwright-pro-dashboard-otter": "dashboard",
3241
3260
  "stackwright-pro-form-wizard-otter": "workflow",
3242
3261
  "stackwright-pro-geo-otter": "geo",
3262
+ // auth-reconcile runs AFTER pages+dashboard+geo+workflow (wave 6)
3263
+ // Reads auth-config.json + all four manifests; emits auth-reconcile-config.json + stackwright.auth.yml
3264
+ "stackwright-pro-auth-reconcile-otter": "auth-reconcile",
3243
3265
  "stackwright-pro-polish-otter": "polish",
3244
3266
  "stackwright-services-otter": "services",
3245
3267
  "stackwright-pro-qa-otter": "qa"
@@ -3455,13 +3477,19 @@ var PHASE_ORDER = [
3455
3477
  "api",
3456
3478
  "data",
3457
3479
  "auth",
3458
- // moved earlier only depends on design-language.json (designer)
3480
+ // wave 2 — moved earlier, only depends on design-language.json (designer)
3459
3481
  "geo",
3460
3482
  "workflow",
3461
3483
  "services",
3462
3484
  "pages",
3463
3485
  "dashboard",
3486
+ // auth-reconcile: wave 6 — post-manifest reconciliation
3487
+ // Reads auth-config.json + pages/dashboard/geo/workflow manifests;
3488
+ // emits auth-reconcile-config.json + stackwright.auth.yml (Gate 4 single source of truth)
3489
+ "auth-reconcile",
3490
+ // wave 7: polish — depends on auth-reconcile (replaces auth-config.json direct dep)
3464
3491
  "polish",
3492
+ // wave 8: qa — terminal QA gate, depends on polish + transitively everything
3465
3493
  "qa"
3466
3494
  ];
3467
3495
  var PHASE_ARTIFACT = {
@@ -3475,6 +3503,7 @@ var PHASE_ARTIFACT = {
3475
3503
  dashboard: "dashboard-manifest.json",
3476
3504
  workflow: "workflow-config.json",
3477
3505
  services: "services-config.json",
3506
+ "auth-reconcile": "auth-reconcile-config.json",
3478
3507
  polish: "polish-manifest.json",
3479
3508
  geo: "geo-manifest.json",
3480
3509
  qa: "qa-findings.json"
@@ -3489,11 +3518,13 @@ var PHASE_TO_OTTER2 = {
3489
3518
  pages: "stackwright-pro-page-otter",
3490
3519
  dashboard: "stackwright-pro-dashboard-otter",
3491
3520
  workflow: "stackwright-pro-form-wizard-otter",
3521
+ "auth-reconcile": "stackwright-pro-auth-reconcile-otter",
3492
3522
  services: "stackwright-services-otter",
3493
3523
  polish: "stackwright-pro-polish-otter",
3494
3524
  geo: "stackwright-pro-geo-otter",
3495
3525
  qa: "stackwright-pro-qa-otter"
3496
3526
  };
3527
+ var AUTO_EXECUTE_PHASES = /* @__PURE__ */ new Set(["auth-reconcile"]);
3497
3528
  var PhaseStatusSchema = import_zod14.z.object({
3498
3529
  status: import_zod14.z.enum(["pending", "running", "completed", "skipped", "failed"]).default("pending"),
3499
3530
  questionsCollected: import_zod14.z.boolean().default(false),
@@ -3750,6 +3781,9 @@ function handleCheckExecutionReady(_cwd, phase) {
3750
3781
  isError: true
3751
3782
  };
3752
3783
  }
3784
+ if (AUTO_EXECUTE_PHASES.has(phase)) {
3785
+ return { text: JSON.stringify({ ready: true, phase, autoExecute: true }), isError: false };
3786
+ }
3753
3787
  const answerFile = (0, import_path8.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3754
3788
  if (!(0, import_fs8.existsSync)(answerFile)) {
3755
3789
  return {
@@ -3779,6 +3813,10 @@ function handleCheckExecutionReady(_cwd, phase) {
3779
3813
  const answeredPhases = [];
3780
3814
  const missingPhases = [];
3781
3815
  for (const phase2 of PHASE_ORDER) {
3816
+ if (AUTO_EXECUTE_PHASES.has(phase2)) {
3817
+ answeredPhases.push(phase2);
3818
+ continue;
3819
+ }
3782
3820
  const answerFile = (0, import_path8.join)(answersDir, `${phase2}.json`);
3783
3821
  if ((0, import_fs8.existsSync)(answerFile)) {
3784
3822
  try {
@@ -4107,6 +4145,8 @@ var PHASE_REQUIRED_KEYS = {
4107
4145
  dashboard: ["version", "generatedBy"],
4108
4146
  workflow: ["version", "generatedBy"],
4109
4147
  services: ["version", "generatedBy", "flows"],
4148
+ // auth-reconcile: must have version, generatedBy, and merged protectedRoutes
4149
+ "auth-reconcile": ["version", "generatedBy", "protectedRoutes"],
4110
4150
  polish: ["version", "generatedBy"],
4111
4151
  geo: ["version", "generatedBy", "geoCollections"],
4112
4152
  // qa: skipped=true path only needs version+generatedBy+skipped;
@@ -4386,6 +4426,28 @@ var PHASE_ARTIFACT_SCHEMA = {
4386
4426
  null,
4387
4427
  2
4388
4428
  ),
4429
+ "auth-reconcile": JSON.stringify(
4430
+ {
4431
+ version: "1.0",
4432
+ generatedBy: "stackwright-pro-auth-reconcile-otter",
4433
+ authType: "<oidc|saml|cac|oauth2>",
4434
+ rbacRoles: ["<HIGHEST_ROLE>", "<MID_ROLE>", "<LOWEST_ROLE>"],
4435
+ rbacDefaultRole: "<LOWEST_ROLE>",
4436
+ protectedRoutes: [
4437
+ { pattern: "/dashboard", requiredRole: "<role>" },
4438
+ { pattern: "/dashboard/:path*", requiredRole: "<role>" }
4439
+ ],
4440
+ uncoveredSlugs: [],
4441
+ manifestsRead: ["pages-manifest.json", "dashboard-manifest.json", "geo-manifest.json"],
4442
+ summary: {
4443
+ totalRoutes: 2,
4444
+ manifestDerived: 2,
4445
+ crossCutting: 0
4446
+ }
4447
+ },
4448
+ null,
4449
+ 2
4450
+ ),
4389
4451
  polish: JSON.stringify(
4390
4452
  {
4391
4453
  version: "1.0",
@@ -5137,6 +5199,20 @@ var OTTER_WRITE_ALLOWLISTS = {
5137
5199
  description: "Stackwright config (themeName + customTheme writeback)"
5138
5200
  }
5139
5201
  ],
5202
+ // swp-hng0: auth-reconcile otter — reads all manifests, re-emits auth-reconcile-config.json
5203
+ // AND overwrites stackwright.auth.yml (Gate 4: single source of truth after reconciliation)
5204
+ "stackwright-pro-auth-reconcile-otter": [
5205
+ {
5206
+ prefix: ".stackwright/artifacts/",
5207
+ suffix: ".json",
5208
+ description: "Auth reconcile artifact (auth-reconcile-config.json)"
5209
+ },
5210
+ {
5211
+ prefix: "stackwright.auth.",
5212
+ suffix: ".yml",
5213
+ description: "Canonical auth sidecar (stackwright.auth.yml \u2014 Gate 4 rewrite)"
5214
+ }
5215
+ ],
5140
5216
  "stackwright-pro-auth-otter": [
5141
5217
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
5142
5218
  {
@@ -6560,7 +6636,11 @@ var _checksums = /* @__PURE__ */ new Map([
6560
6636
  ],
6561
6637
  [
6562
6638
  "stackwright-pro-auth-otter.json",
6563
- "eee511fa214203ce88d51996c6b1b483b6ad3a0ae474cc7be5d380fdbbc43bb1"
6639
+ "c9302132ac4180c70dd452814eb4aa4fb9a1d6a9683f6e01ffce509a6001c6cf"
6640
+ ],
6641
+ [
6642
+ "stackwright-pro-auth-reconcile-otter.json",
6643
+ "7ffd67d51568c7b2a1eb2ea0ac2b2a8041eb9ddd9c1eb5b95744e55ba049b385"
6564
6644
  ],
6565
6645
  [
6566
6646
  "stackwright-pro-dashboard-otter.json",
@@ -6568,7 +6648,7 @@ var _checksums = /* @__PURE__ */ new Map([
6568
6648
  ],
6569
6649
  [
6570
6650
  "stackwright-pro-data-otter.json",
6571
- "be4e3d6f530ae2d6b79d6412e8d2d6defb3caffddedf5be4f928976a72206074"
6651
+ "7d1de846488d84eebf8798aef4d75f4f4d250c1ae070ce1200ead3bb8a545eaf"
6572
6652
  ],
6573
6653
  [
6574
6654
  "stackwright-pro-designer-otter.json",
@@ -6596,7 +6676,7 @@ var _checksums = /* @__PURE__ */ new Map([
6596
6676
  ],
6597
6677
  [
6598
6678
  "stackwright-pro-polish-otter.json",
6599
- "4b8223d89af7f92b9051fee7bb29fc4b1ee2a5a9ab87e8e67a6d8be6122fa029"
6679
+ "3ae1252a60727ebca67e33a807d5061cafb88de52f3b98a9d4641f9db725a6ed"
6600
6680
  ],
6601
6681
  [
6602
6682
  "stackwright-pro-qa-otter.json",
@@ -8501,16 +8581,185 @@ function registerListSpecsTool(server2) {
8501
8581
  );
8502
8582
  }
8503
8583
 
8504
- // src/tools/consolidate-integrations.ts
8584
+ // src/tools/describe-endpoint.ts
8505
8585
  var import_zod25 = require("zod");
8506
8586
  var import_fs18 = require("fs");
8507
- var import_proper_lockfile2 = require("proper-lockfile");
8508
8587
  var import_path18 = require("path");
8509
8588
  var import_js_yaml5 = __toESM(require("js-yaml"));
8589
+ function resolveRef(root, ref) {
8590
+ if (!ref.startsWith("#/")) {
8591
+ throw new Error(`[describe_endpoint] External $refs not supported: "${ref}"`);
8592
+ }
8593
+ const parts = ref.slice(2).split("/");
8594
+ let current = root;
8595
+ for (const part of parts) {
8596
+ const key = part.replace(/~1/g, "/").replace(/~0/g, "~");
8597
+ if (current == null || typeof current !== "object") {
8598
+ throw new Error(
8599
+ `[describe_endpoint] $ref "${ref}" cannot be resolved \u2014 segment "${key}" not found`
8600
+ );
8601
+ }
8602
+ current = current[key];
8603
+ }
8604
+ if (current == null || typeof current !== "object") {
8605
+ throw new Error(`[describe_endpoint] $ref "${ref}" resolved to a non-object value`);
8606
+ }
8607
+ return current;
8608
+ }
8609
+ function deref(root, obj) {
8610
+ if (obj == null || typeof obj !== "object") return {};
8611
+ const o = obj;
8612
+ if (typeof o["$ref"] === "string") {
8613
+ const resolved = resolveRef(root, o["$ref"]);
8614
+ return deref(root, resolved);
8615
+ }
8616
+ return o;
8617
+ }
8618
+ function loadSpec(specPath) {
8619
+ const raw = (0, import_fs18.readFileSync)(specPath, "utf-8");
8620
+ const ext = (0, import_path18.extname)(specPath).toLowerCase();
8621
+ if (ext === ".json") {
8622
+ return JSON.parse(raw);
8623
+ }
8624
+ const parsed = import_js_yaml5.default.load(raw);
8625
+ if (parsed == null || typeof parsed !== "object") {
8626
+ throw new Error(`[describe_endpoint] Spec file "${specPath}" did not parse to an object`);
8627
+ }
8628
+ return parsed;
8629
+ }
8630
+ function extractParams(root, pathItem, operation) {
8631
+ const pathLevelRaw = Array.isArray(pathItem["parameters"]) ? pathItem["parameters"] : [];
8632
+ const opLevelRaw = Array.isArray(operation["parameters"]) ? operation["parameters"] : [];
8633
+ const merged = /* @__PURE__ */ new Map();
8634
+ for (const raw of [...pathLevelRaw, ...opLevelRaw]) {
8635
+ const param = deref(root, raw);
8636
+ const name = String(param["name"] ?? "");
8637
+ const inVal = String(param["in"] ?? "");
8638
+ if (name && inVal) {
8639
+ merged.set(`${name}:${inVal}`, param);
8640
+ }
8641
+ }
8642
+ const pathParams = [];
8643
+ const queryParams = [];
8644
+ for (const param of merged.values()) {
8645
+ const inVal = String(param["in"] ?? "");
8646
+ const name = String(param["name"] ?? "");
8647
+ const schema = deref(root, param["schema"] ?? {});
8648
+ if (inVal === "path") {
8649
+ pathParams.push({ name, in: "path", required: true, schema });
8650
+ } else if (inVal === "query") {
8651
+ const required = param["required"] === true;
8652
+ queryParams.push({ name, in: "query", required, schema });
8653
+ }
8654
+ }
8655
+ return { pathParams, queryParams };
8656
+ }
8657
+ function extractSecurity(root, globalSecurity, operationSecurity) {
8658
+ const securityRequirements = operationSecurity !== void 0 ? operationSecurity : globalSecurity;
8659
+ if (securityRequirements.length === 0) return [];
8660
+ const schemes = root["components"]?.["securitySchemes"];
8661
+ if (!schemes || typeof schemes !== "object") return [];
8662
+ const result = [];
8663
+ const seen = /* @__PURE__ */ new Set();
8664
+ for (const req of securityRequirements) {
8665
+ if (!req || typeof req !== "object") continue;
8666
+ for (const schemeName of Object.keys(req)) {
8667
+ if (seen.has(schemeName)) continue;
8668
+ seen.add(schemeName);
8669
+ const schemeRaw = schemes[schemeName];
8670
+ if (!schemeRaw) continue;
8671
+ const scheme = deref(root, schemeRaw);
8672
+ const type = String(scheme["type"] ?? "unknown");
8673
+ if (type === "apiKey") {
8674
+ const inVal = String(scheme["in"] ?? "header");
8675
+ const paramName = String(scheme["name"] ?? schemeName);
8676
+ result.push({ name: schemeName, type, in: inVal, paramName });
8677
+ } else if (type === "http") {
8678
+ const httpScheme = String(scheme["scheme"] ?? "bearer").toLowerCase();
8679
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8680
+ void httpScheme;
8681
+ } else if (type === "oauth2") {
8682
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8683
+ } else if (type === "openIdConnect") {
8684
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8685
+ } else {
8686
+ result.push({ name: schemeName, type, in: "unknown", paramName: schemeName });
8687
+ }
8688
+ }
8689
+ }
8690
+ return result;
8691
+ }
8692
+ function handleDescribeEndpoint(input) {
8693
+ const { specPath, path: path5, method } = input;
8694
+ const normalizedMethod = method.toLowerCase();
8695
+ const root = loadSpec(specPath);
8696
+ const globalSecurity = Array.isArray(root["security"]) ? root["security"] : [];
8697
+ const paths = root["paths"];
8698
+ if (!paths) {
8699
+ return { pathParams: [], queryParams: [], security: [] };
8700
+ }
8701
+ const pathItem = deref(root, paths[path5]);
8702
+ if (!pathItem || Object.keys(pathItem).length === 0) {
8703
+ throw new Error(`[describe_endpoint] Path "${path5}" not found in spec "${specPath}"`);
8704
+ }
8705
+ const operation = deref(root, pathItem[normalizedMethod]);
8706
+ if (!operation || Object.keys(operation).length === 0) {
8707
+ throw new Error(
8708
+ `[describe_endpoint] Method "${method.toUpperCase()}" not found at path "${path5}" in spec "${specPath}"`
8709
+ );
8710
+ }
8711
+ const { pathParams, queryParams } = extractParams(root, pathItem, operation);
8712
+ const operationSecurity = Array.isArray(operation["security"]) ? operation["security"] : void 0;
8713
+ const security = extractSecurity(root, globalSecurity, operationSecurity);
8714
+ return { pathParams, queryParams, security };
8715
+ }
8716
+ function registerDescribeEndpointTool(server2) {
8717
+ server2.tool(
8718
+ "stackwright_pro_describe_endpoint",
8719
+ [
8720
+ "Inspect an OpenAPI 3.0/3.1 spec and return resolved parameter and security details for a",
8721
+ "specific operation. Deterministic \u2014 no LLM. Handles $ref resolution within the spec document.",
8722
+ "",
8723
+ "Returns:",
8724
+ " pathParams \u2014 path parameters [{name, in, required, schema}]",
8725
+ " queryParams \u2014 query parameters [{name, in, required, schema}]",
8726
+ " security \u2014 applicable security schemes [{name, type, in, paramName}]",
8727
+ "",
8728
+ "Used by the data-otter to populate params: entries in stackwright.collections.yml",
8729
+ "from required query params and resolved auth query params (swp-8r8i + swp-1l6l)."
8730
+ ].join("\n"),
8731
+ {
8732
+ specPath: import_zod25.z.string().describe("Absolute path to the OpenAPI spec file (.yaml, .yml, or .json)"),
8733
+ path: import_zod25.z.string().describe('The API path to inspect (e.g. "/stations/{stationId}/observations")'),
8734
+ method: import_zod25.z.string().describe('HTTP method (case-insensitive: "get", "post", "put", etc.)')
8735
+ },
8736
+ async ({ specPath, path: path5, method }) => {
8737
+ try {
8738
+ const result = handleDescribeEndpoint({ specPath, path: path5, method });
8739
+ return {
8740
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
8741
+ };
8742
+ } catch (err) {
8743
+ const message = err instanceof Error ? err.message : String(err);
8744
+ return {
8745
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
8746
+ isError: true
8747
+ };
8748
+ }
8749
+ }
8750
+ );
8751
+ }
8752
+
8753
+ // src/tools/consolidate-integrations.ts
8754
+ var import_zod26 = require("zod");
8755
+ var import_fs19 = require("fs");
8756
+ var import_proper_lockfile2 = require("proper-lockfile");
8757
+ var import_path19 = require("path");
8758
+ var import_js_yaml6 = __toESM(require("js-yaml"));
8510
8759
  var BASE_MOCK_PORT = 4011;
8511
8760
  var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
8512
8761
  function deriveNameFromFilename(filename) {
8513
- const base = (0, import_path18.basename)(filename, ".json").replace(/^api-config-/, "");
8762
+ const base = (0, import_path19.basename)(filename, ".json").replace(/^api-config-/, "");
8514
8763
  for (const suffix of STRIP_SUFFIXES2) {
8515
8764
  if (base.endsWith(suffix)) return base.slice(0, -suffix.length);
8516
8765
  }
@@ -8518,13 +8767,13 @@ function deriveNameFromFilename(filename) {
8518
8767
  }
8519
8768
  function handleConsolidateIntegrations(input) {
8520
8769
  const { projectRoot } = input;
8521
- const artifactsDir = (0, import_path18.join)(projectRoot, ".stackwright", "artifacts");
8522
- if (!(0, import_fs18.existsSync)(artifactsDir)) {
8770
+ const artifactsDir = (0, import_path19.join)(projectRoot, ".stackwright", "artifacts");
8771
+ if (!(0, import_fs19.existsSync)(artifactsDir)) {
8523
8772
  return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
8524
8773
  }
8525
8774
  let entries;
8526
8775
  try {
8527
- entries = (0, import_fs18.readdirSync)(artifactsDir).filter(
8776
+ entries = (0, import_fs19.readdirSync)(artifactsDir).filter(
8528
8777
  (f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
8529
8778
  );
8530
8779
  } catch {
@@ -8539,7 +8788,7 @@ function handleConsolidateIntegrations(input) {
8539
8788
  entries.forEach((filename, index) => {
8540
8789
  let artifact = {};
8541
8790
  try {
8542
- const raw = (0, import_fs18.readFileSync)((0, import_path18.join)(artifactsDir, filename), "utf-8");
8791
+ const raw = (0, import_fs19.readFileSync)((0, import_path19.join)(artifactsDir, filename), "utf-8");
8543
8792
  artifact = JSON.parse(raw);
8544
8793
  } catch {
8545
8794
  }
@@ -8572,20 +8821,20 @@ function handleConsolidateIntegrations(input) {
8572
8821
  });
8573
8822
  const integrations = Array.from(integrationsByName.values());
8574
8823
  const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
8575
- const integrationsYmlPath = (0, import_path18.join)(projectRoot, "stackwright.integrations.yml");
8824
+ const integrationsYmlPath = (0, import_path19.join)(projectRoot, "stackwright.integrations.yml");
8576
8825
  let existing = {};
8577
- if ((0, import_fs18.existsSync)(integrationsYmlPath)) {
8826
+ if ((0, import_fs19.existsSync)(integrationsYmlPath)) {
8578
8827
  try {
8579
- const raw = (0, import_fs18.readFileSync)(integrationsYmlPath, "utf-8");
8580
- existing = import_js_yaml5.default.load(raw) ?? {};
8828
+ const raw = (0, import_fs19.readFileSync)(integrationsYmlPath, "utf-8");
8829
+ existing = import_js_yaml6.default.load(raw) ?? {};
8581
8830
  } catch {
8582
8831
  existing = {};
8583
8832
  }
8584
8833
  }
8585
8834
  const merged = { ...existing, integrations };
8586
- (0, import_fs18.mkdirSync)((0, import_path18.join)(projectRoot), { recursive: true });
8587
- if (!(0, import_fs18.existsSync)(integrationsYmlPath)) {
8588
- (0, import_fs18.writeFileSync)(integrationsYmlPath, "", "utf-8");
8835
+ (0, import_fs19.mkdirSync)((0, import_path19.join)(projectRoot), { recursive: true });
8836
+ if (!(0, import_fs19.existsSync)(integrationsYmlPath)) {
8837
+ (0, import_fs19.writeFileSync)(integrationsYmlPath, "", "utf-8");
8589
8838
  }
8590
8839
  const release = (0, import_proper_lockfile2.lockSync)(integrationsYmlPath, {
8591
8840
  realpath: false,
@@ -8594,8 +8843,8 @@ function handleConsolidateIntegrations(input) {
8594
8843
  try {
8595
8844
  const header = `# stackwright.integrations.yml \u2014 Auto-generated by stackwright_pro_consolidate_integrations
8596
8845
  `;
8597
- const body = import_js_yaml5.default.dump(merged, { lineWidth: 120, noRefs: true });
8598
- (0, import_fs18.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
8846
+ const body = import_js_yaml6.default.dump(merged, { lineWidth: 120, noRefs: true });
8847
+ (0, import_fs19.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
8599
8848
  } finally {
8600
8849
  release();
8601
8850
  }
@@ -8623,7 +8872,7 @@ function registerConsolidateIntegrationsTool(server2) {
8623
8872
  "no per-spec artifacts are found."
8624
8873
  ].join(" "),
8625
8874
  {
8626
- projectRoot: import_zod25.z.string().describe("Absolute path to the project root directory")
8875
+ projectRoot: import_zod26.z.string().describe("Absolute path to the project root directory")
8627
8876
  },
8628
8877
  async ({ projectRoot }) => {
8629
8878
  try {
@@ -8681,7 +8930,7 @@ var package_default = {
8681
8930
  "test:coverage": "vitest run --coverage"
8682
8931
  },
8683
8932
  name: "@stackwright-pro/mcp",
8684
- version: "0.2.0-alpha.112",
8933
+ version: "0.2.0-alpha.113",
8685
8934
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
8686
8935
  license: "SEE LICENSE IN LICENSE",
8687
8936
  main: "./dist/server.js",
@@ -8751,6 +9000,7 @@ registerCompileTools(server);
8751
9000
  registerStripLegacyIntegrationsTool(server);
8752
9001
  registerEmitterTools(server);
8753
9002
  registerListSpecsTool(server);
9003
+ registerDescribeEndpointTool(server);
8754
9004
  registerConsolidateIntegrationsTool(server);
8755
9005
  (0, import_register.registerContentTypeTools)(server);
8756
9006
  (0, import_register.registerPageTools)(server);