@stackwright-pro/mcp 0.2.0-alpha.12 → 0.2.0-alpha.14
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/integrity.js +1 -1
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +1 -1
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +202 -141
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +202 -141
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -3,15 +3,44 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
|
|
5
5
|
// src/tools/data-explorer.ts
|
|
6
|
-
import { z } from "zod";
|
|
6
|
+
import { z as z2 } from "zod";
|
|
7
7
|
import { listEntities, generateFilter } from "@stackwright-pro/cli-data-explorer";
|
|
8
|
+
|
|
9
|
+
// src/coerce.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
function jsonCoerce(schema) {
|
|
12
|
+
return z.preprocess((v) => {
|
|
13
|
+
if (typeof v === "string") {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(v);
|
|
16
|
+
} catch {
|
|
17
|
+
return v;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return v;
|
|
21
|
+
}, schema);
|
|
22
|
+
}
|
|
23
|
+
function boolCoerce(schema) {
|
|
24
|
+
return z.preprocess((v) => v === "true" ? true : v === "false" ? false : v, schema);
|
|
25
|
+
}
|
|
26
|
+
function numCoerce(schema) {
|
|
27
|
+
return z.preprocess((v) => {
|
|
28
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
29
|
+
const n = Number(v);
|
|
30
|
+
if (!Number.isNaN(n)) return n;
|
|
31
|
+
}
|
|
32
|
+
return v;
|
|
33
|
+
}, schema);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/tools/data-explorer.ts
|
|
8
37
|
function registerDataExplorerTools(server2) {
|
|
9
38
|
server2.tool(
|
|
10
39
|
"stackwright_pro_list_entities",
|
|
11
40
|
"List all available API entities from OpenAPI specs or generated Zod schemas. Use this to discover what entities are available before generating endpoint filters. Returns entity names, endpoints, and field counts. Part of the Pro Otter Raft for building API-integrated Stackwright applications.",
|
|
12
41
|
{
|
|
13
|
-
specPath:
|
|
14
|
-
projectRoot:
|
|
42
|
+
specPath: z2.string().optional().describe("Path to OpenAPI spec file (YAML or JSON)"),
|
|
43
|
+
projectRoot: z2.string().optional().describe("Project root directory (auto-detected if omitted)")
|
|
15
44
|
},
|
|
16
45
|
async ({ specPath, projectRoot }) => {
|
|
17
46
|
const result = listEntities({
|
|
@@ -59,9 +88,13 @@ function registerDataExplorerTools(server2) {
|
|
|
59
88
|
"stackwright_pro_generate_filter",
|
|
60
89
|
"Generate endpoint filter configuration from selected entities. Creates include/exclude patterns for stackwright.yml OpenAPI integration. Use this after stackwright_pro_list_entities to select which API endpoints the application needs. Only selected endpoints will generate client code, reducing bundle size and improving security.",
|
|
61
90
|
{
|
|
62
|
-
selectedEntities:
|
|
63
|
-
|
|
64
|
-
|
|
91
|
+
selectedEntities: jsonCoerce(z2.array(z2.string())).describe(
|
|
92
|
+
'Entity slugs to include (e.g., ["equipment", "supplies"])'
|
|
93
|
+
),
|
|
94
|
+
excludePatterns: jsonCoerce(z2.array(z2.string()).optional()).describe(
|
|
95
|
+
'Glob patterns to exclude (e.g., ["/admin/**", "/reports/**"])'
|
|
96
|
+
),
|
|
97
|
+
projectRoot: z2.string().optional().describe("Project root directory")
|
|
65
98
|
},
|
|
66
99
|
async ({ selectedEntities, excludePatterns, projectRoot }) => {
|
|
67
100
|
const result = generateFilter({
|
|
@@ -117,7 +150,7 @@ function registerDataExplorerTools(server2) {
|
|
|
117
150
|
}
|
|
118
151
|
|
|
119
152
|
// src/tools/security.ts
|
|
120
|
-
import { z as
|
|
153
|
+
import { z as z3 } from "zod";
|
|
121
154
|
import { createHash } from "crypto";
|
|
122
155
|
import fs from "fs";
|
|
123
156
|
import path from "path";
|
|
@@ -126,8 +159,8 @@ function registerSecurityTools(server2) {
|
|
|
126
159
|
"stackwright_pro_validate_spec",
|
|
127
160
|
"Validate an OpenAPI spec against the enterprise approved-specs configuration. Checks if the spec URL is on the allowlist and verifies SHA-256 hash integrity. Use this in enterprise environments where only pre-approved API specs are allowed. Fails build if spec is not approved or has been modified.",
|
|
128
161
|
{
|
|
129
|
-
specPath:
|
|
130
|
-
configPath:
|
|
162
|
+
specPath: z3.string().describe("URL or file path to the OpenAPI spec to validate"),
|
|
163
|
+
configPath: z3.string().optional().describe("Path to stackwright.yml with prebuild.security config")
|
|
131
164
|
},
|
|
132
165
|
async ({ specPath, configPath }) => {
|
|
133
166
|
let securityEnabled = false;
|
|
@@ -235,9 +268,9 @@ Status: Valid (${allowlist.length} specs on allowlist)`
|
|
|
235
268
|
"stackwright_pro_add_approved_spec",
|
|
236
269
|
"Add an OpenAPI spec to the approved-specs allowlist in stackwright.yml. Computes the SHA-256 hash of the spec and adds it to the security configuration. Use this when onboarding a new API in enterprise environments.",
|
|
237
270
|
{
|
|
238
|
-
name:
|
|
239
|
-
url:
|
|
240
|
-
configPath:
|
|
271
|
+
name: z3.string().describe("Human-readable name for the spec"),
|
|
272
|
+
url: z3.string().describe("URL or file path to the OpenAPI spec"),
|
|
273
|
+
configPath: z3.string().optional().describe("Path to stackwright.yml")
|
|
241
274
|
},
|
|
242
275
|
async ({ name, url, configPath }) => {
|
|
243
276
|
const configFile = configPath || path.join(process.cwd(), "stackwright.yml");
|
|
@@ -291,7 +324,7 @@ SHA-256 computed: ${sha256}`
|
|
|
291
324
|
"stackwright_pro_list_approved_specs",
|
|
292
325
|
"List all specs currently on the approved-specs allowlist. Shows spec names, URLs, and hash prefixes. Use this to audit what APIs are approved in the project.",
|
|
293
326
|
{
|
|
294
|
-
configPath:
|
|
327
|
+
configPath: z3.string().optional().describe("Path to stackwright.yml")
|
|
295
328
|
},
|
|
296
329
|
async ({ configPath }) => {
|
|
297
330
|
const configFile = configPath || path.join(process.cwd(), "stackwright.yml");
|
|
@@ -360,16 +393,18 @@ SHA-256 computed: ${sha256}`
|
|
|
360
393
|
}
|
|
361
394
|
|
|
362
395
|
// src/tools/isr.ts
|
|
363
|
-
import { z as
|
|
396
|
+
import { z as z4 } from "zod";
|
|
364
397
|
function registerIsrTools(server2) {
|
|
365
398
|
server2.tool(
|
|
366
399
|
"stackwright_pro_configure_isr",
|
|
367
400
|
"Configure Incremental Static Regeneration (ISR) for an API-backed collection. ISR allows API data to be cached and refreshed on a schedule, providing real-time data with the performance of static generation. Use this after stackwright_pro_generate_filter to set revalidation intervals.",
|
|
368
401
|
{
|
|
369
|
-
collection:
|
|
370
|
-
revalidateSeconds:
|
|
371
|
-
|
|
372
|
-
|
|
402
|
+
collection: z4.string().describe('Collection name (e.g., "equipment", "supplies")'),
|
|
403
|
+
revalidateSeconds: numCoerce(z4.number().optional()).describe(
|
|
404
|
+
"Revalidation interval in seconds (default: 60)"
|
|
405
|
+
),
|
|
406
|
+
fallback: z4.enum(["blocking", "true", "false"]).optional().describe("Fallback behavior for new pages"),
|
|
407
|
+
configPath: z4.string().optional().describe("Path to stackwright.yml")
|
|
373
408
|
},
|
|
374
409
|
async ({ collection, revalidateSeconds = 60, fallback = "blocking", configPath }) => {
|
|
375
410
|
const revalidate = revalidateSeconds;
|
|
@@ -412,14 +447,16 @@ ${yamlSnippet}
|
|
|
412
447
|
"stackwright_pro_configure_isr_batch",
|
|
413
448
|
"Configure ISR for multiple collections at once. More efficient than calling stackwright_pro_configure_isr multiple times. Provide different revalidation intervals based on data freshness requirements.",
|
|
414
449
|
{
|
|
415
|
-
collections:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
450
|
+
collections: jsonCoerce(
|
|
451
|
+
z4.array(
|
|
452
|
+
z4.object({
|
|
453
|
+
name: z4.string().describe("Collection name"),
|
|
454
|
+
revalidateSeconds: numCoerce(z4.number().optional()).describe("Revalidation interval")
|
|
455
|
+
})
|
|
456
|
+
)
|
|
420
457
|
).describe("Array of collection configurations"),
|
|
421
|
-
defaultFallback:
|
|
422
|
-
configPath:
|
|
458
|
+
defaultFallback: z4.enum(["blocking", "true", "false"]).optional().describe("Default fallback behavior"),
|
|
459
|
+
configPath: z4.string().optional().describe("Path to stackwright.yml")
|
|
423
460
|
},
|
|
424
461
|
async ({ collections, defaultFallback = "blocking", configPath }) => {
|
|
425
462
|
const lines = [`\u2699\uFE0F Batch ISR Configuration:
|
|
@@ -447,17 +484,17 @@ Fallback: ${defaultFallback}`);
|
|
|
447
484
|
}
|
|
448
485
|
|
|
449
486
|
// src/tools/dashboard.ts
|
|
450
|
-
import { z as
|
|
487
|
+
import { z as z5 } from "zod";
|
|
451
488
|
import { listEntities as listEntities2 } from "@stackwright-pro/cli-data-explorer";
|
|
452
489
|
function registerDashboardTools(server2) {
|
|
453
490
|
server2.tool(
|
|
454
491
|
"stackwright_pro_generate_dashboard",
|
|
455
492
|
"Generate a dashboard page configuration for displaying API data. Creates YAML content for a Stackwright page with grid, metric_card, data_table, and collection_list content types. Use this after stackwright_pro_generate_filter to create pages for your API collections.",
|
|
456
493
|
{
|
|
457
|
-
entities:
|
|
458
|
-
layout:
|
|
459
|
-
pageTitle:
|
|
460
|
-
specPath:
|
|
494
|
+
entities: jsonCoerce(z5.array(z5.string())).describe("Entity slugs to include in dashboard"),
|
|
495
|
+
layout: z5.enum(["grid", "table", "mixed"]).optional().describe("Dashboard layout style"),
|
|
496
|
+
pageTitle: z5.string().optional().describe("Page title"),
|
|
497
|
+
specPath: z5.string().optional().describe("Path to OpenAPI spec for entity details")
|
|
461
498
|
},
|
|
462
499
|
async ({ entities, layout = "mixed", pageTitle, specPath }) => {
|
|
463
500
|
let entityDetails = [];
|
|
@@ -543,9 +580,9 @@ ${yaml}
|
|
|
543
580
|
"stackwright_pro_generate_detail_page",
|
|
544
581
|
"Generate a detail view page for a single API entity. Creates YAML content for displaying all fields of an individual record. Use this to create detail pages that complement collection listing pages.",
|
|
545
582
|
{
|
|
546
|
-
entity:
|
|
547
|
-
slugField:
|
|
548
|
-
specPath:
|
|
583
|
+
entity: z5.string().describe('Entity slug (e.g., "equipment")'),
|
|
584
|
+
slugField: z5.string().optional().describe('Field to use as URL slug (default: "id")'),
|
|
585
|
+
specPath: z5.string().optional().describe("Path to OpenAPI spec for field details")
|
|
549
586
|
},
|
|
550
587
|
async ({ entity, slugField = "id", specPath }) => {
|
|
551
588
|
let fields = [];
|
|
@@ -621,7 +658,7 @@ ${yaml}
|
|
|
621
658
|
}
|
|
622
659
|
|
|
623
660
|
// src/tools/clarification.ts
|
|
624
|
-
import { z as
|
|
661
|
+
import { z as z6 } from "zod";
|
|
625
662
|
var CONTRADICTION_PATTERNS = [
|
|
626
663
|
{
|
|
627
664
|
keywords: ["minimal", "clean", "simple"],
|
|
@@ -709,12 +746,14 @@ function registerClarificationTools(server2) {
|
|
|
709
746
|
"stackwright_pro_clarify",
|
|
710
747
|
"Ask the user for clarification when a specialist otter encounters ambiguity. This is for MID-EXECUTION questions, NOT upfront question collection (use the Question Manifest Protocol for that). Returns a structured response for the foreman to present to the user via ask_user_question (closed_choice) or directly (open_text).",
|
|
711
748
|
{
|
|
712
|
-
context:
|
|
713
|
-
question_type:
|
|
714
|
-
question:
|
|
715
|
-
choices:
|
|
716
|
-
|
|
717
|
-
|
|
749
|
+
context: z6.string().optional().describe("Context about what the otter is trying to do"),
|
|
750
|
+
question_type: z6.enum(["closed_choice", "open_text"]).describe("Type of question being asked"),
|
|
751
|
+
question: z6.string().describe("The clarification question to ask the user"),
|
|
752
|
+
choices: jsonCoerce(z6.array(z6.string()).optional()).describe(
|
|
753
|
+
"Options for closed_choice questions"
|
|
754
|
+
),
|
|
755
|
+
priority: z6.enum(["blocking", "preferred", "optional"]).optional().default("preferred").describe("How critical is this clarification? Default: preferred"),
|
|
756
|
+
target_field: z6.string().optional().describe("What field/config does this clarify?")
|
|
718
757
|
},
|
|
719
758
|
async ({ context, question_type, question, choices, priority, target_field }) => {
|
|
720
759
|
const result = handleClarify({
|
|
@@ -743,8 +782,10 @@ function registerClarificationTools(server2) {
|
|
|
743
782
|
"stackwright_pro_detect_conflict",
|
|
744
783
|
"Detect when a user's stated preference conflicts with their selected choices. Uses keyword heuristics against known contradiction patterns (minimal vs vibrant, dark vs light, etc). Returns conflict details and resolution options.",
|
|
745
784
|
{
|
|
746
|
-
stated_preference:
|
|
747
|
-
selected_values:
|
|
785
|
+
stated_preference: z6.string().describe("What the user said they wanted"),
|
|
786
|
+
selected_values: jsonCoerce(z6.record(z6.string(), z6.string())).describe(
|
|
787
|
+
"What the user actually selected (field \u2192 value)"
|
|
788
|
+
)
|
|
748
789
|
},
|
|
749
790
|
async ({ stated_preference, selected_values }) => {
|
|
750
791
|
const result = handleDetectConflict({ stated_preference, selected_values });
|
|
@@ -789,7 +830,7 @@ ${result.resolution_options?.map((o) => ` \u2022 ${o}`).join("\n")}
|
|
|
789
830
|
}
|
|
790
831
|
|
|
791
832
|
// src/tools/packages.ts
|
|
792
|
-
import { z as
|
|
833
|
+
import { z as z7 } from "zod";
|
|
793
834
|
import { readFileSync, writeFileSync, existsSync, realpathSync, lstatSync } from "fs";
|
|
794
835
|
import { execSync } from "child_process";
|
|
795
836
|
import path2 from "path";
|
|
@@ -810,17 +851,23 @@ function registerPackageTools(server2) {
|
|
|
810
851
|
"Ensures pro packages are present in a project's package.json. Safe to call multiple times \u2014 never overwrites existing version pins. Use this to bootstrap dependencies before specialist otters run. Pass includeBaseline: true to automatically include all required @stackwright-pro/* baseline dependencies. Safe to call on existing projects \u2014 never overwrites pinned versions.",
|
|
811
852
|
{
|
|
812
853
|
// FIX 3 (B-new-1): Zod v4 requires two-arg z.record(keySchema, valueSchema)
|
|
813
|
-
packages:
|
|
814
|
-
'Dependencies to add. Record<packageName, version>. e.g. { "@stackwright-pro/auth": "latest" }'
|
|
854
|
+
packages: z7.record(z7.string(), z7.string()).optional().default({}).describe(
|
|
855
|
+
'Dependencies to add. Record<packageName, version>. e.g. { "@stackwright-pro/auth": "latest" }. Omit or pass {} when using includeBaseline: true.'
|
|
856
|
+
),
|
|
857
|
+
devPackages: jsonCoerce(z7.record(z7.string(), z7.string()).optional()).describe(
|
|
858
|
+
"devDependencies to add. Same format as packages."
|
|
815
859
|
),
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
860
|
+
scripts: jsonCoerce(z7.record(z7.string(), z7.string()).optional()).describe(
|
|
861
|
+
"npm scripts to add. Only adds if key does not already exist."
|
|
862
|
+
),
|
|
863
|
+
targetDir: z7.string().optional().describe(
|
|
819
864
|
"Project directory containing package.json. Defaults to process.cwd(). Must be an absolute path within the current working directory."
|
|
820
865
|
),
|
|
821
|
-
runInstall:
|
|
822
|
-
|
|
823
|
-
|
|
866
|
+
runInstall: boolCoerce(z7.boolean().optional().default(true)).describe(
|
|
867
|
+
"Run pnpm install after writing package.json. Defaults to true. Pass boolean true/false."
|
|
868
|
+
),
|
|
869
|
+
includeBaseline: boolCoerce(z7.boolean().optional().default(false)).describe(
|
|
870
|
+
"When true, automatically merges BASELINE_DEPS and BASELINE_DEV_DEPS before applying packages/devPackages args. Safe to call on existing projects \u2014 never overwrites pinned versions. Pass boolean true/false."
|
|
824
871
|
)
|
|
825
872
|
},
|
|
826
873
|
async ({ packages, devPackages, scripts, targetDir, runInstall, includeBaseline }) => {
|
|
@@ -978,11 +1025,11 @@ function setupPackages(opts) {
|
|
|
978
1025
|
}
|
|
979
1026
|
}
|
|
980
1027
|
const raw = readFileSync(realPackageJsonPath, "utf8");
|
|
981
|
-
const PackageJsonSchema =
|
|
1028
|
+
const PackageJsonSchema = z7.object({
|
|
982
1029
|
// Zod v4: z.record(keySchema, valueSchema) — two-arg form required
|
|
983
|
-
dependencies:
|
|
984
|
-
devDependencies:
|
|
985
|
-
scripts:
|
|
1030
|
+
dependencies: z7.record(z7.string(), z7.string()).optional(),
|
|
1031
|
+
devDependencies: z7.record(z7.string(), z7.string()).optional(),
|
|
1032
|
+
scripts: z7.record(z7.string(), z7.string()).optional()
|
|
986
1033
|
}).passthrough();
|
|
987
1034
|
const schemaResult = PackageJsonSchema.safeParse(JSON.parse(raw));
|
|
988
1035
|
if (!schemaResult.success) {
|
|
@@ -1065,7 +1112,7 @@ function setupPackages(opts) {
|
|
|
1065
1112
|
// src/tools/questions.ts
|
|
1066
1113
|
import { readFile } from "fs/promises";
|
|
1067
1114
|
import { join } from "path";
|
|
1068
|
-
import { z as
|
|
1115
|
+
import { z as z8 } from "zod";
|
|
1069
1116
|
|
|
1070
1117
|
// src/question-adapter.ts
|
|
1071
1118
|
function truncate(str, maxLength) {
|
|
@@ -1251,22 +1298,22 @@ function answersToManifestFormat(answers, questions) {
|
|
|
1251
1298
|
}
|
|
1252
1299
|
|
|
1253
1300
|
// src/tools/questions.ts
|
|
1254
|
-
var ManifestQuestionSchema =
|
|
1255
|
-
id:
|
|
1256
|
-
question:
|
|
1257
|
-
type:
|
|
1258
|
-
required:
|
|
1259
|
-
options:
|
|
1260
|
-
|
|
1261
|
-
label:
|
|
1262
|
-
value:
|
|
1301
|
+
var ManifestQuestionSchema = z8.object({
|
|
1302
|
+
id: z8.string(),
|
|
1303
|
+
question: z8.string(),
|
|
1304
|
+
type: z8.enum(["text", "select", "multi-select", "confirm"]),
|
|
1305
|
+
required: z8.boolean().optional(),
|
|
1306
|
+
options: z8.array(
|
|
1307
|
+
z8.object({
|
|
1308
|
+
label: z8.string(),
|
|
1309
|
+
value: z8.string()
|
|
1263
1310
|
})
|
|
1264
1311
|
).optional(),
|
|
1265
|
-
default:
|
|
1266
|
-
help:
|
|
1267
|
-
dependsOn:
|
|
1268
|
-
questionId:
|
|
1269
|
-
value:
|
|
1312
|
+
default: z8.union([z8.string(), z8.boolean(), z8.array(z8.string())]).optional(),
|
|
1313
|
+
help: z8.string().optional(),
|
|
1314
|
+
dependsOn: z8.object({
|
|
1315
|
+
questionId: z8.string(),
|
|
1316
|
+
value: z8.union([z8.string(), z8.array(z8.string())])
|
|
1270
1317
|
}).optional()
|
|
1271
1318
|
});
|
|
1272
1319
|
function registerQuestionTools(server2) {
|
|
@@ -1274,11 +1321,13 @@ function registerQuestionTools(server2) {
|
|
|
1274
1321
|
"stackwright_pro_present_phase_questions",
|
|
1275
1322
|
"Adapt manifest-format questions from a specialist otter and present them to the user via ask_user_question. Pass only the phase name \u2014 this tool reads questions from .stackwright/question-manifest.json automatically. The questions parameter is optional and only needed if the manifest has not been written yet. Use this instead of calling ask_user_question directly \u2014 it handles label truncation (50-char limit), header generation, confirm/text defaults, and correct array formatting automatically. IMPORTANT: This is the ONLY approved way to prepare questions before calling ask_user_question. Never call ask_user_question with raw manifest questions. Never retry ask_user_question validation errors by calling it directly \u2014 always re-call this tool.",
|
|
1276
1323
|
{
|
|
1277
|
-
phase:
|
|
1278
|
-
questions:
|
|
1324
|
+
phase: z8.string().describe('Phase name for display context, e.g. "designer", "api", "auth"'),
|
|
1325
|
+
questions: jsonCoerce(z8.array(ManifestQuestionSchema).optional()).describe(
|
|
1279
1326
|
"Questions in Question Manifest format. If omitted, questions are read from .stackwright/question-manifest.json using the phase name."
|
|
1280
1327
|
),
|
|
1281
|
-
answers:
|
|
1328
|
+
answers: jsonCoerce(
|
|
1329
|
+
z8.record(z8.string(), z8.union([z8.string(), z8.array(z8.string()), z8.boolean()])).optional()
|
|
1330
|
+
).describe("Previously collected answers used to resolve dependsOn conditions")
|
|
1282
1331
|
},
|
|
1283
1332
|
async ({ phase, questions, answers }) => {
|
|
1284
1333
|
let resolvedQuestions;
|
|
@@ -1363,7 +1412,7 @@ function registerQuestionTools(server2) {
|
|
|
1363
1412
|
}
|
|
1364
1413
|
|
|
1365
1414
|
// src/tools/orchestration.ts
|
|
1366
|
-
import { z as
|
|
1415
|
+
import { z as z9 } from "zod";
|
|
1367
1416
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync, lstatSync as lstatSync2 } from "fs";
|
|
1368
1417
|
import { join as join2 } from "path";
|
|
1369
1418
|
var OTTER_NAME_TO_PHASE = [
|
|
@@ -1536,8 +1585,8 @@ function registerOrchestrationTools(server2) {
|
|
|
1536
1585
|
"stackwright_pro_parse_otter_response",
|
|
1537
1586
|
"Parse and validate a specialist otter's QUESTION_COLLECTION_MODE JSON response. Handles JSON extraction from LLM responses (strips markdown, fixes single quotes, trailing commas). Detects the phase from the otter name. Use this immediately after invoke_agent() to get a validated manifest phase object.",
|
|
1538
1587
|
{
|
|
1539
|
-
otterName:
|
|
1540
|
-
responseText:
|
|
1588
|
+
otterName: z9.string().describe('The agent name, e.g. "stackwright-pro-api-otter"'),
|
|
1589
|
+
responseText: z9.string().describe("Raw text response from the otter's QUESTION_COLLECTION_MODE invocation")
|
|
1541
1590
|
},
|
|
1542
1591
|
async ({ otterName, responseText }) => {
|
|
1543
1592
|
const { result, isError } = handleParseOtterResponse({ otterName, responseText });
|
|
@@ -1551,16 +1600,18 @@ function registerOrchestrationTools(server2) {
|
|
|
1551
1600
|
"stackwright_pro_save_manifest",
|
|
1552
1601
|
"Write the question manifest to .stackwright/question-manifest.json. Call this after collecting and parsing questions from all otters via stackwright_pro_parse_otter_response.",
|
|
1553
1602
|
{
|
|
1554
|
-
phases:
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1603
|
+
phases: jsonCoerce(
|
|
1604
|
+
z9.array(
|
|
1605
|
+
z9.object({
|
|
1606
|
+
phase: z9.string(),
|
|
1607
|
+
otter: z9.string(),
|
|
1608
|
+
questions: z9.array(z9.any()),
|
|
1609
|
+
requiredPackages: z9.object({
|
|
1610
|
+
dependencies: z9.record(z9.string(), z9.string()).optional(),
|
|
1611
|
+
devPackages: z9.record(z9.string(), z9.string()).optional()
|
|
1612
|
+
}).optional()
|
|
1613
|
+
})
|
|
1614
|
+
)
|
|
1564
1615
|
).describe("Array of parsed phase objects from stackwright_pro_parse_otter_response")
|
|
1565
1616
|
},
|
|
1566
1617
|
async ({ phases }) => {
|
|
@@ -1575,15 +1626,21 @@ function registerOrchestrationTools(server2) {
|
|
|
1575
1626
|
"stackwright_pro_save_phase_answers",
|
|
1576
1627
|
"Save user answers for a phase to .stackwright/answers/{phase}.json. Pass rawAnswers directly from ask_user_question and the original manifest questions for label-to-value reverse mapping.",
|
|
1577
1628
|
{
|
|
1578
|
-
phase:
|
|
1579
|
-
rawAnswers:
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1629
|
+
phase: z9.string().describe('Phase name, e.g. "designer"'),
|
|
1630
|
+
rawAnswers: jsonCoerce(
|
|
1631
|
+
z9.array(
|
|
1632
|
+
z9.object({
|
|
1633
|
+
question_header: z9.string(),
|
|
1634
|
+
selected_options: z9.array(z9.string()),
|
|
1635
|
+
other_text: z9.string().nullable().optional()
|
|
1636
|
+
})
|
|
1637
|
+
)
|
|
1638
|
+
).describe(
|
|
1639
|
+
"Answers as returned by ask_user_question \u2014 pass the native array, not a JSON string"
|
|
1640
|
+
),
|
|
1641
|
+
questions: jsonCoerce(z9.array(z9.any()).optional()).describe(
|
|
1642
|
+
"Original manifest questions for label\u2192value reverse-mapping \u2014 pass the native array, not a JSON string"
|
|
1643
|
+
)
|
|
1587
1644
|
},
|
|
1588
1645
|
async ({ phase, rawAnswers, questions }) => {
|
|
1589
1646
|
const { text, isError } = handleSavePhaseAnswers({
|
|
@@ -1601,7 +1658,7 @@ function registerOrchestrationTools(server2) {
|
|
|
1601
1658
|
"stackwright_pro_read_phase_answers",
|
|
1602
1659
|
"Read saved answers for a phase from .stackwright/answers/{phase}.json. Returns { missing: true } when no answers exist yet \u2014 use this to skip phases safely in the execution loop.",
|
|
1603
1660
|
{
|
|
1604
|
-
phase:
|
|
1661
|
+
phase: z9.string().describe('Phase name, e.g. "designer"')
|
|
1605
1662
|
},
|
|
1606
1663
|
async ({ phase }) => {
|
|
1607
1664
|
const { text, isError } = handleReadPhaseAnswers({ phase });
|
|
@@ -1615,7 +1672,7 @@ function registerOrchestrationTools(server2) {
|
|
|
1615
1672
|
"stackwright_pro_get_otter_name",
|
|
1616
1673
|
"Get the agent name for a phase (e.g. 'designer' \u2192 'stackwright-pro-designer-otter'). Use this in the execution loop to invoke the correct specialist otter without hardcoding names in the prompt.",
|
|
1617
1674
|
{
|
|
1618
|
-
phase:
|
|
1675
|
+
phase: z9.string().describe('Phase name, e.g. "designer", "api", "pages"')
|
|
1619
1676
|
},
|
|
1620
1677
|
async ({ phase }) => {
|
|
1621
1678
|
const { text, isError } = handleGetOtterName({ phase });
|
|
@@ -1628,7 +1685,7 @@ function registerOrchestrationTools(server2) {
|
|
|
1628
1685
|
}
|
|
1629
1686
|
|
|
1630
1687
|
// src/tools/pipeline.ts
|
|
1631
|
-
import { z as
|
|
1688
|
+
import { z as z10 } from "zod";
|
|
1632
1689
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2, lstatSync as lstatSync3 } from "fs";
|
|
1633
1690
|
import { join as join3 } from "path";
|
|
1634
1691
|
var PHASE_ORDER = [
|
|
@@ -2102,11 +2159,15 @@ function registerPipelineTools(server2) {
|
|
|
2102
2159
|
"stackwright_pro_set_pipeline_state",
|
|
2103
2160
|
`Atomic read\u2192modify\u2192write pipeline state. ${DESC}`,
|
|
2104
2161
|
{
|
|
2105
|
-
phase:
|
|
2106
|
-
field:
|
|
2107
|
-
value:
|
|
2108
|
-
|
|
2109
|
-
|
|
2162
|
+
phase: z10.string().optional().describe('Phase to update, e.g. "designer"'),
|
|
2163
|
+
field: z10.enum(["questionsCollected", "answered", "executed", "artifactWritten"]).optional().describe("Boolean field to set"),
|
|
2164
|
+
value: boolCoerce(z10.boolean().optional()).describe(
|
|
2165
|
+
'Value for the field \u2014 must be a JSON boolean (true/false), NOT the string "true"/"false"'
|
|
2166
|
+
),
|
|
2167
|
+
status: z10.enum(["setup", "questions", "execution", "done"]).optional().describe("Top-level status override"),
|
|
2168
|
+
incrementRetry: boolCoerce(z10.boolean().optional()).describe(
|
|
2169
|
+
"Bump retryCount by 1 \u2014 must be a JSON boolean"
|
|
2170
|
+
)
|
|
2110
2171
|
},
|
|
2111
2172
|
async (args) => res(
|
|
2112
2173
|
handleSetPipelineState({
|
|
@@ -2134,30 +2195,30 @@ function registerPipelineTools(server2) {
|
|
|
2134
2195
|
"stackwright_pro_write_phase_questions",
|
|
2135
2196
|
`Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. ${DESC}`,
|
|
2136
2197
|
{
|
|
2137
|
-
phase:
|
|
2138
|
-
responseText:
|
|
2198
|
+
phase: z10.string().describe('Phase name, e.g. "designer"'),
|
|
2199
|
+
responseText: z10.string().describe("Raw LLM response from QUESTION_COLLECTION_MODE")
|
|
2139
2200
|
},
|
|
2140
2201
|
async ({ phase, responseText }) => res(handleWritePhaseQuestions({ phase, responseText }))
|
|
2141
2202
|
);
|
|
2142
2203
|
server2.tool(
|
|
2143
2204
|
"stackwright_pro_build_specialist_prompt",
|
|
2144
2205
|
`Assemble execution prompt from answers + upstream artifacts. Foreman passes verbatim. ${DESC}`,
|
|
2145
|
-
{ phase:
|
|
2206
|
+
{ phase: z10.string().describe('Phase to build prompt for, e.g. "pages"') },
|
|
2146
2207
|
async ({ phase }) => res(handleBuildSpecialistPrompt({ phase }))
|
|
2147
2208
|
);
|
|
2148
2209
|
server2.tool(
|
|
2149
2210
|
"stackwright_pro_validate_artifact",
|
|
2150
2211
|
`Validate specialist response + write artifact to .stackwright/artifacts/. Returns retryPrompt on failure. ${DESC}`,
|
|
2151
2212
|
{
|
|
2152
|
-
phase:
|
|
2153
|
-
responseText:
|
|
2213
|
+
phase: z10.string().describe('Phase that produced this artifact, e.g. "designer"'),
|
|
2214
|
+
responseText: z10.string().describe("Raw response text from the specialist otter")
|
|
2154
2215
|
},
|
|
2155
2216
|
async ({ phase, responseText }) => res(handleValidateArtifact({ phase, responseText }))
|
|
2156
2217
|
);
|
|
2157
2218
|
}
|
|
2158
2219
|
|
|
2159
2220
|
// src/tools/safe-write.ts
|
|
2160
|
-
import { z as
|
|
2221
|
+
import { z as z11 } from "zod";
|
|
2161
2222
|
import { writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3, lstatSync as lstatSync4 } from "fs";
|
|
2162
2223
|
import { normalize, isAbsolute, dirname, join as join4 } from "path";
|
|
2163
2224
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
@@ -2389,10 +2450,10 @@ function registerSafeWriteTools(server2) {
|
|
|
2389
2450
|
"stackwright_pro_safe_write",
|
|
2390
2451
|
DESC,
|
|
2391
2452
|
{
|
|
2392
|
-
callerOtter:
|
|
2393
|
-
filePath:
|
|
2394
|
-
content:
|
|
2395
|
-
createDirectories:
|
|
2453
|
+
callerOtter: z11.string().describe('The otter agent name requesting the write, e.g. "stackwright-pro-page-otter"'),
|
|
2454
|
+
filePath: z11.string().describe('Relative path from project root, e.g. "pages/dashboard/content.yml"'),
|
|
2455
|
+
content: z11.string().describe("File content to write"),
|
|
2456
|
+
createDirectories: z11.boolean().optional().describe("Create parent directories if they don't exist. Default: true")
|
|
2396
2457
|
},
|
|
2397
2458
|
async ({ callerOtter, filePath, content, createDirectories }) => {
|
|
2398
2459
|
const result = handleSafeWrite({
|
|
@@ -2407,7 +2468,7 @@ function registerSafeWriteTools(server2) {
|
|
|
2407
2468
|
}
|
|
2408
2469
|
|
|
2409
2470
|
// src/tools/auth.ts
|
|
2410
|
-
import { z as
|
|
2471
|
+
import { z as z12 } from "zod";
|
|
2411
2472
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync5 } from "fs";
|
|
2412
2473
|
import { join as join5 } from "path";
|
|
2413
2474
|
function buildHierarchy(roles) {
|
|
@@ -2762,35 +2823,35 @@ function registerAuthTools(server2) {
|
|
|
2762
2823
|
"stackwright_pro_configure_auth",
|
|
2763
2824
|
"Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` from a secure template, appends/updates the `auth:` section in `stackwright.yml`, and generates `.env.example` with required environment variables. \u26A0\uFE0F For CAC/PKI: generated `middleware.ts` carries a SECURITY REVIEW REQUIRED comment \u2014 certificate chain validation must be verified by a DoD security officer before production deployment. This is the ONLY approved path to generating `middleware.ts`. Never write TypeScript auth files directly.",
|
|
2764
2825
|
{
|
|
2765
|
-
method:
|
|
2766
|
-
provider:
|
|
2826
|
+
method: z12.enum(["cac", "oidc", "oauth2", "none"]),
|
|
2827
|
+
provider: z12.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
|
|
2767
2828
|
// CAC
|
|
2768
|
-
cacCaBundle:
|
|
2769
|
-
cacEdipiLookup:
|
|
2770
|
-
cacOcspEndpoint:
|
|
2771
|
-
cacCertHeader:
|
|
2829
|
+
cacCaBundle: z12.string().optional(),
|
|
2830
|
+
cacEdipiLookup: z12.string().optional(),
|
|
2831
|
+
cacOcspEndpoint: z12.string().optional(),
|
|
2832
|
+
cacCertHeader: z12.string().optional(),
|
|
2772
2833
|
// OIDC
|
|
2773
|
-
oidcDiscoveryUrl:
|
|
2774
|
-
oidcClientId:
|
|
2775
|
-
oidcClientSecret:
|
|
2776
|
-
oidcScopes:
|
|
2777
|
-
oidcRoleClaim:
|
|
2834
|
+
oidcDiscoveryUrl: z12.string().optional(),
|
|
2835
|
+
oidcClientId: z12.string().optional(),
|
|
2836
|
+
oidcClientSecret: z12.string().optional(),
|
|
2837
|
+
oidcScopes: z12.string().optional(),
|
|
2838
|
+
oidcRoleClaim: z12.string().optional(),
|
|
2778
2839
|
// OAuth2
|
|
2779
|
-
oauth2AuthUrl:
|
|
2780
|
-
oauth2TokenUrl:
|
|
2781
|
-
oauth2ClientId:
|
|
2782
|
-
oauth2ClientSecret:
|
|
2783
|
-
oauth2Scopes:
|
|
2840
|
+
oauth2AuthUrl: z12.string().optional(),
|
|
2841
|
+
oauth2TokenUrl: z12.string().optional(),
|
|
2842
|
+
oauth2ClientId: z12.string().optional(),
|
|
2843
|
+
oauth2ClientSecret: z12.string().optional(),
|
|
2844
|
+
oauth2Scopes: z12.string().optional(),
|
|
2784
2845
|
// RBAC
|
|
2785
|
-
rbacRoles:
|
|
2786
|
-
rbacDefaultRole:
|
|
2846
|
+
rbacRoles: jsonCoerce(z12.array(z12.string()).optional()),
|
|
2847
|
+
rbacDefaultRole: z12.string().optional(),
|
|
2787
2848
|
// Audit
|
|
2788
|
-
auditEnabled:
|
|
2789
|
-
auditRetentionDays:
|
|
2849
|
+
auditEnabled: boolCoerce(z12.boolean().optional()),
|
|
2850
|
+
auditRetentionDays: numCoerce(z12.number().int().positive().optional()),
|
|
2790
2851
|
// Routes
|
|
2791
|
-
protectedRoutes:
|
|
2852
|
+
protectedRoutes: jsonCoerce(z12.array(z12.string()).optional()),
|
|
2792
2853
|
// Injection for tests
|
|
2793
|
-
_cwd:
|
|
2854
|
+
_cwd: z12.string().optional()
|
|
2794
2855
|
},
|
|
2795
2856
|
async (params) => {
|
|
2796
2857
|
const cwd = params._cwd ?? process.cwd();
|
|
@@ -2826,7 +2887,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
2826
2887
|
],
|
|
2827
2888
|
[
|
|
2828
2889
|
"stackwright-pro-foreman-otter.json",
|
|
2829
|
-
"
|
|
2890
|
+
"f52264c1f6297b72f3da6d92d41b6d63356db776242caeab25571e6a8df628e4"
|
|
2830
2891
|
],
|
|
2831
2892
|
[
|
|
2832
2893
|
"stackwright-pro-page-otter.json",
|
|
@@ -3018,7 +3079,7 @@ function registerIntegrityTools(server2) {
|
|
|
3018
3079
|
}
|
|
3019
3080
|
|
|
3020
3081
|
// src/tools/domain.ts
|
|
3021
|
-
import { z as
|
|
3082
|
+
import { z as z13 } from "zod";
|
|
3022
3083
|
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
3023
3084
|
import { join as join7 } from "path";
|
|
3024
3085
|
function handleListCollections(input) {
|
|
@@ -3404,7 +3465,7 @@ function registerDomainTools(server2) {
|
|
|
3404
3465
|
"stackwright_pro_resolve_data_strategy",
|
|
3405
3466
|
"Look up the data freshness strategy configuration from the user's answer. Returns mechanism, revalidation seconds, required packages, and the exact MCP tool call to make. Replaces the strategy table in the data-otter prompt.",
|
|
3406
3467
|
{
|
|
3407
|
-
strategy:
|
|
3468
|
+
strategy: z13.string().describe(
|
|
3408
3469
|
'The data-1 answer value: "pulse-fast", "isr-fast", "isr-standard", or "isr-slow"'
|
|
3409
3470
|
)
|
|
3410
3471
|
},
|
|
@@ -3414,7 +3475,7 @@ function registerDomainTools(server2) {
|
|
|
3414
3475
|
"stackwright_pro_validate_workflow",
|
|
3415
3476
|
"Validate a workflow definition against the Stackwright workflow schema. Checks step ID uniqueness, transition targets, terminal state existence, and service references. Call this after the workflow otter produces output.",
|
|
3416
3477
|
{
|
|
3417
|
-
workflow:
|
|
3478
|
+
workflow: jsonCoerce(z13.record(z13.string(), z13.unknown()).optional()).describe(
|
|
3418
3479
|
"Parsed workflow object. If omitted, reads from .stackwright/artifacts/workflow-config.json"
|
|
3419
3480
|
)
|
|
3420
3481
|
},
|
|
@@ -3444,7 +3505,7 @@ var package_default = {
|
|
|
3444
3505
|
"test:coverage": "vitest run --coverage"
|
|
3445
3506
|
},
|
|
3446
3507
|
name: "@stackwright-pro/mcp",
|
|
3447
|
-
version: "0.2.0-alpha.
|
|
3508
|
+
version: "0.2.0-alpha.14",
|
|
3448
3509
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
3449
3510
|
license: "PROPRIETARY",
|
|
3450
3511
|
main: "./dist/server.js",
|