@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/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: ./middleware.ts
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: ./middleware.ts
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: ./middleware.ts
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 generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
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: ./middleware.ts
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: ./middleware.ts
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: ./middleware.ts
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 middlewareContent = devOnly ? generateDevOnlyMiddlewareContent(
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, "middleware.ts"), middlewareContent, "utf8");
4377
- filesWritten.push("middleware.ts");
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({ success: false, error: `Failed writing middleware.ts: ${msg}` })
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
- "9fbaed0ce6116b82d0289f24432037d04637c89b8e73062ed946e5d49b294734"
4793
+ "c21f127b9bed477b1d7b66949926e00e1b93de081e792293ebfffc88b70d9424"
4585
4794
  ],
4586
4795
  [
4587
4796
  "stackwright-pro-auth-otter.json",
4588
- "c3be215f05cc48ec51ed75541184b89a8ac123463b80fe02f2adb6c1b5163853"
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
- "5f18e37ee3f2064c62a2d01dfc541285f27e5762d0b1011e2a37a48af96c74c8"
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
- "1dcdf866be76d18f3aa2a9e56b8bf57a3d43477b43c410eb6d1feac0e7683658"
4825
+ "cdd878857c1d809c60263693ab0c6cbb45087fd9c3eeda5b4628bd7026d2bded"
4617
4826
  ],
4618
4827
  [
4619
4828
  "stackwright-pro-polish-otter.json",
4620
- "72a52901b4781c9f0be97024ba6e19415cdfbd92369d9274aa3abe7efd208bc8"
4829
+ "e5f88d054dd536646f48e5d47cbd64f825f493100002309b90c912fa69ef279e"
4621
4830
  ],
4622
4831
  [
4623
4832
  "stackwright-pro-theme-otter.json",
4624
- "e8530a1146abc57eb31d2f036ba057d5eae7d4a2d9dca00e415d1c352ef4c5b5"
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.68",
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 {