@stackwright-pro/mcp 0.2.0-alpha.60 → 0.2.0-alpha.66
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 +9 -9
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +9 -9
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +467 -42
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +475 -50
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -1678,6 +1678,14 @@ function handleSavePhaseAnswers(input) {
|
|
|
1678
1678
|
let answers;
|
|
1679
1679
|
if (input.questions && input.questions.length > 0) {
|
|
1680
1680
|
answers = answersToManifestFormat(input.rawAnswers, input.questions);
|
|
1681
|
+
if (Object.keys(answers).length === 0 && input.rawAnswers.length > 0) {
|
|
1682
|
+
answers = Object.fromEntries(
|
|
1683
|
+
input.rawAnswers.map((a) => [
|
|
1684
|
+
a.question_header,
|
|
1685
|
+
a.selected_options.length > 1 ? a.selected_options : a.selected_options[0] ?? ""
|
|
1686
|
+
])
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1681
1689
|
} else {
|
|
1682
1690
|
answers = Object.fromEntries(
|
|
1683
1691
|
input.rawAnswers.map((a) => [a.question_header, a.selected_options[0] ?? ""])
|
|
@@ -2277,12 +2285,13 @@ var PHASE_DEPENDENCIES = {
|
|
|
2277
2285
|
// workflow states as trigger sources. Produces OpenAPI specs consumed
|
|
2278
2286
|
// by pages and dashboard for typed data wiring.
|
|
2279
2287
|
services: ["api", "workflow"],
|
|
2280
|
-
// pages/dashboard:
|
|
2281
|
-
//
|
|
2282
|
-
//
|
|
2283
|
-
//
|
|
2284
|
-
|
|
2285
|
-
|
|
2288
|
+
// pages/dashboard: depend on services for typed endpoint wiring (OpenAPI
|
|
2289
|
+
// specs) and on geo so the specialist prompt includes geo-manifest.json
|
|
2290
|
+
// — page/dashboard otters see which page slugs are already claimed and
|
|
2291
|
+
// won't attempt to overwrite them (swp-73c). 'api' is still transitive
|
|
2292
|
+
// through 'data'; auth removed — page-otter has graceful fallback.
|
|
2293
|
+
pages: ["designer", "theme", "data", "services", "geo"],
|
|
2294
|
+
dashboard: ["designer", "theme", "data", "services", "geo"],
|
|
2286
2295
|
// auth is the penultimate phase — runs after all content-producing phases
|
|
2287
2296
|
// so it can read pages, dashboard, workflow, and geo artifacts for route
|
|
2288
2297
|
// protection and RBAC wiring. Skipped upstream phases still satisfy deps
|
|
@@ -3024,13 +3033,19 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3024
3033
|
null,
|
|
3025
3034
|
2
|
|
3026
3035
|
),
|
|
3036
|
+
// type: 'pki' = CAC/DoD certificate auth | 'oidc' = enterprise SSO
|
|
3037
|
+
// For dev-only mock auth: use type: 'oidc' with devOnly: true (Zod strips devOnly — it's a convention only)
|
|
3027
3038
|
auth: JSON.stringify(
|
|
3028
3039
|
{
|
|
3029
3040
|
version: "1.0",
|
|
3030
3041
|
generatedBy: "stackwright-pro-auth-otter",
|
|
3031
3042
|
authConfig: {
|
|
3032
|
-
|
|
3033
|
-
|
|
3043
|
+
type: "<pki|oidc>",
|
|
3044
|
+
// OIDC-only fields (omit for pki):
|
|
3045
|
+
provider: "<azure_ad|okta|cognito|auth0|authentik|keycloak|custom>",
|
|
3046
|
+
discoveryUrl: "<IdP OIDC discovery URL>",
|
|
3047
|
+
clientId: "<OIDC client ID>",
|
|
3048
|
+
clientSecret: "<OIDC client secret>",
|
|
3034
3049
|
rbacRoles: ["ADMIN", "ANALYST"],
|
|
3035
3050
|
rbacDefaultRole: "ANALYST",
|
|
3036
3051
|
protectedRoutes: ["/dashboard/:path*", "/procurement/:path*"],
|
|
@@ -3311,7 +3326,7 @@ function registerPipelineTools(server2) {
|
|
|
3311
3326
|
|
|
3312
3327
|
// src/tools/safe-write.ts
|
|
3313
3328
|
import { z as z12 } from "zod";
|
|
3314
|
-
import { writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync6 } from "fs";
|
|
3329
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync6 } from "fs";
|
|
3315
3330
|
import { normalize, isAbsolute, dirname, join as join5 } from "path";
|
|
3316
3331
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
3317
3332
|
"stackwright-pro-designer-otter": [
|
|
@@ -3333,6 +3348,16 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3333
3348
|
prefix: ".env",
|
|
3334
3349
|
suffix: "",
|
|
3335
3350
|
description: "Dotenv files (.env, .env.local, .env.production, etc.)"
|
|
3351
|
+
},
|
|
3352
|
+
{
|
|
3353
|
+
prefix: "lib/mock-auth",
|
|
3354
|
+
suffix: ".ts",
|
|
3355
|
+
description: "Mock auth module (DEV_ONLY_MODE role updates)"
|
|
3356
|
+
},
|
|
3357
|
+
{
|
|
3358
|
+
prefix: "package.json",
|
|
3359
|
+
suffix: ".json",
|
|
3360
|
+
description: "Project package.json (DEV_ONLY_MODE script cleanup)"
|
|
3336
3361
|
}
|
|
3337
3362
|
],
|
|
3338
3363
|
"stackwright-pro-data-otter": [
|
|
@@ -3371,6 +3396,16 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3371
3396
|
{ prefix: "pages/", suffix: "/content.yml", description: "Landing page content" },
|
|
3372
3397
|
{ prefix: "pages/", suffix: "/content.yaml", description: "Landing page content" },
|
|
3373
3398
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Polish artifact" }
|
|
3399
|
+
],
|
|
3400
|
+
"stackwright-services-otter": [
|
|
3401
|
+
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Services config artifact" },
|
|
3402
|
+
{ prefix: "services/", suffix: ".ts", description: "Service implementation files" },
|
|
3403
|
+
{ prefix: "services/", suffix: ".yaml", description: "Service flow definitions" },
|
|
3404
|
+
{ prefix: "services/", suffix: ".yml", description: "Service flow definitions" },
|
|
3405
|
+
{ prefix: "lib/seeds/", suffix: ".ts", description: "Seed data files" },
|
|
3406
|
+
{ prefix: "specs/", suffix: ".json", description: "Generated OpenAPI specs" },
|
|
3407
|
+
{ prefix: "specs/", suffix: ".yaml", description: "Generated OpenAPI specs" },
|
|
3408
|
+
{ prefix: "stackwright-generated/", suffix: ".json", description: "Services manifest" }
|
|
3374
3409
|
]
|
|
3375
3410
|
};
|
|
3376
3411
|
var PROTECTED_PATH_PREFIXES = [
|
|
@@ -3380,7 +3415,9 @@ var PROTECTED_PATH_PREFIXES = [
|
|
|
3380
3415
|
".stackwright/artifacts/signatures.json",
|
|
3381
3416
|
// artifact signature manifest
|
|
3382
3417
|
".stackwright/questions/",
|
|
3383
|
-
".stackwright/answers/"
|
|
3418
|
+
".stackwright/answers/",
|
|
3419
|
+
".stackwright/page-registry.json"
|
|
3420
|
+
// page ownership — managed by safe_write internally
|
|
3384
3421
|
];
|
|
3385
3422
|
var MAX_SAFE_WRITE_BYTES_JSON = 512 * 1024;
|
|
3386
3423
|
var MAX_SAFE_WRITE_BYTES_YAML = 256 * 1024;
|
|
@@ -3394,6 +3431,58 @@ function getMaxBytesForPath(filePath) {
|
|
|
3394
3431
|
return { limit: MAX_SAFE_WRITE_BYTES_ENV, label: "env" };
|
|
3395
3432
|
return { limit: MAX_SAFE_WRITE_BYTES_DEFAULT, label: "default" };
|
|
3396
3433
|
}
|
|
3434
|
+
var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
|
|
3435
|
+
function extractPageSlug(filePath) {
|
|
3436
|
+
const match = normalize(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
|
|
3437
|
+
return match?.[1] ?? null;
|
|
3438
|
+
}
|
|
3439
|
+
function extractContentTypes(yamlContent) {
|
|
3440
|
+
const typePattern = /^\s*-?\s*type:\s*(\S+)/gm;
|
|
3441
|
+
const types = [];
|
|
3442
|
+
let m;
|
|
3443
|
+
while ((m = typePattern.exec(yamlContent)) !== null) {
|
|
3444
|
+
const typeName = m[1];
|
|
3445
|
+
if (typeName && !types.includes(typeName)) {
|
|
3446
|
+
types.push(typeName);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
return types.length > 0 ? types : ["unknown"];
|
|
3450
|
+
}
|
|
3451
|
+
function readPageRegistry(cwd) {
|
|
3452
|
+
const regPath = join5(cwd, PAGE_REGISTRY_FILE);
|
|
3453
|
+
if (!existsSync6(regPath)) return {};
|
|
3454
|
+
try {
|
|
3455
|
+
const stat = lstatSync6(regPath);
|
|
3456
|
+
if (stat.isSymbolicLink()) return {};
|
|
3457
|
+
return JSON.parse(readFileSync5(regPath, "utf-8"));
|
|
3458
|
+
} catch {
|
|
3459
|
+
return {};
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
function writePageRegistry(cwd, registry) {
|
|
3463
|
+
const dir = join5(cwd, ".stackwright");
|
|
3464
|
+
mkdirSync5(dir, { recursive: true });
|
|
3465
|
+
const regPath = join5(cwd, PAGE_REGISTRY_FILE);
|
|
3466
|
+
if (existsSync6(regPath)) {
|
|
3467
|
+
const stat = lstatSync6(regPath);
|
|
3468
|
+
if (stat.isSymbolicLink()) {
|
|
3469
|
+
throw new Error("Refusing to write page registry through symlink");
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
writeFileSync5(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
|
|
3473
|
+
}
|
|
3474
|
+
function checkPageOwnership(cwd, slug, callerOtter) {
|
|
3475
|
+
const registry = readPageRegistry(cwd);
|
|
3476
|
+
const existing = registry[slug];
|
|
3477
|
+
if (!existing || existing.claimedBy === callerOtter) {
|
|
3478
|
+
return { allowed: true };
|
|
3479
|
+
}
|
|
3480
|
+
return {
|
|
3481
|
+
allowed: false,
|
|
3482
|
+
owner: existing.claimedBy,
|
|
3483
|
+
contentTypes: existing.contentTypes
|
|
3484
|
+
};
|
|
3485
|
+
}
|
|
3397
3486
|
function checkPathAllowed(callerOtter, filePath) {
|
|
3398
3487
|
const normalized = normalize(filePath);
|
|
3399
3488
|
if (normalized.includes("..")) {
|
|
@@ -3435,6 +3524,16 @@ function checkPathAllowed(callerOtter, filePath) {
|
|
|
3435
3524
|
continue;
|
|
3436
3525
|
}
|
|
3437
3526
|
}
|
|
3527
|
+
if (rule.prefix === "lib/mock-auth" && rule.suffix === ".ts") {
|
|
3528
|
+
if (normalized !== "lib/mock-auth.ts") {
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
if (rule.prefix === "package.json" && rule.suffix === ".json") {
|
|
3533
|
+
if (normalized !== "package.json") {
|
|
3534
|
+
continue;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3438
3537
|
return { allowed: true, rule: rule.description };
|
|
3439
3538
|
}
|
|
3440
3539
|
}
|
|
@@ -3560,11 +3659,38 @@ function handleSafeWrite(input) {
|
|
|
3560
3659
|
};
|
|
3561
3660
|
return { text: JSON.stringify(result), isError: true };
|
|
3562
3661
|
}
|
|
3662
|
+
const pageSlug = extractPageSlug(normalized);
|
|
3663
|
+
if (pageSlug) {
|
|
3664
|
+
const ownership = checkPageOwnership(cwd, pageSlug, callerOtter);
|
|
3665
|
+
if (!ownership.allowed) {
|
|
3666
|
+
const result = {
|
|
3667
|
+
success: false,
|
|
3668
|
+
error: `Page slug "${pageSlug}" is already claimed by ${ownership.owner} (content types: ${ownership.contentTypes.join(", ")}). Use a different slug or coordinate with the owning otter.`,
|
|
3669
|
+
callerOtter,
|
|
3670
|
+
attemptedPath: filePath,
|
|
3671
|
+
allowedPaths: []
|
|
3672
|
+
};
|
|
3673
|
+
return { text: JSON.stringify(result), isError: true };
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3563
3676
|
try {
|
|
3564
3677
|
if (createDirectories) {
|
|
3565
3678
|
mkdirSync5(dirname(fullPath), { recursive: true });
|
|
3566
3679
|
}
|
|
3567
3680
|
writeFileSync5(fullPath, content, { encoding: "utf-8" });
|
|
3681
|
+
if (pageSlug) {
|
|
3682
|
+
try {
|
|
3683
|
+
const registry = readPageRegistry(cwd);
|
|
3684
|
+
registry[pageSlug] = {
|
|
3685
|
+
slug: pageSlug,
|
|
3686
|
+
claimedBy: callerOtter,
|
|
3687
|
+
contentTypes: extractContentTypes(content),
|
|
3688
|
+
writtenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3689
|
+
};
|
|
3690
|
+
writePageRegistry(cwd, registry);
|
|
3691
|
+
} catch {
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3568
3694
|
const result = {
|
|
3569
3695
|
success: true,
|
|
3570
3696
|
path: normalized,
|
|
@@ -3611,7 +3737,7 @@ function registerSafeWriteTools(server2) {
|
|
|
3611
3737
|
|
|
3612
3738
|
// src/tools/auth.ts
|
|
3613
3739
|
import { z as z13 } from "zod";
|
|
3614
|
-
import { readFileSync as
|
|
3740
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
3615
3741
|
import { join as join6 } from "path";
|
|
3616
3742
|
function buildHierarchy(roles) {
|
|
3617
3743
|
const h = {};
|
|
@@ -3831,10 +3957,232 @@ ${routeLines}
|
|
|
3831
3957
|
${auditSection}
|
|
3832
3958
|
`;
|
|
3833
3959
|
}
|
|
3960
|
+
function generateDevOnlyMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
3961
|
+
const rbacBlock = ` rbac: {
|
|
3962
|
+
roles: ${JSON.stringify(roles)},
|
|
3963
|
+
defaultRole: '${defaultRole}',
|
|
3964
|
+
hierarchy: ${JSON.stringify(hierarchy, null, 4)},
|
|
3965
|
+
},`;
|
|
3966
|
+
const auditBlock = ` audit: {
|
|
3967
|
+
enabled: ${auditEnabled},
|
|
3968
|
+
retentionDays: ${auditRetentionDays},
|
|
3969
|
+
},`;
|
|
3970
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(protectedRoutes)},`;
|
|
3971
|
+
const configBlock = `export const config = {
|
|
3972
|
+
matcher: ${JSON.stringify(protectedRoutes)},
|
|
3973
|
+
};`;
|
|
3974
|
+
const devHeader = [
|
|
3975
|
+
"// middleware.ts \u2014 generated by @stackwright-pro/auth-nextjs (dev-only mock)",
|
|
3976
|
+
"// DEV ONLY \u2014 uses mock authentication from lib/mock-auth.ts",
|
|
3977
|
+
"// Do NOT deploy to production.",
|
|
3978
|
+
"import { createProMiddleware } from '@stackwright-pro/auth-nextjs';",
|
|
3979
|
+
"import { mockAuthProvider } from './lib/mock-auth';"
|
|
3980
|
+
].join("\n");
|
|
3981
|
+
if (method === "cac") {
|
|
3982
|
+
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
3983
|
+
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
3984
|
+
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
3985
|
+
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
3986
|
+
return `${devHeader}
|
|
3987
|
+
|
|
3988
|
+
export const middleware = createProMiddleware({
|
|
3989
|
+
method: 'cac',
|
|
3990
|
+
cac: {
|
|
3991
|
+
caBundle: '${caBundle}',
|
|
3992
|
+
edipiLookup: '${edipiLookup}',
|
|
3993
|
+
ocspEndpoint: '${ocspEndpoint}',
|
|
3994
|
+
certHeader: '${certHeader}',
|
|
3995
|
+
provider: mockAuthProvider,
|
|
3996
|
+
},
|
|
3997
|
+
${rbacBlock}
|
|
3998
|
+
${auditBlock}
|
|
3999
|
+
${routesBlock}
|
|
4000
|
+
});
|
|
4001
|
+
|
|
4002
|
+
${configBlock}
|
|
4003
|
+
`;
|
|
4004
|
+
}
|
|
4005
|
+
if (method === "oidc") {
|
|
4006
|
+
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4007
|
+
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4008
|
+
return `${devHeader}
|
|
4009
|
+
|
|
4010
|
+
export const middleware = createProMiddleware({
|
|
4011
|
+
method: 'oidc',
|
|
4012
|
+
oidc: {
|
|
4013
|
+
discoveryUrl: 'https://dev-mock-oidc/.well-known/openid-configuration',
|
|
4014
|
+
clientId: 'dev-mock-client',
|
|
4015
|
+
clientSecret: 'dev-mock-secret',
|
|
4016
|
+
scopes: '${scopes2}',
|
|
4017
|
+
roleClaim: '${roleClaim}',
|
|
4018
|
+
provider: mockAuthProvider,
|
|
4019
|
+
},
|
|
4020
|
+
${rbacBlock}
|
|
4021
|
+
${auditBlock}
|
|
4022
|
+
${routesBlock}
|
|
4023
|
+
});
|
|
4024
|
+
|
|
4025
|
+
${configBlock}
|
|
4026
|
+
`;
|
|
4027
|
+
}
|
|
4028
|
+
const scopes = params.oauth2Scopes ?? "read write";
|
|
4029
|
+
return `${devHeader}
|
|
4030
|
+
|
|
4031
|
+
export const middleware = createProMiddleware({
|
|
4032
|
+
method: 'oauth2',
|
|
4033
|
+
oauth2: {
|
|
4034
|
+
authorizationUrl: 'https://dev-mock-oauth2/authorize',
|
|
4035
|
+
tokenUrl: 'https://dev-mock-oauth2/token',
|
|
4036
|
+
clientId: 'dev-mock-client',
|
|
4037
|
+
clientSecret: 'dev-mock-secret',
|
|
4038
|
+
scopes: '${scopes}',
|
|
4039
|
+
provider: mockAuthProvider,
|
|
4040
|
+
},
|
|
4041
|
+
${rbacBlock}
|
|
4042
|
+
${auditBlock}
|
|
4043
|
+
${routesBlock}
|
|
4044
|
+
});
|
|
4045
|
+
|
|
4046
|
+
${configBlock}
|
|
4047
|
+
`;
|
|
4048
|
+
}
|
|
4049
|
+
function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4050
|
+
const rbacSection = ` rbac:
|
|
4051
|
+
roles:
|
|
4052
|
+
${rolesToYaml(roles, " ")}
|
|
4053
|
+
defaultRole: ${defaultRole}
|
|
4054
|
+
hierarchy:
|
|
4055
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
4056
|
+
const auditSection = ` audit:
|
|
4057
|
+
enabled: ${auditEnabled}
|
|
4058
|
+
retentionDays: ${auditRetentionDays}`;
|
|
4059
|
+
const routeLines = protectedRoutes.map((r) => ` - pattern: ${r}
|
|
4060
|
+
requiredRole: ${defaultRole}`).join("\n");
|
|
4061
|
+
const providerLine = params.provider ? ` provider: ${params.provider}
|
|
4062
|
+
` : "";
|
|
4063
|
+
if (method === "cac") {
|
|
4064
|
+
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4065
|
+
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4066
|
+
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4067
|
+
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4068
|
+
return `auth:
|
|
4069
|
+
method: cac
|
|
4070
|
+
devOnly: true
|
|
4071
|
+
${providerLine} middleware: ./middleware.ts
|
|
4072
|
+
cac:
|
|
4073
|
+
caBundle: ${caBundle}
|
|
4074
|
+
edipiLookup: ${edipiLookup}
|
|
4075
|
+
ocspEndpoint: ${ocspEndpoint}
|
|
4076
|
+
certHeader: ${certHeader}
|
|
4077
|
+
${rbacSection}
|
|
4078
|
+
protectedRoutes:
|
|
4079
|
+
${routeLines}
|
|
4080
|
+
${auditSection}
|
|
4081
|
+
`;
|
|
4082
|
+
}
|
|
4083
|
+
if (method === "oidc") {
|
|
4084
|
+
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4085
|
+
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4086
|
+
return `auth:
|
|
4087
|
+
method: oidc
|
|
4088
|
+
devOnly: true
|
|
4089
|
+
${providerLine} middleware: ./middleware.ts
|
|
4090
|
+
oidc:
|
|
4091
|
+
discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
|
|
4092
|
+
clientId: dev-mock-client
|
|
4093
|
+
clientSecret: dev-mock-secret
|
|
4094
|
+
scopes: ${scopes2}
|
|
4095
|
+
roleClaim: ${roleClaim}
|
|
4096
|
+
${rbacSection}
|
|
4097
|
+
protectedRoutes:
|
|
4098
|
+
${routeLines}
|
|
4099
|
+
${auditSection}
|
|
4100
|
+
`;
|
|
4101
|
+
}
|
|
4102
|
+
const scopes = params.oauth2Scopes ?? "read write";
|
|
4103
|
+
return `auth:
|
|
4104
|
+
method: oauth2
|
|
4105
|
+
devOnly: true
|
|
4106
|
+
${providerLine} middleware: ./middleware.ts
|
|
4107
|
+
oauth2:
|
|
4108
|
+
authorizationUrl: https://dev-mock-oauth2/authorize
|
|
4109
|
+
tokenUrl: https://dev-mock-oauth2/token
|
|
4110
|
+
clientId: dev-mock-client
|
|
4111
|
+
clientSecret: dev-mock-secret
|
|
4112
|
+
scopes: ${scopes}
|
|
4113
|
+
${rbacSection}
|
|
4114
|
+
protectedRoutes:
|
|
4115
|
+
${routeLines}
|
|
4116
|
+
${auditSection}
|
|
4117
|
+
`;
|
|
4118
|
+
}
|
|
4119
|
+
function deriveDevKey(roleName) {
|
|
4120
|
+
const segment = roleName.split("_")[0];
|
|
4121
|
+
return (segment ?? roleName).toLowerCase();
|
|
4122
|
+
}
|
|
4123
|
+
function generateMockAuthContent(roles, mockUsers) {
|
|
4124
|
+
const entries = [];
|
|
4125
|
+
const scriptLines = [];
|
|
4126
|
+
for (let i = 0; i < roles.length; i++) {
|
|
4127
|
+
const role = roles[i];
|
|
4128
|
+
const devKey = deriveDevKey(role);
|
|
4129
|
+
const persona = mockUsers?.[i];
|
|
4130
|
+
const name = persona?.name ?? `Dev ${role}`;
|
|
4131
|
+
const email = persona?.email ?? `dev-${devKey}@example.mil`;
|
|
4132
|
+
const edipi = String(i + 1).padStart(10, "0");
|
|
4133
|
+
entries.push(` ${devKey}: {
|
|
4134
|
+
id: 'mock-${devKey}-${String(i + 1).padStart(3, "0")}',
|
|
4135
|
+
name: '${name}',
|
|
4136
|
+
email: '${email}',
|
|
4137
|
+
roles: ['${role}'],
|
|
4138
|
+
edipi: '${edipi}',
|
|
4139
|
+
}`);
|
|
4140
|
+
scriptLines.push(` * pnpm dev:${devKey} \u2014 ${name}`);
|
|
4141
|
+
}
|
|
4142
|
+
return `/**
|
|
4143
|
+
* Mock authentication for development mode.
|
|
4144
|
+
*
|
|
4145
|
+
* DEV_ONLY_MODE \u2014 no real auth provider. Select a persona via MOCK_USER env var
|
|
4146
|
+
* or use the convenience scripts in package.json:
|
|
4147
|
+
${scriptLines.join("\n")}
|
|
4148
|
+
*/
|
|
4149
|
+
|
|
4150
|
+
export const MOCK_USERS = {
|
|
4151
|
+
${entries.join(",\n")}
|
|
4152
|
+
} as const;
|
|
4153
|
+
|
|
4154
|
+
export type MockRole = keyof typeof MOCK_USERS;
|
|
4155
|
+
|
|
4156
|
+
export function getMockUser(role?: string) {
|
|
4157
|
+
const key = (role ?? process.env.MOCK_USER) as MockRole | undefined;
|
|
4158
|
+
if (!key) return null;
|
|
4159
|
+
return MOCK_USERS[key] ?? null;
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
export function mockAuthProvider() {
|
|
4163
|
+
return getMockUser();
|
|
4164
|
+
}
|
|
4165
|
+
`;
|
|
4166
|
+
}
|
|
4167
|
+
function updatePackageJsonScripts(existingJson, roles, mockUsers) {
|
|
4168
|
+
const pkg = JSON.parse(existingJson);
|
|
4169
|
+
const scripts = pkg.scripts ?? {};
|
|
4170
|
+
delete scripts["dev:admin"];
|
|
4171
|
+
delete scripts["dev:analyst"];
|
|
4172
|
+
delete scripts["dev:viewer"];
|
|
4173
|
+
for (let i = 0; i < roles.length; i++) {
|
|
4174
|
+
const devKey = deriveDevKey(roles[i]);
|
|
4175
|
+
scripts[`dev:${devKey}`] = `MOCK_USER=${devKey} next dev`;
|
|
4176
|
+
}
|
|
4177
|
+
pkg.scripts = scripts;
|
|
4178
|
+
return JSON.stringify(pkg, null, 2) + "\n";
|
|
4179
|
+
}
|
|
3834
4180
|
async function configureAuthHandler(params, cwd) {
|
|
3835
4181
|
const {
|
|
3836
4182
|
method,
|
|
3837
4183
|
provider,
|
|
4184
|
+
devOnly = false,
|
|
4185
|
+
mockUsers,
|
|
3838
4186
|
rbacRoles = ["SUPER_ADMIN", "ADMIN", "ANALYST"],
|
|
3839
4187
|
auditEnabled = true,
|
|
3840
4188
|
auditRetentionDays = 90,
|
|
@@ -3864,7 +4212,16 @@ async function configureAuthHandler(params, cwd) {
|
|
|
3864
4212
|
}
|
|
3865
4213
|
const filesWritten = [];
|
|
3866
4214
|
try {
|
|
3867
|
-
const middlewareContent =
|
|
4215
|
+
const middlewareContent = devOnly ? generateDevOnlyMiddlewareContent(
|
|
4216
|
+
method,
|
|
4217
|
+
params,
|
|
4218
|
+
roles,
|
|
4219
|
+
defaultRole,
|
|
4220
|
+
hierarchy,
|
|
4221
|
+
auditEnabled,
|
|
4222
|
+
auditRetentionDays,
|
|
4223
|
+
protectedRoutes
|
|
4224
|
+
) : generateMiddlewareContent(
|
|
3868
4225
|
method,
|
|
3869
4226
|
params,
|
|
3870
4227
|
roles,
|
|
@@ -3888,30 +4245,87 @@ async function configureAuthHandler(params, cwd) {
|
|
|
3888
4245
|
isError: true
|
|
3889
4246
|
};
|
|
3890
4247
|
}
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
4248
|
+
if (!devOnly) {
|
|
4249
|
+
try {
|
|
4250
|
+
const envBlock = generateEnvBlock(method, params);
|
|
4251
|
+
const envPath = join6(cwd, ".env.example");
|
|
4252
|
+
if (existsSync7(envPath)) {
|
|
4253
|
+
const existing = readFileSync6(envPath, "utf8");
|
|
4254
|
+
writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
4255
|
+
} else {
|
|
4256
|
+
writeFileSync6(envPath, envBlock, "utf8");
|
|
4257
|
+
}
|
|
4258
|
+
filesWritten.push(".env.example");
|
|
4259
|
+
} catch (err) {
|
|
4260
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4261
|
+
return {
|
|
4262
|
+
content: [
|
|
4263
|
+
{
|
|
4264
|
+
type: "text",
|
|
4265
|
+
text: JSON.stringify({ success: false, error: `Failed writing .env.example: ${msg}` })
|
|
4266
|
+
}
|
|
4267
|
+
],
|
|
4268
|
+
isError: true
|
|
4269
|
+
};
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
if (devOnly) {
|
|
4273
|
+
try {
|
|
4274
|
+
const mockAuthDir = join6(cwd, "lib");
|
|
4275
|
+
mkdirSync6(mockAuthDir, { recursive: true });
|
|
4276
|
+
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
4277
|
+
writeFileSync6(join6(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
4278
|
+
filesWritten.push("lib/mock-auth.ts");
|
|
4279
|
+
} catch (err) {
|
|
4280
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4281
|
+
return {
|
|
4282
|
+
content: [
|
|
4283
|
+
{
|
|
4284
|
+
type: "text",
|
|
4285
|
+
text: JSON.stringify({
|
|
4286
|
+
success: false,
|
|
4287
|
+
error: `Failed writing lib/mock-auth.ts: ${msg}`
|
|
4288
|
+
})
|
|
4289
|
+
}
|
|
4290
|
+
],
|
|
4291
|
+
isError: true
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
try {
|
|
4295
|
+
const pkgPath = join6(cwd, "package.json");
|
|
4296
|
+
if (existsSync7(pkgPath)) {
|
|
4297
|
+
const existingPkg = readFileSync6(pkgPath, "utf8");
|
|
4298
|
+
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
4299
|
+
writeFileSync6(pkgPath, updatedPkg, "utf8");
|
|
4300
|
+
filesWritten.push("package.json");
|
|
4301
|
+
}
|
|
4302
|
+
} catch (err) {
|
|
4303
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4304
|
+
return {
|
|
4305
|
+
content: [
|
|
4306
|
+
{
|
|
4307
|
+
type: "text",
|
|
4308
|
+
text: JSON.stringify({
|
|
4309
|
+
success: false,
|
|
4310
|
+
error: `Failed updating package.json: ${msg}`
|
|
4311
|
+
})
|
|
4312
|
+
}
|
|
4313
|
+
],
|
|
4314
|
+
isError: true
|
|
4315
|
+
};
|
|
3899
4316
|
}
|
|
3900
|
-
filesWritten.push(".env.example");
|
|
3901
|
-
} catch (err) {
|
|
3902
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3903
|
-
return {
|
|
3904
|
-
content: [
|
|
3905
|
-
{
|
|
3906
|
-
type: "text",
|
|
3907
|
-
text: JSON.stringify({ success: false, error: `Failed writing .env.example: ${msg}` })
|
|
3908
|
-
}
|
|
3909
|
-
],
|
|
3910
|
-
isError: true
|
|
3911
|
-
};
|
|
3912
4317
|
}
|
|
3913
4318
|
try {
|
|
3914
|
-
const authYaml =
|
|
4319
|
+
const authYaml = devOnly ? generateDevOnlyYamlBlock(
|
|
4320
|
+
method,
|
|
4321
|
+
params,
|
|
4322
|
+
roles,
|
|
4323
|
+
defaultRole,
|
|
4324
|
+
hierarchy,
|
|
4325
|
+
auditEnabled,
|
|
4326
|
+
auditRetentionDays,
|
|
4327
|
+
protectedRoutes
|
|
4328
|
+
) : generateYamlBlock(
|
|
3915
4329
|
method,
|
|
3916
4330
|
params,
|
|
3917
4331
|
roles,
|
|
@@ -3925,7 +4339,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
3925
4339
|
if (!existsSync7(ymlPath)) {
|
|
3926
4340
|
writeFileSync6(ymlPath, authYaml, "utf8");
|
|
3927
4341
|
} else {
|
|
3928
|
-
const existing =
|
|
4342
|
+
const existing = readFileSync6(ymlPath, "utf8");
|
|
3929
4343
|
writeFileSync6(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
|
|
3930
4344
|
}
|
|
3931
4345
|
filesWritten.push("stackwright.yml");
|
|
@@ -3993,7 +4407,9 @@ function registerAuthTools(server2) {
|
|
|
3993
4407
|
// Routes
|
|
3994
4408
|
protectedRoutes: jsonCoerce(z13.array(z13.string()).optional()),
|
|
3995
4409
|
// Injection for tests
|
|
3996
|
-
_cwd: z13.string().optional()
|
|
4410
|
+
_cwd: z13.string().optional(),
|
|
4411
|
+
devOnly: boolCoerce(z13.boolean().optional()),
|
|
4412
|
+
mockUsers: jsonCoerce(z13.array(z13.object({ name: z13.string(), email: z13.string() })).optional())
|
|
3997
4413
|
},
|
|
3998
4414
|
async (params) => {
|
|
3999
4415
|
const cwd = params._cwd ?? process.cwd();
|
|
@@ -4004,7 +4420,7 @@ function registerAuthTools(server2) {
|
|
|
4004
4420
|
|
|
4005
4421
|
// src/integrity.ts
|
|
4006
4422
|
import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
4007
|
-
import { readFileSync as
|
|
4423
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync2, lstatSync as lstatSync7 } from "fs";
|
|
4008
4424
|
import { join as join7, basename } from "path";
|
|
4009
4425
|
var _checksums = /* @__PURE__ */ new Map([
|
|
4010
4426
|
[
|
|
@@ -4013,15 +4429,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4013
4429
|
],
|
|
4014
4430
|
[
|
|
4015
4431
|
"stackwright-pro-auth-otter.json",
|
|
4016
|
-
"
|
|
4432
|
+
"4bf6beba7150d08c74c5f6fbbeb20e988aba52a2029ff2892615e71f6ab12ed1"
|
|
4017
4433
|
],
|
|
4018
4434
|
[
|
|
4019
4435
|
"stackwright-pro-dashboard-otter.json",
|
|
4020
|
-
"
|
|
4436
|
+
"9c319d311801730e8dc9bc142eebb8fc5a7f48da48fa0b8d8c3b7431652447be"
|
|
4021
4437
|
],
|
|
4022
4438
|
[
|
|
4023
4439
|
"stackwright-pro-data-otter.json",
|
|
4024
|
-
"
|
|
4440
|
+
"4d9369277685a4acc484116920c9622ad8a1838012a493fcfe42a6ae5abe53cf"
|
|
4025
4441
|
],
|
|
4026
4442
|
[
|
|
4027
4443
|
"stackwright-pro-designer-otter.json",
|
|
@@ -4029,23 +4445,23 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4029
4445
|
],
|
|
4030
4446
|
[
|
|
4031
4447
|
"stackwright-pro-domain-expert-otter.json",
|
|
4032
|
-
"
|
|
4448
|
+
"6055a2efc78f54a8393f628839e2a2563bf0c6de3ad32de00c82779a53381efd"
|
|
4033
4449
|
],
|
|
4034
4450
|
[
|
|
4035
4451
|
"stackwright-pro-foreman-otter.json",
|
|
4036
|
-
"
|
|
4452
|
+
"ab38ef53b95ec610a38b2866d78a135cbec16d257a9b35d7e46e2fee2d4de235"
|
|
4037
4453
|
],
|
|
4038
4454
|
[
|
|
4039
4455
|
"stackwright-pro-geo-otter.json",
|
|
4040
|
-
"
|
|
4456
|
+
"9e09aaf2bb10197c6d1c05d0fd5f5f9380acc0cb697a410fcae839ffba648561"
|
|
4041
4457
|
],
|
|
4042
4458
|
[
|
|
4043
4459
|
"stackwright-pro-page-otter.json",
|
|
4044
|
-
"
|
|
4460
|
+
"532bb7e9a25a5c832edd1ff1ea0886dd4453905d86e6f9331eb957ae5e121833"
|
|
4045
4461
|
],
|
|
4046
4462
|
[
|
|
4047
4463
|
"stackwright-pro-polish-otter.json",
|
|
4048
|
-
"
|
|
4464
|
+
"8f284d4d6a204137cd786824fc584d5bddac1bc757204769b99ca5412cf2cea2"
|
|
4049
4465
|
],
|
|
4050
4466
|
[
|
|
4051
4467
|
"stackwright-pro-theme-otter.json",
|
|
@@ -4057,7 +4473,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4057
4473
|
],
|
|
4058
4474
|
[
|
|
4059
4475
|
"stackwright-services-otter.json",
|
|
4060
|
-
"
|
|
4476
|
+
"4893a596d187110124f78336ee91184a51b3c8d980c455382fe481adb9b487b5"
|
|
4061
4477
|
]
|
|
4062
4478
|
]);
|
|
4063
4479
|
Object.freeze(_checksums);
|
|
@@ -4104,7 +4520,7 @@ function verifyOtterFile(filePath) {
|
|
|
4104
4520
|
}
|
|
4105
4521
|
let raw;
|
|
4106
4522
|
try {
|
|
4107
|
-
raw =
|
|
4523
|
+
raw = readFileSync7(filePath);
|
|
4108
4524
|
} catch (err) {
|
|
4109
4525
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4110
4526
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -4274,7 +4690,7 @@ function registerIntegrityTools(server2) {
|
|
|
4274
4690
|
|
|
4275
4691
|
// src/tools/domain.ts
|
|
4276
4692
|
import { z as z14 } from "zod";
|
|
4277
|
-
import { readFileSync as
|
|
4693
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
4278
4694
|
import { join as join8 } from "path";
|
|
4279
4695
|
function handleListCollections(input) {
|
|
4280
4696
|
const cwd = input._cwd ?? process.cwd();
|
|
@@ -4299,7 +4715,7 @@ function handleListCollections(input) {
|
|
|
4299
4715
|
for (const { path: path3, source, parse } of sources) {
|
|
4300
4716
|
if (!existsSync8(path3)) continue;
|
|
4301
4717
|
try {
|
|
4302
|
-
const collections = parse(
|
|
4718
|
+
const collections = parse(readFileSync8(path3, "utf8"));
|
|
4303
4719
|
return {
|
|
4304
4720
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
4305
4721
|
isError: false
|
|
@@ -4460,7 +4876,7 @@ function handleValidateWorkflow(input) {
|
|
|
4460
4876
|
]);
|
|
4461
4877
|
}
|
|
4462
4878
|
try {
|
|
4463
|
-
raw = JSON.parse(
|
|
4879
|
+
raw = JSON.parse(readFileSync8(artifactPath, "utf8"));
|
|
4464
4880
|
} catch (err) {
|
|
4465
4881
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
4466
4882
|
}
|
|
@@ -4790,7 +5206,7 @@ var package_default = {
|
|
|
4790
5206
|
"test:coverage": "vitest run --coverage"
|
|
4791
5207
|
},
|
|
4792
5208
|
name: "@stackwright-pro/mcp",
|
|
4793
|
-
version: "0.2.0-alpha.
|
|
5209
|
+
version: "0.2.0-alpha.61",
|
|
4794
5210
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
4795
5211
|
license: "SEE LICENSE IN LICENSE",
|
|
4796
5212
|
main: "./dist/server.js",
|
|
@@ -4846,6 +5262,15 @@ registerDomainTools(server);
|
|
|
4846
5262
|
registerTypeSchemasTool(server);
|
|
4847
5263
|
async function main() {
|
|
4848
5264
|
const transport = new StdioServerTransport();
|
|
5265
|
+
try {
|
|
5266
|
+
const servicesRegisterPkg = "@stackwright-services/mcp/register";
|
|
5267
|
+
const mod = await import(servicesRegisterPkg);
|
|
5268
|
+
if (typeof mod.registerServicesTools === "function") {
|
|
5269
|
+
mod.registerServicesTools(server);
|
|
5270
|
+
console.error("Stackwright Services tools registered on pro MCP");
|
|
5271
|
+
}
|
|
5272
|
+
} catch {
|
|
5273
|
+
}
|
|
4849
5274
|
await server.connect(transport);
|
|
4850
5275
|
console.error("Stackwright Pro MCP server running on stdio");
|
|
4851
5276
|
}
|