@corva/ui 3.78.0-18 → 3.78.0-19

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.
Files changed (2) hide show
  1. package/mcp-server/server.mjs +102 -71
  2. package/package.json +1 -1
@@ -246,9 +246,9 @@ const validateDsn = dsn => {
246
246
  return url.href;
247
247
  };
248
248
 
249
- const MCP_SERVER_VERSION = '1.9.0';
249
+ const MCP_SERVER_VERSION = '1.9.1';
250
250
 
251
- var version = "3.78.0-18";
251
+ var version = "3.78.0-19";
252
252
 
253
253
  const CORVA_UI_VERSION = version;
254
254
 
@@ -1010,6 +1010,77 @@ const resolveProjectIdentity = async (server, logger) => {
1010
1010
  }
1011
1011
  };
1012
1012
 
1013
+ const createToolResponse = text => ({
1014
+ content: [{
1015
+ type: 'text',
1016
+ text
1017
+ }]
1018
+ });
1019
+
1020
+ const createErrorResponse = message => ({
1021
+ content: [{
1022
+ type: 'text',
1023
+ text: message
1024
+ }],
1025
+ isError: true
1026
+ });
1027
+
1028
+ const withTimeout = async (promise, ms) => {
1029
+ let timeoutId;
1030
+ const timeout = new Promise(resolve => {
1031
+ timeoutId = setTimeout(() => resolve(undefined), ms);
1032
+ });
1033
+ try {
1034
+ return await Promise.race([promise, timeout]);
1035
+ } finally {
1036
+ clearTimeout(timeoutId);
1037
+ }
1038
+ };
1039
+
1040
+ /**
1041
+ * A field of a listing is ONE line: every whitespace run becomes a space, the ends are trimmed,
1042
+ * every other character survives. Not the first paragraph (drops words) and not indentation
1043
+ * (CommonMark honours up to three spaces, so a fence, heading or blank line inside a description
1044
+ * would still open a block of its own).
1045
+ */
1046
+ const singleLine = text => text.replace(/\s+/g, ' ').trim();
1047
+
1048
+ // The SDK gives roots/list 60 s, and a session that ends inside the wait would never be counted.
1049
+ const IDENTITY_FLUSH_TIMEOUT_MS = 2000;
1050
+ /**
1051
+ * Records the session counter and the initialize span exactly once, with the project identity when
1052
+ * it resolves within IDENTITY_FLUSH_TIMEOUT_MS. The deadline starts at the call, so call it only
1053
+ * once telemetry has settled; an identity that resolves later is ignored here.
1054
+ */
1055
+ const recordSessionStart = async ({
1056
+ telemetry,
1057
+ initData,
1058
+ identity
1059
+ }) => {
1060
+ if (!telemetry.isEnabled()) {
1061
+ return;
1062
+ }
1063
+ const projectIdentity = await withTimeout(identity.catch(() => null), IDENTITY_FLUSH_TIMEOUT_MS);
1064
+ const {
1065
+ timestamp,
1066
+ clientName,
1067
+ clientVersion,
1068
+ clientCapabilities
1069
+ } = initData;
1070
+ telemetry.metrics.recordSession(clientName, clientVersion, projectIdentity);
1071
+ telemetry.recordInitialize({
1072
+ timestamp,
1073
+ clientName,
1074
+ clientVersion,
1075
+ clientCapabilities,
1076
+ sessionId: telemetry.sessionId,
1077
+ protocolVersion: MCP_PROTOCOL_VERSION,
1078
+ networkTransport: MCP_NETWORK_TRANSPORT,
1079
+ appKey: projectIdentity?.appKey ?? undefined,
1080
+ packageName: projectIdentity?.packageName ?? undefined
1081
+ });
1082
+ };
1083
+
1013
1084
  // This file is auto-generated by `yarn mcp:generate-prompts`. Do not edit by hand.
1014
1085
  // Source: ./figma-to-code.prompt.md
1015
1086
 
@@ -21548,41 +21619,6 @@ const formatZIndex = zIndex => {
21548
21619
  return table(['Name', 'CSS Variable', 'Value'], rows);
21549
21620
  };
21550
21621
 
