@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.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: ./middleware.ts
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: ./middleware.ts
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: ./middleware.ts
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 generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
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: ./middleware.ts
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: ./middleware.ts
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: ./middleware.ts
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 middlewareContent = devOnly ? generateDevOnlyMiddlewareContent(
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, "middleware.ts"), middlewareContent, "utf8");
4385
- filesWritten.push("middleware.ts");
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({ success: false, error: `Failed writing middleware.ts: ${msg}` })
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
- "9fbaed0ce6116b82d0289f24432037d04637c89b8e73062ed946e5d49b294734"
4801
+ "c21f127b9bed477b1d7b66949926e00e1b93de081e792293ebfffc88b70d9424"
4593
4802
  ],
4594
4803
  [
4595
4804
  "stackwright-pro-auth-otter.json",
4596
- "c3be215f05cc48ec51ed75541184b89a8ac123463b80fe02f2adb6c1b5163853"
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
- "5f18e37ee3f2064c62a2d01dfc541285f27e5762d0b1011e2a37a48af96c74c8"
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
- "1dcdf866be76d18f3aa2a9e56b8bf57a3d43477b43c410eb6d1feac0e7683658"
4833
+ "cdd878857c1d809c60263693ab0c6cbb45087fd9c3eeda5b4628bd7026d2bded"
4625
4834
  ],
4626
4835
  [
4627
4836
  "stackwright-pro-polish-otter.json",
4628
- "72a52901b4781c9f0be97024ba6e19415cdfbd92369d9274aa3abe7efd208bc8"
4837
+ "e5f88d054dd536646f48e5d47cbd64f825f493100002309b90c912fa69ef279e"
4629
4838
  ],
4630
4839
  [
4631
4840
  "stackwright-pro-theme-otter.json",
4632
- "e8530a1146abc57eb31d2f036ba057d5eae7d4a2d9dca00e415d1c352ef4c5b5"
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.68",
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 {