@stackwright-pro/mcp 0.2.0-alpha.68 → 0.2.0-alpha.71
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 +6 -6
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +6 -6
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +471 -22
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +471 -22
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -3037,6 +3037,13 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3037
3037
|
mutationType: null
|
|
3038
3038
|
}
|
|
3039
3039
|
],
|
|
3040
|
+
skipped: [
|
|
3041
|
+
{
|
|
3042
|
+
spec: "ais-message-models.yaml",
|
|
3043
|
+
format: "asyncapi",
|
|
3044
|
+
reason: "AsyncAPI spec \u2014 WebSocket/event integration required (no REST endpoints)"
|
|
3045
|
+
}
|
|
3046
|
+
],
|
|
3040
3047
|
auth: { type: "bearer", header: "Authorization", envVar: "API_TOKEN" },
|
|
3041
3048
|
baseUrl: "https://api.example.mil/v2",
|
|
3042
3049
|
specPath: "./specs/api.yaml"
|
|
@@ -4009,6 +4016,91 @@ ${auditBlock}
|
|
|
4009
4016
|
${routesBlock}
|
|
4010
4017
|
});
|
|
4011
4018
|
|
|
4019
|
+
${configBlock}
|
|
4020
|
+
`;
|
|
4021
|
+
}
|
|
4022
|
+
function generateProxyContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4023
|
+
const rbacBlock = ` rbac: {
|
|
4024
|
+
roles: ${JSON.stringify(roles)},
|
|
4025
|
+
defaultRole: '${defaultRole}',
|
|
4026
|
+
hierarchy: ${JSON.stringify(hierarchy, null, 4)},
|
|
4027
|
+
},`;
|
|
4028
|
+
const auditBlock = ` audit: {
|
|
4029
|
+
enabled: ${auditEnabled},
|
|
4030
|
+
retentionDays: ${auditRetentionDays},
|
|
4031
|
+
},`;
|
|
4032
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(protectedRoutes)},`;
|
|
4033
|
+
const configBlock = `export const config = {
|
|
4034
|
+
matcher: ${JSON.stringify(protectedRoutes)},
|
|
4035
|
+
};`;
|
|
4036
|
+
if (method === "cac") {
|
|
4037
|
+
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4038
|
+
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4039
|
+
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4040
|
+
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4041
|
+
return `// proxy.ts \u2014 generated by @stackwright-pro/auth (Next.js >=16)
|
|
4042
|
+
// SECURITY REVIEW REQUIRED \u2014 CAC/PKI certificate validation
|
|
4043
|
+
// DoD security officer review required before production deployment.
|
|
4044
|
+
// Verify: CA bundle completeness, EDIPI lookup endpoint, OCSP accessibility.
|
|
4045
|
+
import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
4046
|
+
|
|
4047
|
+
export const proxy = createAuthProxy({
|
|
4048
|
+
method: 'cac',
|
|
4049
|
+
cac: {
|
|
4050
|
+
caBundle: process.env.CAC_CA_BUNDLE ?? '${caBundle}',
|
|
4051
|
+
edipiLookup: '${edipiLookup}',
|
|
4052
|
+
ocspEndpoint: process.env.CAC_OCSP_ENDPOINT ?? '${ocspEndpoint}',
|
|
4053
|
+
certHeader: '${certHeader}',
|
|
4054
|
+
},
|
|
4055
|
+
${rbacBlock}
|
|
4056
|
+
${auditBlock}
|
|
4057
|
+
${routesBlock}
|
|
4058
|
+
});
|
|
4059
|
+
|
|
4060
|
+
${configBlock}
|
|
4061
|
+
`;
|
|
4062
|
+
}
|
|
4063
|
+
if (method === "oidc") {
|
|
4064
|
+
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4065
|
+
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4066
|
+
return `// proxy.ts \u2014 generated by @stackwright-pro/auth (Next.js >=16)
|
|
4067
|
+
import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
4068
|
+
|
|
4069
|
+
export const proxy = createAuthProxy({
|
|
4070
|
+
method: 'oidc',
|
|
4071
|
+
oidc: {
|
|
4072
|
+
discoveryUrl: process.env.OIDC_DISCOVERY_URL!,
|
|
4073
|
+
clientId: process.env.OIDC_CLIENT_ID!,
|
|
4074
|
+
clientSecret: process.env.OIDC_CLIENT_SECRET!,
|
|
4075
|
+
scopes: '${scopes2}',
|
|
4076
|
+
roleClaim: '${roleClaim}',
|
|
4077
|
+
},
|
|
4078
|
+
${rbacBlock}
|
|
4079
|
+
${auditBlock}
|
|
4080
|
+
${routesBlock}
|
|
4081
|
+
});
|
|
4082
|
+
|
|
4083
|
+
${configBlock}
|
|
4084
|
+
`;
|
|
4085
|
+
}
|
|
4086
|
+
const scopes = params.oauth2Scopes ?? "read write";
|
|
4087
|
+
return `// proxy.ts \u2014 generated by @stackwright-pro/auth (Next.js >=16)
|
|
4088
|
+
import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
4089
|
+
|
|
4090
|
+
export const proxy = createAuthProxy({
|
|
4091
|
+
method: 'oauth2',
|
|
4092
|
+
oauth2: {
|
|
4093
|
+
authorizationUrl: process.env.OAUTH2_AUTH_URL!,
|
|
4094
|
+
tokenUrl: process.env.OAUTH2_TOKEN_URL!,
|
|
4095
|
+
clientId: process.env.OAUTH2_CLIENT_ID!,
|
|
4096
|
+
clientSecret: process.env.OAUTH2_CLIENT_SECRET!,
|
|
4097
|
+
scopes: '${scopes}',
|
|
4098
|
+
},
|
|
4099
|
+
${rbacBlock}
|
|
4100
|
+
${auditBlock}
|
|
4101
|
+
${routesBlock}
|
|
4102
|
+
});
|
|
4103
|
+
|
|
4012
4104
|
${configBlock}
|
|
4013
4105
|
`;
|
|
4014
4106
|
}
|
|
@@ -4038,7 +4130,7 @@ OAUTH2_CLIENT_ID=your-client-id
|
|
|
4038
4130
|
OAUTH2_CLIENT_SECRET=your-client-secret
|
|
4039
4131
|
`;
|
|
4040
4132
|
}
|
|
4041
|
-
function generateYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4133
|
+
function generateYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
|
|
4042
4134
|
const rbacSection = ` rbac:
|
|
4043
4135
|
roles:
|
|
4044
4136
|
${rolesToYaml(roles, " ")}
|
|
@@ -4061,7 +4153,7 @@ ${routesToYaml(protectedRoutes, " ", defaultRole)}`.replace(/\n\s+,/g, "");
|
|
|
4061
4153
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4062
4154
|
return `auth:
|
|
4063
4155
|
method: cac
|
|
4064
|
-
${providerLine} middleware:
|
|
4156
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4065
4157
|
cac:
|
|
4066
4158
|
caBundle: \${CAC_CA_BUNDLE}
|
|
4067
4159
|
edipiLookup: ${edipiLookup}
|
|
@@ -4078,7 +4170,7 @@ ${auditSection}
|
|
|
4078
4170
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4079
4171
|
return `auth:
|
|
4080
4172
|
method: oidc
|
|
4081
|
-
${providerLine} middleware:
|
|
4173
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4082
4174
|
oidc:
|
|
4083
4175
|
discoveryUrl: \${OIDC_DISCOVERY_URL}
|
|
4084
4176
|
clientId: \${OIDC_CLIENT_ID}
|
|
@@ -4094,7 +4186,7 @@ ${auditSection}
|
|
|
4094
4186
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4095
4187
|
return `auth:
|
|
4096
4188
|
method: oauth2
|
|
4097
|
-
${providerLine} middleware:
|
|
4189
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4098
4190
|
oauth2:
|
|
4099
4191
|
authorizationUrl: \${OAUTH2_AUTH_URL}
|
|
4100
4192
|
tokenUrl: \${OAUTH2_TOKEN_URL}
|
|
@@ -4196,7 +4288,96 @@ ${routesBlock}
|
|
|
4196
4288
|
${configBlock}
|
|
4197
4289
|
`;
|
|
4198
4290
|
}
|
|
4199
|
-
function
|
|
4291
|
+
function generateDevOnlyProxyContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4292
|
+
const rbacBlock = ` rbac: {
|
|
4293
|
+
roles: ${JSON.stringify(roles)},
|
|
4294
|
+
defaultRole: '${defaultRole}',
|
|
4295
|
+
hierarchy: ${JSON.stringify(hierarchy, null, 4)},
|
|
4296
|
+
},`;
|
|
4297
|
+
const auditBlock = ` audit: {
|
|
4298
|
+
enabled: ${auditEnabled},
|
|
4299
|
+
retentionDays: ${auditRetentionDays},
|
|
4300
|
+
},`;
|
|
4301
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(protectedRoutes)},`;
|
|
4302
|
+
const configBlock = `export const config = {
|
|
4303
|
+
matcher: ${JSON.stringify(protectedRoutes)},
|
|
4304
|
+
};`;
|
|
4305
|
+
const devHeader = [
|
|
4306
|
+
"// proxy.ts -- generated by @stackwright-pro/auth (Next.js >=16, dev-only mock)",
|
|
4307
|
+
"// DEV ONLY -- uses mock authentication from lib/mock-auth.ts",
|
|
4308
|
+
"// Do NOT deploy to production.",
|
|
4309
|
+
"import { createAuthProxy } from '@stackwright-pro/auth-nextjs';",
|
|
4310
|
+
"import { mockAuthProvider } from './lib/mock-auth';"
|
|
4311
|
+
].join("\n");
|
|
4312
|
+
if (method === "cac") {
|
|
4313
|
+
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4314
|
+
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4315
|
+
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4316
|
+
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4317
|
+
return `${devHeader}
|
|
4318
|
+
|
|
4319
|
+
export const proxy = createAuthProxy({
|
|
4320
|
+
method: 'cac',
|
|
4321
|
+
cac: {
|
|
4322
|
+
caBundle: '${caBundle}',
|
|
4323
|
+
edipiLookup: '${edipiLookup}',
|
|
4324
|
+
ocspEndpoint: '${ocspEndpoint}',
|
|
4325
|
+
certHeader: '${certHeader}',
|
|
4326
|
+
provider: mockAuthProvider,
|
|
4327
|
+
},
|
|
4328
|
+
${rbacBlock}
|
|
4329
|
+
${auditBlock}
|
|
4330
|
+
${routesBlock}
|
|
4331
|
+
});
|
|
4332
|
+
|
|
4333
|
+
${configBlock}
|
|
4334
|
+
`;
|
|
4335
|
+
}
|
|
4336
|
+
if (method === "oidc") {
|
|
4337
|
+
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4338
|
+
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4339
|
+
return `${devHeader}
|
|
4340
|
+
|
|
4341
|
+
export const proxy = createAuthProxy({
|
|
4342
|
+
method: 'oidc',
|
|
4343
|
+
oidc: {
|
|
4344
|
+
discoveryUrl: 'https://dev-mock-oidc/.well-known/openid-configuration',
|
|
4345
|
+
clientId: 'dev-mock-client',
|
|
4346
|
+
clientSecret: 'dev-mock-secret',
|
|
4347
|
+
scopes: '${scopes2}',
|
|
4348
|
+
roleClaim: '${roleClaim}',
|
|
4349
|
+
provider: mockAuthProvider,
|
|
4350
|
+
},
|
|
4351
|
+
${rbacBlock}
|
|
4352
|
+
${auditBlock}
|
|
4353
|
+
${routesBlock}
|
|
4354
|
+
});
|
|
4355
|
+
|
|
4356
|
+
${configBlock}
|
|
4357
|
+
`;
|
|
4358
|
+
}
|
|
4359
|
+
const scopes = params.oauth2Scopes ?? "read write";
|
|
4360
|
+
return `${devHeader}
|
|
4361
|
+
|
|
4362
|
+
export const proxy = createAuthProxy({
|
|
4363
|
+
method: 'oauth2',
|
|
4364
|
+
oauth2: {
|
|
4365
|
+
authorizationUrl: 'https://dev-mock-oauth2/authorize',
|
|
4366
|
+
tokenUrl: 'https://dev-mock-oauth2/token',
|
|
4367
|
+
clientId: 'dev-mock-client',
|
|
4368
|
+
clientSecret: 'dev-mock-secret',
|
|
4369
|
+
scopes: '${scopes}',
|
|
4370
|
+
provider: mockAuthProvider,
|
|
4371
|
+
},
|
|
4372
|
+
${rbacBlock}
|
|
4373
|
+
${auditBlock}
|
|
4374
|
+
${routesBlock}
|
|
4375
|
+
});
|
|
4376
|
+
|
|
4377
|
+
${configBlock}
|
|
4378
|
+
`;
|
|
4379
|
+
}
|
|
4380
|
+
function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
|
|
4200
4381
|
const rbacSection = ` rbac:
|
|
4201
4382
|
roles:
|
|
4202
4383
|
${rolesToYaml(roles, " ")}
|
|
@@ -4218,7 +4399,7 @@ ${hierarchyToYaml(hierarchy, " ")}`;
|
|
|
4218
4399
|
return `auth:
|
|
4219
4400
|
method: cac
|
|
4220
4401
|
devOnly: true
|
|
4221
|
-
${providerLine} middleware:
|
|
4402
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4222
4403
|
cac:
|
|
4223
4404
|
caBundle: ${caBundle}
|
|
4224
4405
|
edipiLookup: ${edipiLookup}
|
|
@@ -4236,7 +4417,7 @@ ${auditSection}
|
|
|
4236
4417
|
return `auth:
|
|
4237
4418
|
method: oidc
|
|
4238
4419
|
devOnly: true
|
|
4239
|
-
${providerLine} middleware:
|
|
4420
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4240
4421
|
oidc:
|
|
4241
4422
|
discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
|
|
4242
4423
|
clientId: dev-mock-client
|
|
@@ -4253,7 +4434,7 @@ ${auditSection}
|
|
|
4253
4434
|
return `auth:
|
|
4254
4435
|
method: oauth2
|
|
4255
4436
|
devOnly: true
|
|
4256
|
-
${providerLine} middleware:
|
|
4437
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4257
4438
|
oauth2:
|
|
4258
4439
|
authorizationUrl: https://dev-mock-oauth2/authorize
|
|
4259
4440
|
tokenUrl: https://dev-mock-oauth2/token
|
|
@@ -4341,6 +4522,8 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4341
4522
|
const roles = rbacRoles;
|
|
4342
4523
|
const defaultRole = params.rbacDefaultRole ?? roles[roles.length - 1];
|
|
4343
4524
|
const hierarchy = buildHierarchy(roles);
|
|
4525
|
+
const useProxy = (params.nextMajorVersion ?? 0) >= 16;
|
|
4526
|
+
const conventionFile = useProxy ? "proxy.ts" : "middleware.ts";
|
|
4344
4527
|
if (method === "none") {
|
|
4345
4528
|
return {
|
|
4346
4529
|
content: [
|
|
@@ -4362,7 +4545,25 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4362
4545
|
}
|
|
4363
4546
|
const filesWritten = [];
|
|
4364
4547
|
try {
|
|
4365
|
-
const
|
|
4548
|
+
const authFileContent = devOnly ? useProxy ? generateDevOnlyProxyContent(
|
|
4549
|
+
method,
|
|
4550
|
+
params,
|
|
4551
|
+
roles,
|
|
4552
|
+
defaultRole,
|
|
4553
|
+
hierarchy,
|
|
4554
|
+
auditEnabled,
|
|
4555
|
+
auditRetentionDays,
|
|
4556
|
+
protectedRoutes
|
|
4557
|
+
) : generateDevOnlyMiddlewareContent(
|
|
4558
|
+
method,
|
|
4559
|
+
params,
|
|
4560
|
+
roles,
|
|
4561
|
+
defaultRole,
|
|
4562
|
+
hierarchy,
|
|
4563
|
+
auditEnabled,
|
|
4564
|
+
auditRetentionDays,
|
|
4565
|
+
protectedRoutes
|
|
4566
|
+
) : useProxy ? generateProxyContent(
|
|
4366
4567
|
method,
|
|
4367
4568
|
params,
|
|
4368
4569
|
roles,
|
|
@@ -4381,15 +4582,18 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4381
4582
|
auditRetentionDays,
|
|
4382
4583
|
protectedRoutes
|
|
4383
4584
|
);
|
|
4384
|
-
(0, import_fs7.writeFileSync)((0, import_path7.join)(cwd,
|
|
4385
|
-
filesWritten.push(
|
|
4585
|
+
(0, import_fs7.writeFileSync)((0, import_path7.join)(cwd, conventionFile), authFileContent, "utf8");
|
|
4586
|
+
filesWritten.push(conventionFile);
|
|
4386
4587
|
} catch (err) {
|
|
4387
4588
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4388
4589
|
return {
|
|
4389
4590
|
content: [
|
|
4390
4591
|
{
|
|
4391
4592
|
type: "text",
|
|
4392
|
-
text: JSON.stringify({
|
|
4593
|
+
text: JSON.stringify({
|
|
4594
|
+
success: false,
|
|
4595
|
+
error: `Failed writing ${conventionFile}: ${msg}`
|
|
4596
|
+
})
|
|
4393
4597
|
}
|
|
4394
4598
|
],
|
|
4395
4599
|
isError: true
|
|
@@ -4474,7 +4678,8 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4474
4678
|
hierarchy,
|
|
4475
4679
|
auditEnabled,
|
|
4476
4680
|
auditRetentionDays,
|
|
4477
|
-
protectedRoutes
|
|
4681
|
+
protectedRoutes,
|
|
4682
|
+
useProxy
|
|
4478
4683
|
) : generateYamlBlock(
|
|
4479
4684
|
method,
|
|
4480
4685
|
params,
|
|
@@ -4483,7 +4688,8 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4483
4688
|
hierarchy,
|
|
4484
4689
|
auditEnabled,
|
|
4485
4690
|
auditRetentionDays,
|
|
4486
|
-
protectedRoutes
|
|
4691
|
+
protectedRoutes,
|
|
4692
|
+
useProxy
|
|
4487
4693
|
);
|
|
4488
4694
|
const ymlPath = (0, import_path7.join)(cwd, "stackwright.yml");
|
|
4489
4695
|
if (!(0, import_fs7.existsSync)(ymlPath)) {
|
|
@@ -4518,6 +4724,8 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4518
4724
|
rbacDefaultRole: defaultRole,
|
|
4519
4725
|
protectedRoutesCount: protectedRoutes.length,
|
|
4520
4726
|
filesWritten,
|
|
4727
|
+
convention: useProxy ? "proxy" : "middleware",
|
|
4728
|
+
nextMajorVersion: params.nextMajorVersion ?? null,
|
|
4521
4729
|
securityWarning,
|
|
4522
4730
|
...devOnly ? {
|
|
4523
4731
|
devScripts: {
|
|
@@ -4573,7 +4781,8 @@ function registerAuthTools(server2) {
|
|
|
4573
4781
|
// Injection for tests
|
|
4574
4782
|
_cwd: import_zod13.z.string().optional(),
|
|
4575
4783
|
devOnly: boolCoerce(import_zod13.z.boolean().optional()),
|
|
4576
|
-
mockUsers: jsonCoerce(import_zod13.z.array(import_zod13.z.object({ name: import_zod13.z.string(), email: import_zod13.z.string() })).optional())
|
|
4784
|
+
mockUsers: jsonCoerce(import_zod13.z.array(import_zod13.z.object({ name: import_zod13.z.string(), email: import_zod13.z.string() })).optional()),
|
|
4785
|
+
nextMajorVersion: numCoerce(import_zod13.z.number().int().positive().optional())
|
|
4577
4786
|
},
|
|
4578
4787
|
async (params) => {
|
|
4579
4788
|
const cwd = params._cwd ?? process.cwd();
|
|
@@ -4589,11 +4798,11 @@ var import_path8 = require("path");
|
|
|
4589
4798
|
var _checksums = /* @__PURE__ */ new Map([
|
|
4590
4799
|
[
|
|
4591
4800
|
"stackwright-pro-api-otter.json",
|
|
4592
|
-
"
|
|
4801
|
+
"c21f127b9bed477b1d7b66949926e00e1b93de081e792293ebfffc88b70d9424"
|
|
4593
4802
|
],
|
|
4594
4803
|
[
|
|
4595
4804
|
"stackwright-pro-auth-otter.json",
|
|
4596
|
-
"
|
|
4805
|
+
"6d4d4d50f52be84a796c2a43ed997f00b41b42ed8c5f046e1defb2e423933588"
|
|
4597
4806
|
],
|
|
4598
4807
|
[
|
|
4599
4808
|
"stackwright-pro-dashboard-otter.json",
|
|
@@ -4613,7 +4822,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4613
4822
|
],
|
|
4614
4823
|
[
|
|
4615
4824
|
"stackwright-pro-foreman-otter.json",
|
|
4616
|
-
"
|
|
4825
|
+
"e563a99d647fcf95401508f15f10e032fb14e826577bc6bcff5960bf2981d58d"
|
|
4617
4826
|
],
|
|
4618
4827
|
[
|
|
4619
4828
|
"stackwright-pro-geo-otter.json",
|
|
@@ -4621,15 +4830,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4621
4830
|
],
|
|
4622
4831
|
[
|
|
4623
4832
|
"stackwright-pro-page-otter.json",
|
|
4624
|
-
"
|
|
4833
|
+
"cdd878857c1d809c60263693ab0c6cbb45087fd9c3eeda5b4628bd7026d2bded"
|
|
4625
4834
|
],
|
|
4626
4835
|
[
|
|
4627
4836
|
"stackwright-pro-polish-otter.json",
|
|
4628
|
-
"
|
|
4837
|
+
"e5f88d054dd536646f48e5d47cbd64f825f493100002309b90c912fa69ef279e"
|
|
4629
4838
|
],
|
|
4630
4839
|
[
|
|
4631
4840
|
"stackwright-pro-theme-otter.json",
|
|
4632
|
-
"
|
|
4841
|
+
"532d9ca7db6911a09ce5029a8c74c89360bdf6cb8ede8c2636866b509cbe06f9"
|
|
4633
4842
|
],
|
|
4634
4843
|
[
|
|
4635
4844
|
"stackwright-pro-workflow-otter.json",
|
|
@@ -5523,6 +5732,245 @@ function registerScaffoldCleanupTools(server2) {
|
|
|
5523
5732
|
);
|
|
5524
5733
|
}
|
|
5525
5734
|
|
|
5735
|
+
// src/tools/contrast.ts
|
|
5736
|
+
var import_zod16 = require("zod");
|
|
5737
|
+
function linearizeChannel(c255) {
|
|
5738
|
+
const c = c255 / 255;
|
|
5739
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
5740
|
+
}
|
|
5741
|
+
function relativeLuminance(r, g, b) {
|
|
5742
|
+
return 0.2126 * linearizeChannel(r) + 0.7152 * linearizeChannel(g) + 0.0722 * linearizeChannel(b);
|
|
5743
|
+
}
|
|
5744
|
+
function contrastRatioFromLuminance(l1, l2) {
|
|
5745
|
+
const lighter = Math.max(l1, l2);
|
|
5746
|
+
const darker = Math.min(l1, l2);
|
|
5747
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
5748
|
+
}
|
|
5749
|
+
function parseHex(hex) {
|
|
5750
|
+
const clean = hex.replace("#", "").trim().toLowerCase();
|
|
5751
|
+
if (clean.length === 3) {
|
|
5752
|
+
const r = parseInt(clean.charAt(0) + clean.charAt(0), 16);
|
|
5753
|
+
const g = parseInt(clean.charAt(1) + clean.charAt(1), 16);
|
|
5754
|
+
const b = parseInt(clean.charAt(2) + clean.charAt(2), 16);
|
|
5755
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
5756
|
+
return [r, g, b];
|
|
5757
|
+
}
|
|
5758
|
+
if (clean.length === 6) {
|
|
5759
|
+
const r = parseInt(clean.slice(0, 2), 16);
|
|
5760
|
+
const g = parseInt(clean.slice(2, 4), 16);
|
|
5761
|
+
const b = parseInt(clean.slice(4, 6), 16);
|
|
5762
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
5763
|
+
return [r, g, b];
|
|
5764
|
+
}
|
|
5765
|
+
return null;
|
|
5766
|
+
}
|
|
5767
|
+
function hslToRgb(h, s, l) {
|
|
5768
|
+
const hn = h / 360;
|
|
5769
|
+
const sn = s / 100;
|
|
5770
|
+
const ln = l / 100;
|
|
5771
|
+
const hue2rgb = (p, q, t) => {
|
|
5772
|
+
let tt = t;
|
|
5773
|
+
if (tt < 0) tt += 1;
|
|
5774
|
+
if (tt > 1) tt -= 1;
|
|
5775
|
+
if (tt < 1 / 6) return p + (q - p) * 6 * tt;
|
|
5776
|
+
if (tt < 1 / 2) return q;
|
|
5777
|
+
if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6;
|
|
5778
|
+
return p;
|
|
5779
|
+
};
|
|
5780
|
+
let r, g, b;
|
|
5781
|
+
if (sn === 0) {
|
|
5782
|
+
r = g = b = ln;
|
|
5783
|
+
} else {
|
|
5784
|
+
const q = ln < 0.5 ? ln * (1 + sn) : ln + sn - ln * sn;
|
|
5785
|
+
const p = 2 * ln - q;
|
|
5786
|
+
r = hue2rgb(p, q, hn + 1 / 3);
|
|
5787
|
+
g = hue2rgb(p, q, hn);
|
|
5788
|
+
b = hue2rgb(p, q, hn - 1 / 3);
|
|
5789
|
+
}
|
|
5790
|
+
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
|
5791
|
+
}
|
|
5792
|
+
function parseHsl(hsl) {
|
|
5793
|
+
let str = hsl.replace(/^hsl\s*\(\s*/i, "").replace(/\s*\)$/, "").trim();
|
|
5794
|
+
str = str.replace(/%/g, "");
|
|
5795
|
+
const parts = str.split(/[\s,]+/).filter(Boolean);
|
|
5796
|
+
if (parts.length !== 3) return null;
|
|
5797
|
+
const h = parseFloat(parts[0]);
|
|
5798
|
+
const s = parseFloat(parts[1]);
|
|
5799
|
+
const l = parseFloat(parts[2]);
|
|
5800
|
+
if (isNaN(h) || isNaN(s) || isNaN(l)) return null;
|
|
5801
|
+
return hslToRgb(h, s, l);
|
|
5802
|
+
}
|
|
5803
|
+
function parseColor(color) {
|
|
5804
|
+
const trimmed = color.trim();
|
|
5805
|
+
if (trimmed.startsWith("#")) return parseHex(trimmed);
|
|
5806
|
+
if (/^hsl\s*\(/i.test(trimmed)) return parseHsl(trimmed);
|
|
5807
|
+
if (/^\d[\d.]*[\s,]+\d[\d.]*%?[\s,]+\d[\d.]*%?$/.test(trimmed)) return parseHsl(trimmed);
|
|
5808
|
+
return null;
|
|
5809
|
+
}
|
|
5810
|
+
function rgbToHex(r, g, b) {
|
|
5811
|
+
return "#" + [r, g, b].map(
|
|
5812
|
+
(c) => Math.max(0, Math.min(255, Math.round(c))).toString(16).padStart(2, "0")
|
|
5813
|
+
).join("");
|
|
5814
|
+
}
|
|
5815
|
+
function handleCheckContrast(fg, bg) {
|
|
5816
|
+
const fgRgb = parseColor(fg);
|
|
5817
|
+
const bgRgb = parseColor(bg);
|
|
5818
|
+
if (!fgRgb) throw new Error(`Cannot parse foreground color: "${fg}"`);
|
|
5819
|
+
if (!bgRgb) throw new Error(`Cannot parse background color: "${bg}"`);
|
|
5820
|
+
const fgL = relativeLuminance(...fgRgb);
|
|
5821
|
+
const bgL = relativeLuminance(...bgRgb);
|
|
5822
|
+
const ratio = parseFloat(contrastRatioFromLuminance(fgL, bgL).toFixed(2));
|
|
5823
|
+
return {
|
|
5824
|
+
fgHex: rgbToHex(...fgRgb),
|
|
5825
|
+
bgHex: rgbToHex(...bgRgb),
|
|
5826
|
+
ratio,
|
|
5827
|
+
aa: ratio >= 4.5,
|
|
5828
|
+
aaLarge: ratio >= 3,
|
|
5829
|
+
aaa: ratio >= 7,
|
|
5830
|
+
aaaLarge: ratio >= 4.5
|
|
5831
|
+
};
|
|
5832
|
+
}
|
|
5833
|
+
function handleDeriveAccessiblePalette(seed, targetRatio) {
|
|
5834
|
+
const seedRgb = parseColor(seed);
|
|
5835
|
+
if (!seedRgb) throw new Error(`Cannot parse seed color: "${seed}"`);
|
|
5836
|
+
const seedHex = rgbToHex(...seedRgb);
|
|
5837
|
+
const seedL = relativeLuminance(...seedRgb);
|
|
5838
|
+
const whiteRatio = parseFloat(contrastRatioFromLuminance(1, seedL).toFixed(2));
|
|
5839
|
+
const blackRatio = parseFloat(contrastRatioFromLuminance(0, seedL).toFixed(2));
|
|
5840
|
+
const whiteWorks = whiteRatio >= targetRatio;
|
|
5841
|
+
const blackWorks = blackRatio >= targetRatio;
|
|
5842
|
+
let strategy;
|
|
5843
|
+
let foreground;
|
|
5844
|
+
let ratio;
|
|
5845
|
+
let alternativeForeground;
|
|
5846
|
+
let alternativeRatio;
|
|
5847
|
+
if (blackWorks && whiteWorks) {
|
|
5848
|
+
if (blackRatio >= whiteRatio) {
|
|
5849
|
+
strategy = "black";
|
|
5850
|
+
foreground = "#000000";
|
|
5851
|
+
ratio = blackRatio;
|
|
5852
|
+
alternativeForeground = "#ffffff";
|
|
5853
|
+
alternativeRatio = whiteRatio;
|
|
5854
|
+
} else {
|
|
5855
|
+
strategy = "white";
|
|
5856
|
+
foreground = "#ffffff";
|
|
5857
|
+
ratio = whiteRatio;
|
|
5858
|
+
alternativeForeground = "#000000";
|
|
5859
|
+
alternativeRatio = blackRatio;
|
|
5860
|
+
}
|
|
5861
|
+
} else if (blackWorks) {
|
|
5862
|
+
strategy = "black";
|
|
5863
|
+
foreground = "#000000";
|
|
5864
|
+
ratio = blackRatio;
|
|
5865
|
+
alternativeForeground = "#ffffff";
|
|
5866
|
+
alternativeRatio = whiteRatio;
|
|
5867
|
+
} else if (whiteWorks) {
|
|
5868
|
+
strategy = "white";
|
|
5869
|
+
foreground = "#ffffff";
|
|
5870
|
+
ratio = whiteRatio;
|
|
5871
|
+
alternativeForeground = "#000000";
|
|
5872
|
+
alternativeRatio = blackRatio;
|
|
5873
|
+
} else {
|
|
5874
|
+
strategy = "unachievable";
|
|
5875
|
+
if (blackRatio >= whiteRatio) {
|
|
5876
|
+
foreground = "#000000";
|
|
5877
|
+
ratio = blackRatio;
|
|
5878
|
+
alternativeForeground = "#ffffff";
|
|
5879
|
+
alternativeRatio = whiteRatio;
|
|
5880
|
+
} else {
|
|
5881
|
+
foreground = "#ffffff";
|
|
5882
|
+
ratio = whiteRatio;
|
|
5883
|
+
alternativeForeground = "#000000";
|
|
5884
|
+
alternativeRatio = blackRatio;
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
return {
|
|
5888
|
+
seedHex,
|
|
5889
|
+
foreground,
|
|
5890
|
+
ratio,
|
|
5891
|
+
aa: ratio >= 4.5,
|
|
5892
|
+
aaLarge: ratio >= 3,
|
|
5893
|
+
aaa: ratio >= 7,
|
|
5894
|
+
meetsTarget: ratio >= targetRatio,
|
|
5895
|
+
strategy,
|
|
5896
|
+
alternativeForeground,
|
|
5897
|
+
alternativeRatio
|
|
5898
|
+
};
|
|
5899
|
+
}
|
|
5900
|
+
var PASS = "[PASS]";
|
|
5901
|
+
var FAIL = "[FAIL]";
|
|
5902
|
+
var mark = (ok) => ok ? PASS : FAIL;
|
|
5903
|
+
function registerContrastTools(server2) {
|
|
5904
|
+
server2.tool(
|
|
5905
|
+
"stackwright_pro_check_contrast",
|
|
5906
|
+
'Compute the exact WCAG 2.1 contrast ratio between a foreground and background color. Returns the ratio and AA/AAA pass/fail flags. Use this to verify any foreground/background color pair before writing it to the token set. Accepts #RGB, #RRGGBB, hsl(...), or shadcn HSL format ("H S% L%").',
|
|
5907
|
+
{
|
|
5908
|
+
fg: import_zod16.z.string().describe("Foreground (text) color \u2014 hex or HSL"),
|
|
5909
|
+
bg: import_zod16.z.string().describe("Background color \u2014 hex or HSL")
|
|
5910
|
+
},
|
|
5911
|
+
async ({ fg, bg }) => {
|
|
5912
|
+
let result;
|
|
5913
|
+
try {
|
|
5914
|
+
result = handleCheckContrast(fg, bg);
|
|
5915
|
+
} catch (err) {
|
|
5916
|
+
return {
|
|
5917
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
5918
|
+
};
|
|
5919
|
+
}
|
|
5920
|
+
const text = [
|
|
5921
|
+
`WCAG 2.1 Contrast Check`,
|
|
5922
|
+
``,
|
|
5923
|
+
` Foreground : ${result.fgHex}`,
|
|
5924
|
+
` Background : ${result.bgHex}`,
|
|
5925
|
+
` Ratio : ${result.ratio}:1`,
|
|
5926
|
+
``,
|
|
5927
|
+
` ${mark(result.aa)} AA \u2014 normal text (\u22654.5:1)`,
|
|
5928
|
+
` ${mark(result.aaLarge)} AA \u2014 large text (\u22653.0:1)`,
|
|
5929
|
+
` ${mark(result.aaa)} AAA \u2014 normal text (\u22657.0:1)`,
|
|
5930
|
+
` ${mark(result.aaaLarge)} AAA \u2014 large text (\u22654.5:1)`
|
|
5931
|
+
].join("\n");
|
|
5932
|
+
return { content: [{ type: "text", text }] };
|
|
5933
|
+
}
|
|
5934
|
+
);
|
|
5935
|
+
server2.tool(
|
|
5936
|
+
"stackwright_pro_derive_accessible_palette",
|
|
5937
|
+
'Given a seed color (treated as a background), derive the best accessible foreground (text) color that meets the target contrast ratio. Evaluates #ffffff and #000000 against the seed and picks whichever satisfies the target ratio with the highest contrast. Use this for every {name}-foreground token derivation instead of guessing white vs black. Accepts #RGB, #RRGGBB, hsl(...), or shadcn HSL format ("H S% L%").',
|
|
5938
|
+
{
|
|
5939
|
+
seed: import_zod16.z.string().describe("Background (seed) color \u2014 hex or HSL"),
|
|
5940
|
+
targetRatio: numCoerce(import_zod16.z.number().default(4.5)).describe(
|
|
5941
|
+
"Minimum required contrast ratio (default 4.5 for WCAG AA)"
|
|
5942
|
+
)
|
|
5943
|
+
},
|
|
5944
|
+
async ({ seed, targetRatio }) => {
|
|
5945
|
+
let result;
|
|
5946
|
+
try {
|
|
5947
|
+
result = handleDeriveAccessiblePalette(seed, targetRatio ?? 4.5);
|
|
5948
|
+
} catch (err) {
|
|
5949
|
+
return {
|
|
5950
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
5951
|
+
};
|
|
5952
|
+
}
|
|
5953
|
+
const metLine = result.meetsTarget ? `${PASS} Meets target (${targetRatio}:1)` : `${FAIL} Target (${targetRatio}:1) UNACHIEVABLE with white or black \u2014 best available used`;
|
|
5954
|
+
const altNote = result.alternativeRatio >= (targetRatio ?? 4.5) ? `also passes (${result.alternativeRatio}:1)` : `fails at ${result.alternativeRatio}:1`;
|
|
5955
|
+
const text = [
|
|
5956
|
+
`Accessible Palette Derivation`,
|
|
5957
|
+
``,
|
|
5958
|
+
` Background (seed) : ${result.seedHex}`,
|
|
5959
|
+
` Foreground : ${result.foreground} (${result.strategy})`,
|
|
5960
|
+
` Contrast ratio : ${result.ratio}:1`,
|
|
5961
|
+
` ${metLine}`,
|
|
5962
|
+
``,
|
|
5963
|
+
` ${mark(result.aa)} AA \u2014 normal text (\u22654.5:1)`,
|
|
5964
|
+
` ${mark(result.aaLarge)} AA \u2014 large text (\u22653.0:1)`,
|
|
5965
|
+
` ${mark(result.aaa)} AAA \u2014 normal text (\u22657.0:1)`,
|
|
5966
|
+
``,
|
|
5967
|
+
` Alternative (${result.alternativeForeground}): ${altNote}`
|
|
5968
|
+
].join("\n");
|
|
5969
|
+
return { content: [{ type: "text", text }] };
|
|
5970
|
+
}
|
|
5971
|
+
);
|
|
5972
|
+
}
|
|
5973
|
+
|
|
5526
5974
|
// package.json
|
|
5527
5975
|
var package_default = {
|
|
5528
5976
|
dependencies: {
|
|
@@ -5546,7 +5994,7 @@ var package_default = {
|
|
|
5546
5994
|
"test:coverage": "vitest run --coverage"
|
|
5547
5995
|
},
|
|
5548
5996
|
name: "@stackwright-pro/mcp",
|
|
5549
|
-
version: "0.2.0-alpha.
|
|
5997
|
+
version: "0.2.0-alpha.71",
|
|
5550
5998
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
5551
5999
|
license: "SEE LICENSE IN LICENSE",
|
|
5552
6000
|
main: "./dist/server.js",
|
|
@@ -5601,6 +6049,7 @@ registerArtifactSigningTools(server);
|
|
|
5601
6049
|
registerDomainTools(server);
|
|
5602
6050
|
registerTypeSchemasTool(server);
|
|
5603
6051
|
registerScaffoldCleanupTools(server);
|
|
6052
|
+
registerContrastTools(server);
|
|
5604
6053
|
async function main() {
|
|
5605
6054
|
const transport = new import_stdio.StdioServerTransport();
|
|
5606
6055
|
try {
|