@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.mjs CHANGED
@@ -556,7 +556,7 @@ function registerDashboardTools(server2) {
556
556
  yamlLines.push(` header: Status`);
557
557
  yamlLines.push(` type: badge`);
558
558
  }
559
- const yaml3 = yamlLines.join("\n");
559
+ const yaml4 = yamlLines.join("\n");
560
560
  return {
561
561
  content: [
562
562
  {
@@ -566,7 +566,7 @@ function registerDashboardTools(server2) {
566
566
  Layout: ${layout}
567
567
 
568
568
  \`\`\`yaml
569
- ${yaml3}
569
+ ${yaml4}
570
570
  \`\`\`
571
571
 
572
572
  \u{1F4A1} Add this to pages/dashboard/content.yml and validate with stackwright_validate_pages.`
@@ -647,7 +647,7 @@ ${yaml3}
647
647
  yamlLines.push(" action: navigate");
648
648
  yamlLines.push(` href: "/${entity}"`);
649
649
  yamlLines.push(" style: secondary");
650
- const yaml3 = yamlLines.join("\n");
650
+ const yaml4 = yamlLines.join("\n");
651
651
  return {
652
652
  content: [
653
653
  {
@@ -655,7 +655,7 @@ ${yaml3}
655
655
  text: `\u{1F4C4} Generated Detail Page for: ${entity}
656
656
 
657
657
  \`\`\`yaml
658
- ${yaml3}
658
+ ${yaml4}
659
659
  \`\`\`
660
660
 
661
661
  \u{1F4A1} Add this to pages/${entity}/[${slugField}]/content.yml and validate.`
@@ -2826,21 +2826,40 @@ function readAllManifestPages(artifactsDir) {
2826
2826
  return allPages;
2827
2827
  }
2828
2828
  for (const entry of entries) {
2829
- if (!entry.endsWith("-manifest.json")) continue;
2830
- const filePath = join5(artifactsDir, entry);
2831
- try {
2832
- const raw = JSON.parse(readFileSync5(filePath, "utf-8"));
2833
- if (!Array.isArray(raw.pages)) continue;
2834
- for (const page of raw.pages) {
2835
- if (typeof page.slug !== "string" || !page.slug) continue;
2829
+ if (entry.endsWith("-manifest.json")) {
2830
+ const filePath = join5(artifactsDir, entry);
2831
+ try {
2832
+ const raw = JSON.parse(readFileSync5(filePath, "utf-8"));
2833
+ if (!Array.isArray(raw.pages)) continue;
2834
+ for (const page of raw.pages) {
2835
+ if (typeof page.slug !== "string" || !page.slug) continue;
2836
+ allPages.push({
2837
+ slug: page.slug,
2838
+ authRequired: page.authRequired,
2839
+ requiredRoles: page.requiredRoles,
2840
+ source: entry
2841
+ });
2842
+ }
2843
+ } catch {
2844
+ }
2845
+ continue;
2846
+ }
2847
+ if (entry === "workflow-config.json" || entry.startsWith("workflow-config-")) {
2848
+ const filePath = join5(artifactsDir, entry);
2849
+ try {
2850
+ const raw = JSON.parse(readFileSync5(filePath, "utf-8"));
2851
+ const route = raw.workflow?.route;
2852
+ if (typeof route !== "string" || !route) continue;
2853
+ const slug = route.startsWith("/") ? route.slice(1) : route;
2836
2854
  allPages.push({
2837
- slug: page.slug,
2838
- authRequired: page.authRequired,
2839
- requiredRoles: page.requiredRoles,
2855
+ slug,
2856
+ authRequired: true,
2857
+ requiredRoles: void 0,
2840
2858
  source: entry
2841
2859
  });
2860
+ } catch {
2842
2861
  }
2843
- } catch {
2862
+ continue;
2844
2863
  }
2845
2864
  }
2846
2865
  return allPages;
@@ -2848,7 +2867,7 @@ function readAllManifestPages(artifactsDir) {
2848
2867
  function registerReadAuthManifestsTool(server2) {
2849
2868
  server2.tool(
2850
2869
  "stackwright_pro_read_auth_manifests",
2851
- "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.",
2870
+ "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).",
2852
2871
  {
2853
2872
  rbacRoles: z11.array(z11.string()).describe(
2854
2873
  'RBAC role hierarchy in descending privilege order, e.g. ["ESF8_COORDINATOR", "HOSPITAL_EM", "OBSERVER"]. Used to compute the lowestRole for each authRequired page.'
@@ -2994,7 +3013,7 @@ function enrichErrors(issues, synonyms) {
2994
3013
  });
2995
3014
  }
2996
3015
  function handleValidateYamlFragment(input) {
2997
- const { schemaName, yaml: yaml3 } = input;
3016
+ const { schemaName, yaml: yaml4 } = input;
2998
3017
  if (!SUPPORTED_SCHEMA_NAMES.includes(schemaName)) {
2999
3018
  return {
3000
3019
  valid: false,
@@ -3004,7 +3023,7 @@ function handleValidateYamlFragment(input) {
3004
3023
  const name = schemaName;
3005
3024
  let parsed;
3006
3025
  try {
3007
- parsed = yamlLoad2(yaml3);
3026
+ parsed = yamlLoad2(yaml4);
3008
3027
  } catch (err) {
3009
3028
  return {
3010
3029
  valid: false,
@@ -3030,8 +3049,8 @@ function registerValidateYamlFragmentTool(server2) {
3030
3049
  ),
3031
3050
  yaml: z12.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
3032
3051
  },
3033
- async ({ schemaName, yaml: yaml3 }) => {
3034
- const result = handleValidateYamlFragment({ schemaName, yaml: yaml3 });
3052
+ async ({ schemaName, yaml: yaml4 }) => {
3053
+ const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
3035
3054
  return {
3036
3055
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
3037
3056
  };
@@ -3244,6 +3263,9 @@ var MANIFEST_NAME_TO_PHASE = {
3244
3263
  "stackwright-pro-dashboard-otter": "dashboard",
3245
3264
  "stackwright-pro-form-wizard-otter": "workflow",
3246
3265
  "stackwright-pro-geo-otter": "geo",
3266
+ // auth-reconcile runs AFTER pages+dashboard+geo+workflow (wave 6)
3267
+ // Reads auth-config.json + all four manifests; emits auth-reconcile-config.json + stackwright.auth.yml
3268
+ "stackwright-pro-auth-reconcile-otter": "auth-reconcile",
3247
3269
  "stackwright-pro-polish-otter": "polish",
3248
3270
  "stackwright-services-otter": "services",
3249
3271
  "stackwright-pro-qa-otter": "qa"
@@ -3459,13 +3481,19 @@ var PHASE_ORDER = [
3459
3481
  "api",
3460
3482
  "data",
3461
3483
  "auth",
3462
- // moved earlier only depends on design-language.json (designer)
3484
+ // wave 2 — moved earlier, only depends on design-language.json (designer)
3463
3485
  "geo",
3464
3486
  "workflow",
3465
3487
  "services",
3466
3488
  "pages",
3467
3489
  "dashboard",
3490
+ // auth-reconcile: wave 6 — post-manifest reconciliation
3491
+ // Reads auth-config.json + pages/dashboard/geo/workflow manifests;
3492
+ // emits auth-reconcile-config.json + stackwright.auth.yml (Gate 4 single source of truth)
3493
+ "auth-reconcile",
3494
+ // wave 7: polish — depends on auth-reconcile (replaces auth-config.json direct dep)
3468
3495
  "polish",
3496
+ // wave 8: qa — terminal QA gate, depends on polish + transitively everything
3469
3497
  "qa"
3470
3498
  ];
3471
3499
  var PHASE_ARTIFACT = {
@@ -3479,6 +3507,7 @@ var PHASE_ARTIFACT = {
3479
3507
  dashboard: "dashboard-manifest.json",
3480
3508
  workflow: "workflow-config.json",
3481
3509
  services: "services-config.json",
3510
+ "auth-reconcile": "auth-reconcile-config.json",
3482
3511
  polish: "polish-manifest.json",
3483
3512
  geo: "geo-manifest.json",
3484
3513
  qa: "qa-findings.json"
@@ -3493,11 +3522,13 @@ var PHASE_TO_OTTER2 = {
3493
3522
  pages: "stackwright-pro-page-otter",
3494
3523
  dashboard: "stackwright-pro-dashboard-otter",
3495
3524
  workflow: "stackwright-pro-form-wizard-otter",
3525
+ "auth-reconcile": "stackwright-pro-auth-reconcile-otter",
3496
3526
  services: "stackwright-services-otter",
3497
3527
  polish: "stackwright-pro-polish-otter",
3498
3528
  geo: "stackwright-pro-geo-otter",
3499
3529
  qa: "stackwright-pro-qa-otter"
3500
3530
  };
3531
+ var AUTO_EXECUTE_PHASES = /* @__PURE__ */ new Set(["auth-reconcile"]);
3501
3532
  var PhaseStatusSchema = z14.object({
3502
3533
  status: z14.enum(["pending", "running", "completed", "skipped", "failed"]).default("pending"),
3503
3534
  questionsCollected: z14.boolean().default(false),
@@ -3754,6 +3785,9 @@ function handleCheckExecutionReady(_cwd, phase) {
3754
3785
  isError: true
3755
3786
  };
3756
3787
  }
3788
+ if (AUTO_EXECUTE_PHASES.has(phase)) {
3789
+ return { text: JSON.stringify({ ready: true, phase, autoExecute: true }), isError: false };
3790
+ }
3757
3791
  const answerFile = join7(cwd, ".stackwright", "answers", `${phase}.json`);
3758
3792
  if (!existsSync7(answerFile)) {
3759
3793
  return {
@@ -3783,6 +3817,10 @@ function handleCheckExecutionReady(_cwd, phase) {
3783
3817
  const answeredPhases = [];
3784
3818
  const missingPhases = [];
3785
3819
  for (const phase2 of PHASE_ORDER) {
3820
+ if (AUTO_EXECUTE_PHASES.has(phase2)) {
3821
+ answeredPhases.push(phase2);
3822
+ continue;
3823
+ }
3786
3824
  const answerFile = join7(answersDir, `${phase2}.json`);
3787
3825
  if (existsSync7(answerFile)) {
3788
3826
  try {
@@ -4111,6 +4149,8 @@ var PHASE_REQUIRED_KEYS = {
4111
4149
  dashboard: ["version", "generatedBy"],
4112
4150
  workflow: ["version", "generatedBy"],
4113
4151
  services: ["version", "generatedBy", "flows"],
4152
+ // auth-reconcile: must have version, generatedBy, and merged protectedRoutes
4153
+ "auth-reconcile": ["version", "generatedBy", "protectedRoutes"],
4114
4154
  polish: ["version", "generatedBy"],
4115
4155
  geo: ["version", "generatedBy", "geoCollections"],
4116
4156
  // qa: skipped=true path only needs version+generatedBy+skipped;
@@ -4390,6 +4430,28 @@ var PHASE_ARTIFACT_SCHEMA = {
4390
4430
  null,
4391
4431
  2
4392
4432
  ),
4433
+ "auth-reconcile": JSON.stringify(
4434
+ {
4435
+ version: "1.0",
4436
+ generatedBy: "stackwright-pro-auth-reconcile-otter",
4437
+ authType: "<oidc|saml|cac|oauth2>",
4438
+ rbacRoles: ["<HIGHEST_ROLE>", "<MID_ROLE>", "<LOWEST_ROLE>"],
4439
+ rbacDefaultRole: "<LOWEST_ROLE>",
4440
+ protectedRoutes: [
4441
+ { pattern: "/dashboard", requiredRole: "<role>" },
4442
+ { pattern: "/dashboard/:path*", requiredRole: "<role>" }
4443
+ ],
4444
+ uncoveredSlugs: [],
4445
+ manifestsRead: ["pages-manifest.json", "dashboard-manifest.json", "geo-manifest.json"],
4446
+ summary: {
4447
+ totalRoutes: 2,
4448
+ manifestDerived: 2,
4449
+ crossCutting: 0
4450
+ }
4451
+ },
4452
+ null,
4453
+ 2
4454
+ ),
4393
4455
  polish: JSON.stringify(
4394
4456
  {
4395
4457
  version: "1.0",
@@ -5141,6 +5203,20 @@ var OTTER_WRITE_ALLOWLISTS = {
5141
5203
  description: "Stackwright config (themeName + customTheme writeback)"
5142
5204
  }
5143
5205
  ],
5206
+ // swp-hng0: auth-reconcile otter — reads all manifests, re-emits auth-reconcile-config.json
5207
+ // AND overwrites stackwright.auth.yml (Gate 4: single source of truth after reconciliation)
5208
+ "stackwright-pro-auth-reconcile-otter": [
5209
+ {
5210
+ prefix: ".stackwright/artifacts/",
5211
+ suffix: ".json",
5212
+ description: "Auth reconcile artifact (auth-reconcile-config.json)"
5213
+ },
5214
+ {
5215
+ prefix: "stackwright.auth.",
5216
+ suffix: ".yml",
5217
+ description: "Canonical auth sidecar (stackwright.auth.yml \u2014 Gate 4 rewrite)"
5218
+ }
5219
+ ],
5144
5220
  "stackwright-pro-auth-otter": [
5145
5221
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
5146
5222
  {
@@ -6564,7 +6640,11 @@ var _checksums = /* @__PURE__ */ new Map([
6564
6640
  ],
6565
6641
  [
6566
6642
  "stackwright-pro-auth-otter.json",
6567
- "eee511fa214203ce88d51996c6b1b483b6ad3a0ae474cc7be5d380fdbbc43bb1"
6643
+ "c9302132ac4180c70dd452814eb4aa4fb9a1d6a9683f6e01ffce509a6001c6cf"
6644
+ ],
6645
+ [
6646
+ "stackwright-pro-auth-reconcile-otter.json",
6647
+ "7ffd67d51568c7b2a1eb2ea0ac2b2a8041eb9ddd9c1eb5b95744e55ba049b385"
6568
6648
  ],
6569
6649
  [
6570
6650
  "stackwright-pro-dashboard-otter.json",
@@ -6572,7 +6652,7 @@ var _checksums = /* @__PURE__ */ new Map([
6572
6652
  ],
6573
6653
  [
6574
6654
  "stackwright-pro-data-otter.json",
6575
- "be4e3d6f530ae2d6b79d6412e8d2d6defb3caffddedf5be4f928976a72206074"
6655
+ "7d1de846488d84eebf8798aef4d75f4f4d250c1ae070ce1200ead3bb8a545eaf"
6576
6656
  ],
6577
6657
  [
6578
6658
  "stackwright-pro-designer-otter.json",
@@ -6600,7 +6680,7 @@ var _checksums = /* @__PURE__ */ new Map([
6600
6680
  ],
6601
6681
  [
6602
6682
  "stackwright-pro-polish-otter.json",
6603
- "4b8223d89af7f92b9051fee7bb29fc4b1ee2a5a9ab87e8e67a6d8be6122fa029"
6683
+ "3ae1252a60727ebca67e33a807d5061cafb88de52f3b98a9d4641f9db725a6ed"
6604
6684
  ],
6605
6685
  [
6606
6686
  "stackwright-pro-qa-otter.json",
@@ -8514,12 +8594,181 @@ function registerListSpecsTool(server2) {
8514
8594
  );
8515
8595
  }
8516
8596
 
8517
- // src/tools/consolidate-integrations.ts
8597
+ // src/tools/describe-endpoint.ts
8518
8598
  import { z as z25 } from "zod";
8519
- import { readdirSync as readdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
8599
+ import { readFileSync as readFileSync16 } from "fs";
8600
+ import { extname as extname2 } from "path";
8601
+ import yaml2 from "js-yaml";
8602
+ function resolveRef(root, ref) {
8603
+ if (!ref.startsWith("#/")) {
8604
+ throw new Error(`[describe_endpoint] External $refs not supported: "${ref}"`);
8605
+ }
8606
+ const parts = ref.slice(2).split("/");
8607
+ let current = root;
8608
+ for (const part of parts) {
8609
+ const key = part.replace(/~1/g, "/").replace(/~0/g, "~");
8610
+ if (current == null || typeof current !== "object") {
8611
+ throw new Error(
8612
+ `[describe_endpoint] $ref "${ref}" cannot be resolved \u2014 segment "${key}" not found`
8613
+ );
8614
+ }
8615
+ current = current[key];
8616
+ }
8617
+ if (current == null || typeof current !== "object") {
8618
+ throw new Error(`[describe_endpoint] $ref "${ref}" resolved to a non-object value`);
8619
+ }
8620
+ return current;
8621
+ }
8622
+ function deref(root, obj) {
8623
+ if (obj == null || typeof obj !== "object") return {};
8624
+ const o = obj;
8625
+ if (typeof o["$ref"] === "string") {
8626
+ const resolved = resolveRef(root, o["$ref"]);
8627
+ return deref(root, resolved);
8628
+ }
8629
+ return o;
8630
+ }
8631
+ function loadSpec(specPath) {
8632
+ const raw = readFileSync16(specPath, "utf-8");
8633
+ const ext = extname2(specPath).toLowerCase();
8634
+ if (ext === ".json") {
8635
+ return JSON.parse(raw);
8636
+ }
8637
+ const parsed = yaml2.load(raw);
8638
+ if (parsed == null || typeof parsed !== "object") {
8639
+ throw new Error(`[describe_endpoint] Spec file "${specPath}" did not parse to an object`);
8640
+ }
8641
+ return parsed;
8642
+ }
8643
+ function extractParams(root, pathItem, operation) {
8644
+ const pathLevelRaw = Array.isArray(pathItem["parameters"]) ? pathItem["parameters"] : [];
8645
+ const opLevelRaw = Array.isArray(operation["parameters"]) ? operation["parameters"] : [];
8646
+ const merged = /* @__PURE__ */ new Map();
8647
+ for (const raw of [...pathLevelRaw, ...opLevelRaw]) {
8648
+ const param = deref(root, raw);
8649
+ const name = String(param["name"] ?? "");
8650
+ const inVal = String(param["in"] ?? "");
8651
+ if (name && inVal) {
8652
+ merged.set(`${name}:${inVal}`, param);
8653
+ }
8654
+ }
8655
+ const pathParams = [];
8656
+ const queryParams = [];
8657
+ for (const param of merged.values()) {
8658
+ const inVal = String(param["in"] ?? "");
8659
+ const name = String(param["name"] ?? "");
8660
+ const schema = deref(root, param["schema"] ?? {});
8661
+ if (inVal === "path") {
8662
+ pathParams.push({ name, in: "path", required: true, schema });
8663
+ } else if (inVal === "query") {
8664
+ const required = param["required"] === true;
8665
+ queryParams.push({ name, in: "query", required, schema });
8666
+ }
8667
+ }
8668
+ return { pathParams, queryParams };
8669
+ }
8670
+ function extractSecurity(root, globalSecurity, operationSecurity) {
8671
+ const securityRequirements = operationSecurity !== void 0 ? operationSecurity : globalSecurity;
8672
+ if (securityRequirements.length === 0) return [];
8673
+ const schemes = root["components"]?.["securitySchemes"];
8674
+ if (!schemes || typeof schemes !== "object") return [];
8675
+ const result = [];
8676
+ const seen = /* @__PURE__ */ new Set();
8677
+ for (const req of securityRequirements) {
8678
+ if (!req || typeof req !== "object") continue;
8679
+ for (const schemeName of Object.keys(req)) {
8680
+ if (seen.has(schemeName)) continue;
8681
+ seen.add(schemeName);
8682
+ const schemeRaw = schemes[schemeName];
8683
+ if (!schemeRaw) continue;
8684
+ const scheme = deref(root, schemeRaw);
8685
+ const type = String(scheme["type"] ?? "unknown");
8686
+ if (type === "apiKey") {
8687
+ const inVal = String(scheme["in"] ?? "header");
8688
+ const paramName = String(scheme["name"] ?? schemeName);
8689
+ result.push({ name: schemeName, type, in: inVal, paramName });
8690
+ } else if (type === "http") {
8691
+ const httpScheme = String(scheme["scheme"] ?? "bearer").toLowerCase();
8692
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8693
+ void httpScheme;
8694
+ } else if (type === "oauth2") {
8695
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8696
+ } else if (type === "openIdConnect") {
8697
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8698
+ } else {
8699
+ result.push({ name: schemeName, type, in: "unknown", paramName: schemeName });
8700
+ }
8701
+ }
8702
+ }
8703
+ return result;
8704
+ }
8705
+ function handleDescribeEndpoint(input) {
8706
+ const { specPath, path: path5, method } = input;
8707
+ const normalizedMethod = method.toLowerCase();
8708
+ const root = loadSpec(specPath);
8709
+ const globalSecurity = Array.isArray(root["security"]) ? root["security"] : [];
8710
+ const paths = root["paths"];
8711
+ if (!paths) {
8712
+ return { pathParams: [], queryParams: [], security: [] };
8713
+ }
8714
+ const pathItem = deref(root, paths[path5]);
8715
+ if (!pathItem || Object.keys(pathItem).length === 0) {
8716
+ throw new Error(`[describe_endpoint] Path "${path5}" not found in spec "${specPath}"`);
8717
+ }
8718
+ const operation = deref(root, pathItem[normalizedMethod]);
8719
+ if (!operation || Object.keys(operation).length === 0) {
8720
+ throw new Error(
8721
+ `[describe_endpoint] Method "${method.toUpperCase()}" not found at path "${path5}" in spec "${specPath}"`
8722
+ );
8723
+ }
8724
+ const { pathParams, queryParams } = extractParams(root, pathItem, operation);
8725
+ const operationSecurity = Array.isArray(operation["security"]) ? operation["security"] : void 0;
8726
+ const security = extractSecurity(root, globalSecurity, operationSecurity);
8727
+ return { pathParams, queryParams, security };
8728
+ }
8729
+ function registerDescribeEndpointTool(server2) {
8730
+ server2.tool(
8731
+ "stackwright_pro_describe_endpoint",
8732
+ [
8733
+ "Inspect an OpenAPI 3.0/3.1 spec and return resolved parameter and security details for a",
8734
+ "specific operation. Deterministic \u2014 no LLM. Handles $ref resolution within the spec document.",
8735
+ "",
8736
+ "Returns:",
8737
+ " pathParams \u2014 path parameters [{name, in, required, schema}]",
8738
+ " queryParams \u2014 query parameters [{name, in, required, schema}]",
8739
+ " security \u2014 applicable security schemes [{name, type, in, paramName}]",
8740
+ "",
8741
+ "Used by the data-otter to populate params: entries in stackwright.collections.yml",
8742
+ "from required query params and resolved auth query params (swp-8r8i + swp-1l6l)."
8743
+ ].join("\n"),
8744
+ {
8745
+ specPath: z25.string().describe("Absolute path to the OpenAPI spec file (.yaml, .yml, or .json)"),
8746
+ path: z25.string().describe('The API path to inspect (e.g. "/stations/{stationId}/observations")'),
8747
+ method: z25.string().describe('HTTP method (case-insensitive: "get", "post", "put", etc.)')
8748
+ },
8749
+ async ({ specPath, path: path5, method }) => {
8750
+ try {
8751
+ const result = handleDescribeEndpoint({ specPath, path: path5, method });
8752
+ return {
8753
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
8754
+ };
8755
+ } catch (err) {
8756
+ const message = err instanceof Error ? err.message : String(err);
8757
+ return {
8758
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
8759
+ isError: true
8760
+ };
8761
+ }
8762
+ }
8763
+ );
8764
+ }
8765
+
8766
+ // src/tools/consolidate-integrations.ts
8767
+ import { z as z26 } from "zod";
8768
+ import { readdirSync as readdirSync10, readFileSync as readFileSync17, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
8520
8769
  import { lockSync as lockSync2 } from "proper-lockfile";
8521
8770
  import { join as join17, basename as basename3 } from "path";
8522
- import yaml2 from "js-yaml";
8771
+ import yaml3 from "js-yaml";
8523
8772
  var BASE_MOCK_PORT = 4011;
8524
8773
  var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
8525
8774
  function deriveNameFromFilename(filename) {
@@ -8552,7 +8801,7 @@ function handleConsolidateIntegrations(input) {
8552
8801
  entries.forEach((filename, index) => {
8553
8802
  let artifact = {};
8554
8803
  try {
8555
- const raw = readFileSync16(join17(artifactsDir, filename), "utf-8");
8804
+ const raw = readFileSync17(join17(artifactsDir, filename), "utf-8");
8556
8805
  artifact = JSON.parse(raw);
8557
8806
  } catch {
8558
8807
  }
@@ -8589,8 +8838,8 @@ function handleConsolidateIntegrations(input) {
8589
8838
  let existing = {};
8590
8839
  if (existsSync16(integrationsYmlPath)) {
8591
8840
  try {
8592
- const raw = readFileSync16(integrationsYmlPath, "utf-8");
8593
- existing = yaml2.load(raw) ?? {};
8841
+ const raw = readFileSync17(integrationsYmlPath, "utf-8");
8842
+ existing = yaml3.load(raw) ?? {};
8594
8843
  } catch {
8595
8844
  existing = {};
8596
8845
  }
@@ -8607,7 +8856,7 @@ function handleConsolidateIntegrations(input) {
8607
8856
  try {
8608
8857
  const header = `# stackwright.integrations.yml \u2014 Auto-generated by stackwright_pro_consolidate_integrations
8609
8858
  `;
8610
- const body = yaml2.dump(merged, { lineWidth: 120, noRefs: true });
8859
+ const body = yaml3.dump(merged, { lineWidth: 120, noRefs: true });
8611
8860
  writeFileSync10(integrationsYmlPath, header + body, "utf-8");
8612
8861
  } finally {
8613
8862
  release();
@@ -8636,7 +8885,7 @@ function registerConsolidateIntegrationsTool(server2) {
8636
8885
  "no per-spec artifacts are found."
8637
8886
  ].join(" "),
8638
8887
  {
8639
- projectRoot: z25.string().describe("Absolute path to the project root directory")
8888
+ projectRoot: z26.string().describe("Absolute path to the project root directory")
8640
8889
  },
8641
8890
  async ({ projectRoot }) => {
8642
8891
  try {
@@ -8694,7 +8943,7 @@ var package_default = {
8694
8943
  "test:coverage": "vitest run --coverage"
8695
8944
  },
8696
8945
  name: "@stackwright-pro/mcp",
8697
- version: "0.2.0-alpha.112",
8946
+ version: "0.2.0-alpha.113",
8698
8947
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
8699
8948
  license: "SEE LICENSE IN LICENSE",
8700
8949
  main: "./dist/server.js",
@@ -8777,6 +9026,7 @@ registerCompileTools(server);
8777
9026
  registerStripLegacyIntegrationsTool(server);
8778
9027
  registerEmitterTools(server);
8779
9028
  registerListSpecsTool(server);
9029
+ registerDescribeEndpointTool(server);
8780
9030
  registerConsolidateIntegrationsTool(server);
8781
9031
  registerContentTypeTools(server);
8782
9032
  registerPageTools(server);