21551
- const createToolResponse = text => ({
21552
- content: [{
21553
- type: 'text',
21554
- text
21555
- }]
21556
- });
21557
-
21558
- const createErrorResponse = message => ({
21559
- content: [{
21560
- type: 'text',
21561
- text: message
21562
- }],
21563
- isError: true
21564
- });
21565
-
21566
- const withTimeout = async (promise, ms) => {
21567
- let timeoutId;
21568
- const timeout = new Promise(resolve => {
21569
- timeoutId = setTimeout(() => resolve(undefined), ms);
21570
- });
21571
- try {
21572
- return await Promise.race([promise, timeout]);
21573
- } finally {
21574
- clearTimeout(timeoutId);
21575
- }
21576
- };
21577
-
21578
- /**
21579
- * A field of a listing is ONE line: every whitespace run becomes a space, the ends are trimmed,
21580
- * every other character survives. Not the first paragraph (drops words) and not indentation
21581
- * (CommonMark honours up to three spaces, so a fence, heading or blank line inside a description
21582
- * would still open a block of its own).
21583
- */
21584
- const singleLine = text => text.replace(/\s+/g, ' ').trim();
21585
-
21586
21622
  const parseQuery = queryLower => {
21587
21623
  const rawWords = queryLower.trim().split(/\s+/);
21588
21624
  const queryWords = rawWords.filter(w => w.length > 1);
@@ -23385,9 +23421,9 @@ class CorvaUiMcpServer {
23385
23421
  telemetryDisabledReason = null;
23386
23422
  telemetryInitPromise = null;
23387
23423
  shutdownRequested = false;
23388
- pendingInitData = null;
23389
23424
  projectIdentity = null;
23390
23425
  identityPromise = null;
23426
+ sessionStartPromise = null;
23391
23427
  constructor() {
23392
23428
  const baseLogger = createLogger({
23393
23429
  prefix: '[CorvaUI MCP]'
@@ -23411,50 +23447,42 @@ class CorvaUiMcpServer {
23411
23447
  }
23412
23448
  setupInitializedHandler() {
23413
23449
  this.server.server.oninitialized = () => {
23450
+ // The SDK calls this for every notifications/initialized, and a repeat must not count a
23451
+ // second session.
23452
+ if (this.sessionStartPromise) {
23453
+ return;
23454
+ }
23414
23455
  const timestamp = Date.now();
23415
23456
  const clientInfo = this.server.server.getClientVersion();
23416
23457
  const clientCapabilities = this.server.server.getClientCapabilities();
23417
- this.pendingInitData = {
23418
- timestamp,
23419
- clientName: clientInfo?.name,
23420
- clientVersion: clientInfo?.version,
23421
- clientCapabilities: clientCapabilities ? JSON.stringify(clientCapabilities) : undefined
23422
- };
23423
23458
  if (clientInfo?.name) {
23424
23459
  this.mcpLogger.info(`Client connected: ${clientInfo.name}${clientInfo.version ? ` v${clientInfo.version}` : ''}`);
23425
23460
  }
23426
- this.identityPromise = resolveProjectIdentity(this.server.server, this.mcpLogger).then(identity => {
23427
- this.projectIdentity = identity;
23461
+ const identity = resolveProjectIdentity(this.server.server, this.mcpLogger).then(resolved => {
23462
+ this.projectIdentity = resolved;
23463
+ return resolved;
23428
23464
  }).catch(error => {
23429
23465
  this.mcpLogger.error('Failed to resolve project identity', error);
23430
- }).finally(() => {
23431
- this.flushPendingInitData();
23466
+ return null;
23467
+ });
23468
+ this.identityPromise = identity.then(() => undefined);
23469
+
23470
+ // run() connects only after telemetry has settled, so initialized is the later of the two
23471
+ // and the identity deadline starts here.
23472
+ this.sessionStartPromise = recordSessionStart({
23473
+ telemetry: this.telemetry,
23474
+ initData: {
23475
+ timestamp,
23476
+ clientName: clientInfo?.name,
23477
+ clientVersion: clientInfo?.version,
23478
+ clientCapabilities: clientCapabilities ? JSON.stringify(clientCapabilities) : undefined
23479
+ },
23480
+ identity
23481
+ }).catch(error => {
23482
+ this.mcpLogger.error('Failed to record session start', error);
23432
23483
  });
23433
23484
  };
23434
23485
  }
23435
- flushPendingInitData() {
23436
- if (!this.pendingInitData || !this.telemetry.isEnabled()) {
23437
- return;
23438
- }
23439
- const {
23440
- timestamp,
23441
- clientName,
23442
- clientVersion,
23443
- clientCapabilities
23444
- } = this.pendingInitData;
23445
- this.telemetry.metrics.recordSession(clientName, clientVersion);
23446
- const attrs = {
23447
- timestamp,
23448
- clientName,
23449
- clientVersion,
23450
- clientCapabilities,
23451
- sessionId: this.telemetry.sessionId,
23452
- protocolVersion: MCP_PROTOCOL_VERSION,
23453
- networkTransport: MCP_NETWORK_TRANSPORT
23454
- };
23455
- this.telemetry.recordInitialize(attrs);
23456
- this.pendingInitData = null;
23457
- }
23458
23486
  executeToolWithObservability(toolName, args, handler, formatResult, options) {
23459
23487
  this.requestCount += 1;
23460
23488
  const startTime = Date.now();
@@ -23663,6 +23691,9 @@ class CorvaUiMcpServer {
23663
23691
  if (this.identityPromise) {
23664
23692
  await withTimeout(this.identityPromise, TELEMETRY_INIT_TIMEOUT_MS);
23665
23693
  }
23694
+ if (this.sessionStartPromise) {
23695
+ await withTimeout(this.sessionStartPromise, IDENTITY_FLUSH_TIMEOUT_MS);
23696
+ }
23666
23697
 
23667
23698
  // No transport means shutdown came during the telemetry wait: the client never connected, so
23668
23699
  // there is no session to end.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corva/ui",
3
- "version": "3.78.0-18",
3
+ "version": "3.78.0-19",
4
4
  "license": "UNLICENSED",
5
5
  "description": "Shared components/utils for Corva ui projects",
6
6
  "repository": {