@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.mjs
CHANGED
|
@@ -3029,6 +3029,13 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3029
3029
|
mutationType: null
|
|
3030
3030
|
}
|
|
3031
3031
|
],
|
|
3032
|
+
skipped: [
|
|
3033
|
+
{
|
|
3034
|
+
spec: "ais-message-models.yaml",
|
|
3035
|
+
format: "asyncapi",
|
|
3036
|
+
reason: "AsyncAPI spec \u2014 WebSocket/event integration required (no REST endpoints)"
|
|
3037
|
+
}
|
|
3038
|
+
],
|
|
3032
3039
|
auth: { type: "bearer", header: "Authorization", envVar: "API_TOKEN" },
|
|
3033
3040
|
baseUrl: "https://api.example.mil/v2",
|
|
3034
3041
|
specPath: "./specs/api.yaml"
|
|
@@ -4001,6 +4008,91 @@ ${auditBlock}
|
|
|
4001
4008
|
${routesBlock}
|
|
4002
4009
|
});
|
|
4003
4010
|
|
|
4011
|
+
${configBlock}
|
|
4012
|
+
`;
|
|
4013
|
+
}
|
|
4014
|
+
function generateProxyContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4015
|
+
const rbacBlock = ` rbac: {
|
|
4016
|
+
roles: ${JSON.stringify(roles)},
|
|
4017
|
+
defaultRole: '${defaultRole}',
|
|
4018
|
+
hierarchy: ${JSON.stringify(hierarchy, null, 4)},
|
|
4019
|
+
},`;
|
|
4020
|
+
const auditBlock = ` audit: {
|
|
4021
|
+
enabled: ${auditEnabled},
|
|
4022
|
+
retentionDays: ${auditRetentionDays},
|
|
4023
|
+
},`;
|
|
4024
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(protectedRoutes)},`;
|
|
4025
|
+
const configBlock = `export const config = {
|
|
4026
|
+
matcher: ${JSON.stringify(protectedRoutes)},
|
|
4027
|
+
};`;
|
|
4028
|
+
if (method === "cac") {
|
|
4029
|
+
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4030
|
+
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4031
|
+
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4032
|
+
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4033
|
+
return `// proxy.ts \u2014 generated by @stackwright-pro/auth (Next.js >=16)
|
|
4034
|
+
// SECURITY REVIEW REQUIRED \u2014 CAC/PKI certificate validation
|
|
4035
|
+
// DoD security officer review required before production deployment.
|
|
4036
|
+
// Verify: CA bundle completeness, EDIPI lookup endpoint, OCSP accessibility.
|
|
4037
|
+
import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
4038
|
+
|
|
4039
|
+
export const proxy = createAuthProxy({
|
|
4040
|
+
method: 'cac',
|
|
4041
|
+
cac: {
|
|
4042
|
+
caBundle: process.env.CAC_CA_BUNDLE ?? '${caBundle}',
|
|
4043
|
+
edipiLookup: '${edipiLookup}',
|
|
4044
|
+
ocspEndpoint: process.env.CAC_OCSP_ENDPOINT ?? '${ocspEndpoint}',
|
|
4045
|
+
certHeader: '${certHeader}',
|
|
4046
|
+
},
|
|
4047
|
+
${rbacBlock}
|
|
4048
|
+
${auditBlock}
|
|
4049
|
+
${routesBlock}
|
|
4050
|
+
});
|
|
4051
|
+
|
|
4052
|
+
${configBlock}
|
|
4053
|
+
`;
|
|
4054
|
+
}
|
|
4055
|
+
if (method === "oidc") {
|
|
4056
|
+
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4057
|
+
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4058
|
+
return `// proxy.ts \u2014 generated by @stackwright-pro/auth (Next.js >=16)
|
|
4059
|
+
import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
4060
|
+
|
|
4061
|
+
export const proxy = createAuthProxy({
|
|
4062
|
+
method: 'oidc',
|
|
4063
|
+
oidc: {
|
|
4064
|
+
discoveryUrl: process.env.OIDC_DISCOVERY_URL!,
|
|
4065
|
+
clientId: process.env.OIDC_CLIENT_ID!,
|
|
4066
|
+
clientSecret: process.env.OIDC_CLIENT_SECRET!,
|
|
4067
|
+
scopes: '${scopes2}',
|
|
4068
|
+
roleClaim: '${roleClaim}',
|
|
4069
|
+
},
|
|
4070
|
+
${rbacBlock}
|
|
4071
|
+
${auditBlock}
|
|
4072
|
+
${routesBlock}
|
|
4073
|
+
});
|
|
4074
|
+
|
|
4075
|
+
${configBlock}
|
|
4076
|
+
`;
|
|
4077
|
+
}
|
|
4078
|
+
const scopes = params.oauth2Scopes ?? "read write";
|
|
4079
|
+
return `// proxy.ts \u2014 generated by @stackwright-pro/auth (Next.js >=16)
|
|
4080
|
+
import { createAuthProxy } from '@stackwright-pro/auth-nextjs';
|
|
4081
|
+
|
|
4082
|
+
export const proxy = createAuthProxy({
|
|
4083
|
+
method: 'oauth2',
|
|
4084
|
+
oauth2: {
|
|
4085
|
+
authorizationUrl: process.env.OAUTH2_AUTH_URL!,
|
|
4086
|
+
tokenUrl: process.env.OAUTH2_TOKEN_URL!,
|
|
4087
|
+
clientId: process.env.OAUTH2_CLIENT_ID!,
|
|
4088
|
+
clientSecret: process.env.OAUTH2_CLIENT_SECRET!,
|
|
4089
|
+
scopes: '${scopes}',
|
|
4090
|
+
},
|
|
4091
|
+
${rbacBlock}
|
|
4092
|
+
${auditBlock}
|
|
4093
|
+
${routesBlock}
|
|
4094
|
+
});
|
|
4095
|
+
|
|
4004
4096
|
${configBlock}
|
|
4005
4097
|
`;
|
|
4006
4098
|
}
|
|
@@ -4030,7 +4122,7 @@ OAUTH2_CLIENT_ID=your-client-id
|
|
|
4030
4122
|
OAUTH2_CLIENT_SECRET=your-client-secret
|
|
4031
4123
|
`;
|
|
4032
4124
|
}
|
|
4033
|
-
function generateYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4125
|
+
function generateYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
|
|
4034
4126
|
const rbacSection = ` rbac:
|
|
4035
4127
|
roles:
|
|
4036
4128
|
${rolesToYaml(roles, " ")}
|
|
@@ -4053,7 +4145,7 @@ ${routesToYaml(protectedRoutes, " ", defaultRole)}`.replace(/\n\s+,/g, "");
|
|
|
4053
4145
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4054
4146
|
return `auth:
|
|
4055
4147
|
method: cac
|
|
4056
|
-
${providerLine} middleware:
|
|
4148
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4057
4149
|
cac:
|
|
4058
4150
|
caBundle: \${CAC_CA_BUNDLE}
|
|
4059
4151
|
edipiLookup: ${edipiLookup}
|
|
@@ -4070,7 +4162,7 @@ ${auditSection}
|
|
|
4070
4162
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4071
4163
|
return `auth:
|
|
4072
4164
|
method: oidc
|
|
4073
|
-
${providerLine} middleware:
|
|
4165
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4074
4166
|
oidc:
|
|
4075
4167
|
discoveryUrl: \${OIDC_DISCOVERY_URL}
|
|
4076
4168
|
clientId: \${OIDC_CLIENT_ID}
|
|
@@ -4086,7 +4178,7 @@ ${auditSection}
|
|
|
4086
4178
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4087
4179
|
return `auth:
|
|
4088
4180
|
method: oauth2
|
|
4089
|
-
${providerLine} middleware:
|
|
4181
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4090
4182
|
oauth2:
|
|
4091
4183
|
authorizationUrl: \${OAUTH2_AUTH_URL}
|
|
4092
4184
|
tokenUrl: \${OAUTH2_TOKEN_URL}
|
|
@@ -4188,7 +4280,96 @@ ${routesBlock}
|
|
|
4188
4280
|
${configBlock}
|
|
4189
4281
|
`;
|
|
4190
4282
|
}
|
|
4191
|
-
function
|
|
4283
|
+
function generateDevOnlyProxyContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4284
|
+
const rbacBlock = ` rbac: {
|
|
4285
|
+
roles: ${JSON.stringify(roles)},
|
|
4286
|
+
defaultRole: '${defaultRole}',
|
|
4287
|
+
hierarchy: ${JSON.stringify(hierarchy, null, 4)},
|
|
4288
|
+
},`;
|
|
4289
|
+
const auditBlock = ` audit: {
|
|
4290
|
+
enabled: ${auditEnabled},
|
|
4291
|
+
retentionDays: ${auditRetentionDays},
|
|
4292
|
+
},`;
|
|
4293
|
+
const routesBlock = ` protectedRoutes: ${JSON.stringify(protectedRoutes)},`;
|
|
4294
|
+
const configBlock = `export const config = {
|
|
4295
|
+
matcher: ${JSON.stringify(protectedRoutes)},
|
|
4296
|
+
};`;
|
|
4297
|
+
const devHeader = [
|
|
4298
|
+
"// proxy.ts -- generated by @stackwright-pro/auth (Next.js >=16, dev-only mock)",
|
|
4299
|
+
"// DEV ONLY -- uses mock authentication from lib/mock-auth.ts",
|
|
4300
|
+
"// Do NOT deploy to production.",
|
|
4301
|
+
"import { createAuthProxy } from '@stackwright-pro/auth-nextjs';",
|
|
4302
|
+
"import { mockAuthProvider } from './lib/mock-auth';"
|
|
4303
|
+
].join("\n");
|
|
4304
|
+
if (method === "cac") {
|
|
4305
|
+
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4306
|
+
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4307
|
+
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4308
|
+
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4309
|
+
return `${devHeader}
|
|
4310
|
+
|
|
4311
|
+
export const proxy = createAuthProxy({
|
|
4312
|
+
method: 'cac',
|
|
4313
|
+
cac: {
|
|
4314
|
+
caBundle: '${caBundle}',
|
|
4315
|
+
edipiLookup: '${edipiLookup}',
|
|
4316
|
+
ocspEndpoint: '${ocspEndpoint}',
|
|
4317
|
+
certHeader: '${certHeader}',
|
|
4318
|
+
provider: mockAuthProvider,
|
|
4319
|
+
},
|
|
4320
|
+
${rbacBlock}
|
|
4321
|
+
${auditBlock}
|
|
4322
|
+
${routesBlock}
|
|
4323
|
+
});
|
|
4324
|
+
|
|
4325
|
+
${configBlock}
|
|
4326
|
+
`;
|
|
4327
|
+
}
|
|
4328
|
+
if (method === "oidc") {
|
|
4329
|
+
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4330
|
+
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4331
|
+
return `${devHeader}
|
|
4332
|
+
|
|
4333
|
+
export const proxy = createAuthProxy({
|
|
4334
|
+
method: 'oidc',
|
|
4335
|
+
oidc: {
|
|
4336
|
+
discoveryUrl: 'https://dev-mock-oidc/.well-known/openid-configuration',
|
|
4337
|
+
clientId: 'dev-mock-client',
|
|
4338
|
+
clientSecret: 'dev-mock-secret',
|
|
4339
|
+
scopes: '${scopes2}',
|
|
4340
|
+
roleClaim: '${roleClaim}',
|
|
4341
|
+
provider: mockAuthProvider,
|
|
4342
|
+
},
|
|
4343
|
+
${rbacBlock}
|
|
4344
|
+
${auditBlock}
|
|
4345
|
+
${routesBlock}
|
|
4346
|
+
});
|
|
4347
|
+
|
|
4348
|
+
${configBlock}
|
|
4349
|
+
`;
|
|
4350
|
+
}
|
|
4351
|
+
const scopes = params.oauth2Scopes ?? "read write";
|
|
4352
|
+
return `${devHeader}
|
|
4353
|
+
|
|
4354
|
+
export const proxy = createAuthProxy({
|
|
4355
|
+
method: 'oauth2',
|
|
4356
|
+
oauth2: {
|
|
4357
|
+
authorizationUrl: 'https://dev-mock-oauth2/authorize',
|
|
4358
|
+
tokenUrl: 'https://dev-mock-oauth2/token',
|
|
4359
|
+
clientId: 'dev-mock-client',
|
|
4360
|
+
clientSecret: 'dev-mock-secret',
|
|
4361
|
+
scopes: '${scopes}',
|
|
4362
|
+
provider: mockAuthProvider,
|
|
4363
|
+
},
|
|
4364
|
+
${rbacBlock}
|
|
4365
|
+
${auditBlock}
|
|
4366
|
+
${routesBlock}
|
|
4367
|
+
});
|
|
4368
|
+
|
|
4369
|
+
${configBlock}
|
|
4370
|
+
`;
|
|
4371
|
+
}
|
|
4372
|
+
function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
|
|
4192
4373
|
const rbacSection = ` rbac:
|
|
4193
4374
|
roles:
|
|
4194
4375
|
${rolesToYaml(roles, " ")}
|
|
@@ -4210,7 +4391,7 @@ ${hierarchyToYaml(hierarchy, " ")}`;
|
|
|
4210
4391
|
return `auth:
|
|
4211
4392
|
method: cac
|
|
4212
4393
|
devOnly: true
|
|
4213
|
-
${providerLine} middleware:
|
|
4394
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4214
4395
|
cac:
|
|
4215
4396
|
caBundle: ${caBundle}
|
|
4216
4397
|
edipiLookup: ${edipiLookup}
|
|
@@ -4228,7 +4409,7 @@ ${auditSection}
|
|
|
4228
4409
|
return `auth:
|
|
4229
4410
|
method: oidc
|
|
4230
4411
|
devOnly: true
|
|
4231
|
-
${providerLine} middleware:
|
|
4412
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4232
4413
|
oidc:
|
|
4233
4414
|
discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
|
|
4234
4415
|
clientId: dev-mock-client
|
|
@@ -4245,7 +4426,7 @@ ${auditSection}
|
|
|
4245
4426
|
return `auth:
|
|
4246
4427
|
method: oauth2
|
|
4247
4428
|
devOnly: true
|
|
4248
|
-
${providerLine} middleware:
|
|
4429
|
+
${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4249
4430
|
oauth2:
|
|
4250
4431
|
authorizationUrl: https://dev-mock-oauth2/authorize
|
|
4251
4432
|
tokenUrl: https://dev-mock-oauth2/token
|
|
@@ -4333,6 +4514,8 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4333
4514
|
const roles = rbacRoles;
|
|
4334
4515
|
const defaultRole = params.rbacDefaultRole ?? roles[roles.length - 1];
|
|
4335
4516
|
const hierarchy = buildHierarchy(roles);
|
|
4517
|
+
const useProxy = (params.nextMajorVersion ?? 0) >= 16;
|
|
4518
|
+
const conventionFile = useProxy ? "proxy.ts" : "middleware.ts";
|
|
4336
4519
|
if (method === "none") {
|
|
4337
4520
|
return {
|
|
4338
4521
|
content: [
|
|
@@ -4354,7 +4537,25 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4354
4537
|
}
|
|
4355
4538
|
const filesWritten = [];
|
|
4356
4539
|
try {
|
|
4357
|
-
const
|
|
4540
|
+
const authFileContent = devOnly ? useProxy ? generateDevOnlyProxyContent(
|
|
4541
|
+
method,
|
|
4542
|
+
params,
|
|
4543
|
+
roles,
|
|
4544
|
+
defaultRole,
|
|
4545
|
+
hierarchy,
|
|
4546
|
+
auditEnabled,
|
|
4547
|
+
auditRetentionDays,
|
|
4548
|
+
protectedRoutes
|
|
4549
|
+
) : generateDevOnlyMiddlewareContent(
|
|
4550
|
+
method,
|
|
4551
|
+
params,
|
|
4552
|
+
roles,
|
|
4553
|
+
defaultRole,
|
|
4554
|
+
hierarchy,
|
|
4555
|
+
auditEnabled,
|
|
4556
|
+
auditRetentionDays,
|
|
4557
|
+
protectedRoutes
|
|
4558
|
+
) : useProxy ? generateProxyContent(
|
|
4358
4559
|
method,
|
|
4359
4560
|
params,
|
|
4360
4561
|
roles,
|
|
@@ -4373,15 +4574,18 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4373
4574
|
auditRetentionDays,
|
|
4374
4575
|
protectedRoutes
|
|
4375
4576
|
);
|
|
4376
|
-
writeFileSync6(join6(cwd,
|
|
4377
|
-
filesWritten.push(
|
|
4577
|
+
writeFileSync6(join6(cwd, conventionFile), authFileContent, "utf8");
|
|
4578
|
+
filesWritten.push(conventionFile);
|
|
4378
4579
|
} catch (err) {
|
|
4379
4580
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4380
4581
|
return {
|
|
4381
4582
|
content: [
|
|
4382
4583
|
{
|
|
4383
4584
|
type: "text",
|
|
4384
|
-
text: JSON.stringify({
|
|
4585
|
+
text: JSON.stringify({
|
|
4586
|
+
success: false,
|
|
4587
|
+
error: `Failed writing ${conventionFile}: ${msg}`
|
|
4588
|
+
})
|
|
4385
4589
|
}
|
|
4386
4590
|
],
|
|
4387
4591
|
isError: true
|
|
@@ -4466,7 +4670,8 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4466
4670
|
hierarchy,
|
|
4467
4671
|
auditEnabled,
|
|
4468
4672
|
auditRetentionDays,
|
|
4469
|
-
protectedRoutes
|
|
4673
|
+
protectedRoutes,
|
|
4674
|
+
useProxy
|
|
4470
4675
|
) : generateYamlBlock(
|
|
4471
4676
|
method,
|
|
4472
4677
|
params,
|
|
@@ -4475,7 +4680,8 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4475
4680
|
hierarchy,
|
|
4476
4681
|
auditEnabled,
|
|
4477
4682
|
auditRetentionDays,
|
|
4478
|
-
protectedRoutes
|
|
4683
|
+
protectedRoutes,
|
|
4684
|
+
useProxy
|
|
4479
4685
|
);
|
|
4480
4686
|
const ymlPath = join6(cwd, "stackwright.yml");
|
|
4481
4687
|
if (!existsSync7(ymlPath)) {
|
|
@@ -4510,6 +4716,8 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4510
4716
|
rbacDefaultRole: defaultRole,
|
|
4511
4717
|
protectedRoutesCount: protectedRoutes.length,
|
|
4512
4718
|
filesWritten,
|
|
4719
|
+
convention: useProxy ? "proxy" : "middleware",
|
|
4720
|
+
nextMajorVersion: params.nextMajorVersion ?? null,
|
|
4513
4721
|
securityWarning,
|
|
4514
4722
|
...devOnly ? {
|
|
4515
4723
|
devScripts: {
|
|
@@ -4565,7 +4773,8 @@ function registerAuthTools(server2) {
|
|
|
4565
4773
|
// Injection for tests
|
|
4566
4774
|
_cwd: z13.string().optional(),
|
|
4567
4775
|
devOnly: boolCoerce(z13.boolean().optional()),
|
|
4568
|
-
mockUsers: jsonCoerce(z13.array(z13.object({ name: z13.string(), email: z13.string() })).optional())
|
|
4776
|
+
mockUsers: jsonCoerce(z13.array(z13.object({ name: z13.string(), email: z13.string() })).optional()),
|
|
4777
|
+
nextMajorVersion: numCoerce(z13.number().int().positive().optional())
|
|
4569
4778
|
},
|
|
4570
4779
|
async (params) => {
|
|
4571
4780
|
const cwd = params._cwd ?? process.cwd();
|
|
@@ -4581,11 +4790,11 @@ import { join as join7, basename } from "path";
|
|
|
4581
4790
|
var _checksums = /* @__PURE__ */ new Map([
|
|
4582
4791
|
[
|
|
4583
4792
|
"stackwright-pro-api-otter.json",
|
|
4584
|
-
"
|
|
4793
|
+
"c21f127b9bed477b1d7b66949926e00e1b93de081e792293ebfffc88b70d9424"
|
|
4585
4794
|
],
|
|
4586
4795
|
[
|
|
4587
4796
|
"stackwright-pro-auth-otter.json",
|
|
4588
|
-
"
|
|
4797
|
+
"6d4d4d50f52be84a796c2a43ed997f00b41b42ed8c5f046e1defb2e423933588"
|
|
4589
4798
|
],
|
|
4590
4799
|
[
|
|
4591
4800
|
"stackwright-pro-dashboard-otter.json",
|
|
@@ -4605,7 +4814,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4605
4814
|
],
|
|
4606
4815
|
[
|
|
4607
4816
|
"stackwright-pro-foreman-otter.json",
|
|
4608
|
-
"
|
|
4817
|
+
"e563a99d647fcf95401508f15f10e032fb14e826577bc6bcff5960bf2981d58d"
|
|
4609
4818
|
],
|
|
4610
4819
|
[
|
|
4611
4820
|
"stackwright-pro-geo-otter.json",
|
|
@@ -4613,15 +4822,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4613
4822
|
],
|
|
4614
4823
|
[
|
|
4615
4824
|
"stackwright-pro-page-otter.json",
|
|
4616
|
-
"
|
|
4825
|
+
"cdd878857c1d809c60263693ab0c6cbb45087fd9c3eeda5b4628bd7026d2bded"
|
|
4617
4826
|
],
|
|
4618
4827
|
[
|
|
4619
4828
|
"stackwright-pro-polish-otter.json",
|
|
4620
|
-
"
|
|
4829
|
+
"e5f88d054dd536646f48e5d47cbd64f825f493100002309b90c912fa69ef279e"
|
|
4621
4830
|
],
|
|
4622
4831
|
[
|
|
4623
4832
|
"stackwright-pro-theme-otter.json",
|
|
4624
|
-
"
|
|
4833
|
+
"532d9ca7db6911a09ce5029a8c74c89360bdf6cb8ede8c2636866b509cbe06f9"
|
|
4625
4834
|
],
|
|
4626
4835
|
[
|
|
4627
4836
|
"stackwright-pro-workflow-otter.json",
|
|
@@ -5524,6 +5733,245 @@ function registerScaffoldCleanupTools(server2) {
|
|
|
5524
5733
|
);
|
|
5525
5734
|
}
|
|
5526
5735
|
|
|
5736
|
+
// src/tools/contrast.ts
|
|
5737
|
+
import { z as z16 } from "zod";
|
|
5738
|
+
function linearizeChannel(c255) {
|
|
5739
|
+
const c = c255 / 255;
|
|
5740
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
5741
|
+
}
|
|
5742
|
+
function relativeLuminance(r, g, b) {
|
|
5743
|
+
return 0.2126 * linearizeChannel(r) + 0.7152 * linearizeChannel(g) + 0.0722 * linearizeChannel(b);
|
|
5744
|
+
}
|
|
5745
|
+
function contrastRatioFromLuminance(l1, l2) {
|
|
5746
|
+
const lighter = Math.max(l1, l2);
|
|
5747
|
+
const darker = Math.min(l1, l2);
|
|
5748
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
5749
|
+
}
|
|
5750
|
+
function parseHex(hex) {
|
|
5751
|
+
const clean = hex.replace("#", "").trim().toLowerCase();
|
|
5752
|
+
if (clean.length === 3) {
|
|
5753
|
+
const r = parseInt(clean.charAt(0) + clean.charAt(0), 16);
|
|
5754
|
+
const g = parseInt(clean.charAt(1) + clean.charAt(1), 16);
|
|
5755
|
+
const b = parseInt(clean.charAt(2) + clean.charAt(2), 16);
|
|
5756
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
5757
|
+
return [r, g, b];
|
|
5758
|
+
}
|
|
5759
|
+
if (clean.length === 6) {
|
|
5760
|
+
const r = parseInt(clean.slice(0, 2), 16);
|
|
5761
|
+
const g = parseInt(clean.slice(2, 4), 16);
|
|
5762
|
+
const b = parseInt(clean.slice(4, 6), 16);
|
|
5763
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
5764
|
+
return [r, g, b];
|
|
5765
|
+
}
|
|
5766
|
+
return null;
|
|
5767
|
+
}
|
|
5768
|
+
function hslToRgb(h, s, l) {
|
|
5769
|
+
const hn = h / 360;
|
|
5770
|
+
const sn = s / 100;
|
|
5771
|
+
const ln = l / 100;
|
|
5772
|
+
const hue2rgb = (p, q, t) => {
|
|
5773
|
+
let tt = t;
|
|
5774
|
+
if (tt < 0) tt += 1;
|
|
5775
|
+
if (tt > 1) tt -= 1;
|
|
5776
|
+
if (tt < 1 / 6) return p + (q - p) * 6 * tt;
|
|
5777
|
+
if (tt < 1 / 2) return q;
|
|
5778
|
+
if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6;
|
|
5779
|
+
return p;
|
|
5780
|
+
};
|
|
5781
|
+
let r, g, b;
|
|
5782
|
+
if (sn === 0) {
|
|
5783
|
+
r = g = b = ln;
|
|
5784
|
+
} else {
|
|
5785
|
+
const q = ln < 0.5 ? ln * (1 + sn) : ln + sn - ln * sn;
|
|
5786
|
+
const p = 2 * ln - q;
|
|
5787
|
+
r = hue2rgb(p, q, hn + 1 / 3);
|
|
5788
|
+
g = hue2rgb(p, q, hn);
|
|
5789
|
+
b = hue2rgb(p, q, hn - 1 / 3);
|
|
5790
|
+
}
|
|
5791
|
+
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
|
5792
|
+
}
|
|
5793
|
+
function parseHsl(hsl) {
|
|
5794
|
+
let str = hsl.replace(/^hsl\s*\(\s*/i, "").replace(/\s*\)$/, "").trim();
|
|
5795
|
+
str = str.replace(/%/g, "");
|
|
5796
|
+
const parts = str.split(/[\s,]+/).filter(Boolean);
|
|
5797
|
+
if (parts.length !== 3) return null;
|
|
5798
|
+
const h = parseFloat(parts[0]);
|
|
5799
|
+
const s = parseFloat(parts[1]);
|
|
5800
|
+
const l = parseFloat(parts[2]);
|
|
5801
|
+
if (isNaN(h) || isNaN(s) || isNaN(l)) return null;
|
|
5802
|
+
return hslToRgb(h, s, l);
|
|
5803
|
+
}
|
|
5804
|
+
function parseColor(color) {
|
|
5805
|
+
const trimmed = color.trim();
|
|
5806
|
+
if (trimmed.startsWith("#")) return parseHex(trimmed);
|
|
5807
|
+
if (/^hsl\s*\(/i.test(trimmed)) return parseHsl(trimmed);
|
|
5808
|
+
if (/^\d[\d.]*[\s,]+\d[\d.]*%?[\s,]+\d[\d.]*%?$/.test(trimmed)) return parseHsl(trimmed);
|
|
5809
|
+
return null;
|
|
5810
|
+
}
|
|
5811
|
+
function rgbToHex(r, g, b) {
|
|
5812
|
+
return "#" + [r, g, b].map(
|
|
5813
|
+
(c) => Math.max(0, Math.min(255, Math.round(c))).toString(16).padStart(2, "0")
|
|
5814
|
+
).join("");
|
|
5815
|
+
}
|
|
5816
|
+
function handleCheckContrast(fg, bg) {
|
|
5817
|
+
const fgRgb = parseColor(fg);
|
|
5818
|
+
const bgRgb = parseColor(bg);
|
|
5819
|
+
if (!fgRgb) throw new Error(`Cannot parse foreground color: "${fg}"`);
|
|
5820
|
+
if (!bgRgb) throw new Error(`Cannot parse background color: "${bg}"`);
|
|
5821
|
+
const fgL = relativeLuminance(...fgRgb);
|
|
5822
|
+
const bgL = relativeLuminance(...bgRgb);
|
|
5823
|
+
const ratio = parseFloat(contrastRatioFromLuminance(fgL, bgL).toFixed(2));
|
|
5824
|
+
return {
|
|
5825
|
+
fgHex: rgbToHex(...fgRgb),
|
|
5826
|
+
bgHex: rgbToHex(...bgRgb),
|
|
5827
|
+
ratio,
|
|
5828
|
+
aa: ratio >= 4.5,
|
|
5829
|
+
aaLarge: ratio >= 3,
|
|
5830
|
+
aaa: ratio >= 7,
|
|
5831
|
+
aaaLarge: ratio >= 4.5
|
|
5832
|
+
};
|
|
5833
|
+
}
|
|
5834
|
+
function handleDeriveAccessiblePalette(seed, targetRatio) {
|
|
5835
|
+
const seedRgb = parseColor(seed);
|
|
5836
|
+
if (!seedRgb) throw new Error(`Cannot parse seed color: "${seed}"`);
|
|
5837
|
+
const seedHex = rgbToHex(...seedRgb);
|
|
5838
|
+
const seedL = relativeLuminance(...seedRgb);
|
|
5839
|
+
const whiteRatio = parseFloat(contrastRatioFromLuminance(1, seedL).toFixed(2));
|
|
5840
|
+
const blackRatio = parseFloat(contrastRatioFromLuminance(0, seedL).toFixed(2));
|
|
5841
|
+
const whiteWorks = whiteRatio >= targetRatio;
|
|
5842
|
+
const blackWorks = blackRatio >= targetRatio;
|
|
5843
|
+
let strategy;
|
|
5844
|
+
let foreground;
|
|
5845
|
+
let ratio;
|
|
5846
|
+
let alternativeForeground;
|
|
5847
|
+
let alternativeRatio;
|
|
5848
|
+
if (blackWorks && whiteWorks) {
|
|
5849
|
+
if (blackRatio >= whiteRatio) {
|
|
5850
|
+
strategy = "black";
|
|
5851
|
+
foreground = "#000000";
|
|
5852
|
+
ratio = blackRatio;
|
|
5853
|
+
alternativeForeground = "#ffffff";
|
|
5854
|
+
alternativeRatio = whiteRatio;
|
|
5855
|
+
} else {
|
|
5856
|
+
strategy = "white";
|
|
5857
|
+
foreground = "#ffffff";
|
|
5858
|
+
ratio = whiteRatio;
|
|
5859
|
+
alternativeForeground = "#000000";
|
|
5860
|
+
alternativeRatio = blackRatio;
|
|
5861
|
+
}
|
|
5862
|
+
} else if (blackWorks) {
|
|
5863
|
+
strategy = "black";
|
|
5864
|
+
foreground = "#000000";
|
|
5865
|
+
ratio = blackRatio;
|
|
5866
|
+
alternativeForeground = "#ffffff";
|
|
5867
|
+
alternativeRatio = whiteRatio;
|
|
5868
|
+
} else if (whiteWorks) {
|
|
5869
|
+
strategy = "white";
|
|
5870
|
+
foreground = "#ffffff";
|
|
5871
|
+
ratio = whiteRatio;
|
|
5872
|
+
alternativeForeground = "#000000";
|
|
5873
|
+
alternativeRatio = blackRatio;
|
|
5874
|
+
} else {
|
|
5875
|
+
strategy = "unachievable";
|
|
5876
|
+
if (blackRatio >= whiteRatio) {
|
|
5877
|
+
foreground = "#000000";
|
|
5878
|
+
ratio = blackRatio;
|
|
5879
|
+
alternativeForeground = "#ffffff";
|
|
5880
|
+
alternativeRatio = whiteRatio;
|
|
5881
|
+
} else {
|
|
5882
|
+
foreground = "#ffffff";
|
|
5883
|
+
ratio = whiteRatio;
|
|
5884
|
+
alternativeForeground = "#000000";
|
|
5885
|
+
alternativeRatio = blackRatio;
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5888
|
+
return {
|
|
5889
|
+
seedHex,
|
|
5890
|
+
foreground,
|
|
5891
|
+
ratio,
|
|
5892
|
+
aa: ratio >= 4.5,
|
|
5893
|
+
aaLarge: ratio >= 3,
|
|
5894
|
+
aaa: ratio >= 7,
|
|
5895
|
+
meetsTarget: ratio >= targetRatio,
|
|
5896
|
+
strategy,
|
|
5897
|
+
alternativeForeground,
|
|
5898
|
+
alternativeRatio
|
|
5899
|
+
};
|
|
5900
|
+
}
|
|
5901
|
+
var PASS = "[PASS]";
|
|
5902
|
+
var FAIL = "[FAIL]";
|
|
5903
|
+
var mark = (ok) => ok ? PASS : FAIL;
|
|
5904
|
+
function registerContrastTools(server2) {
|
|
5905
|
+
server2.tool(
|
|
5906
|
+
"stackwright_pro_check_contrast",
|
|
5907
|
+
'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%").',
|
|
5908
|
+
{
|
|
5909
|
+
fg: z16.string().describe("Foreground (text) color \u2014 hex or HSL"),
|
|
5910
|
+
bg: z16.string().describe("Background color \u2014 hex or HSL")
|
|
5911
|
+
},
|
|
5912
|
+
async ({ fg, bg }) => {
|
|
5913
|
+
let result;
|
|
5914
|
+
try {
|
|
5915
|
+
result = handleCheckContrast(fg, bg);
|
|
5916
|
+
} catch (err) {
|
|
5917
|
+
return {
|
|
5918
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
5919
|
+
};
|
|
5920
|
+
}
|
|
5921
|
+
const text = [
|
|
5922
|
+
`WCAG 2.1 Contrast Check`,
|
|
5923
|
+
``,
|
|
5924
|
+
` Foreground : ${result.fgHex}`,
|
|
5925
|
+
` Background : ${result.bgHex}`,
|
|
5926
|
+
` Ratio : ${result.ratio}:1`,
|
|
5927
|
+
``,
|
|
5928
|
+
` ${mark(result.aa)} AA \u2014 normal text (\u22654.5:1)`,
|
|
5929
|
+
` ${mark(result.aaLarge)} AA \u2014 large text (\u22653.0:1)`,
|
|
5930
|
+
` ${mark(result.aaa)} AAA \u2014 normal text (\u22657.0:1)`,
|
|
5931
|
+
` ${mark(result.aaaLarge)} AAA \u2014 large text (\u22654.5:1)`
|
|
5932
|
+
].join("\n");
|
|
5933
|
+
return { content: [{ type: "text", text }] };
|
|
5934
|
+
}
|
|
5935
|
+
);
|
|
5936
|
+
server2.tool(
|
|
5937
|
+
"stackwright_pro_derive_accessible_palette",
|
|
5938
|
+
'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%").',
|
|
5939
|
+
{
|
|
5940
|
+
seed: z16.string().describe("Background (seed) color \u2014 hex or HSL"),
|
|
5941
|
+
targetRatio: numCoerce(z16.number().default(4.5)).describe(
|
|
5942
|
+
"Minimum required contrast ratio (default 4.5 for WCAG AA)"
|
|
5943
|
+
)
|
|
5944
|
+
},
|
|
5945
|
+
async ({ seed, targetRatio }) => {
|
|
5946
|
+
let result;
|
|
5947
|
+
try {
|
|
5948
|
+
result = handleDeriveAccessiblePalette(seed, targetRatio ?? 4.5);
|
|
5949
|
+
} catch (err) {
|
|
5950
|
+
return {
|
|
5951
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
5952
|
+
};
|
|
5953
|
+
}
|
|
5954
|
+
const metLine = result.meetsTarget ? `${PASS} Meets target (${targetRatio}:1)` : `${FAIL} Target (${targetRatio}:1) UNACHIEVABLE with white or black \u2014 best available used`;
|
|
5955
|
+
const altNote = result.alternativeRatio >= (targetRatio ?? 4.5) ? `also passes (${result.alternativeRatio}:1)` : `fails at ${result.alternativeRatio}:1`;
|
|
5956
|
+
const text = [
|
|
5957
|
+
`Accessible Palette Derivation`,
|
|
5958
|
+
``,
|
|
5959
|
+
` Background (seed) : ${result.seedHex}`,
|
|
5960
|
+
` Foreground : ${result.foreground} (${result.strategy})`,
|
|
5961
|
+
` Contrast ratio : ${result.ratio}:1`,
|
|
5962
|
+
` ${metLine}`,
|
|
5963
|
+
``,
|
|
5964
|
+
` ${mark(result.aa)} AA \u2014 normal text (\u22654.5:1)`,
|
|
5965
|
+
` ${mark(result.aaLarge)} AA \u2014 large text (\u22653.0:1)`,
|
|
5966
|
+
` ${mark(result.aaa)} AAA \u2014 normal text (\u22657.0:1)`,
|
|
5967
|
+
``,
|
|
5968
|
+
` Alternative (${result.alternativeForeground}): ${altNote}`
|
|
5969
|
+
].join("\n");
|
|
5970
|
+
return { content: [{ type: "text", text }] };
|
|
5971
|
+
}
|
|
5972
|
+
);
|
|
5973
|
+
}
|
|
5974
|
+
|
|
5527
5975
|
// package.json
|
|
5528
5976
|
var package_default = {
|
|
5529
5977
|
dependencies: {
|
|
@@ -5547,7 +5995,7 @@ var package_default = {
|
|
|
5547
5995
|
"test:coverage": "vitest run --coverage"
|
|
5548
5996
|
},
|
|
5549
5997
|
name: "@stackwright-pro/mcp",
|
|
5550
|
-
version: "0.2.0-alpha.
|
|
5998
|
+
version: "0.2.0-alpha.71",
|
|
5551
5999
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
5552
6000
|
license: "SEE LICENSE IN LICENSE",
|
|
5553
6001
|
main: "./dist/server.js",
|
|
@@ -5602,6 +6050,7 @@ registerArtifactSigningTools(server);
|
|
|
5602
6050
|
registerDomainTools(server);
|
|
5603
6051
|
registerTypeSchemasTool(server);
|
|
5604
6052
|
registerScaffoldCleanupTools(server);
|
|
6053
|
+
registerContrastTools(server);
|
|
5605
6054
|
async function main() {
|
|
5606
6055
|
const transport = new StdioServerTransport();
|
|
5607
6056
|
try {
|