Skip to content

Commit 32dbb40

Browse files
Use monotonic UUID v7 for task IDs with polyfill for older targets
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
1 parent a80c8b4 commit 32dbb40

1 file changed

Lines changed: 57 additions & 1 deletion

File tree

src/ModelContextProtocol.Core/Server/InMemoryMcpTaskStore.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,63 @@ public void Dispose()
448448
_cleanupTimer?.Dispose();
449449
}
450450

451-
private static string GenerateTaskId() => Guid.NewGuid().ToString("N");
451+
#if NET9_0_OR_GREATER
452+
private static string GenerateTaskId() => Guid.CreateVersion7().ToString("N");
453+
#else
454+
private static string GenerateTaskId() => CreateVersion7Guid().ToString("N");
455+
456+
/// <summary>
457+
/// Polyfill for Guid.CreateVersion7() on targets earlier than .NET 9.
458+
/// Generates a UUID v7 with embedded timestamp for monotonic ordering.
459+
/// </summary>
460+
private static Guid CreateVersion7Guid()
461+
{
462+
// UUID v7 format (RFC 9562):
463+
// - 48 bits: Unix timestamp in milliseconds (big-endian)
464+
// - 4 bits: version (0111 = 7)
465+
// - 12 bits: random
466+
// - 2 bits: variant (10)
467+
// - 62 bits: random
468+
469+
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
470+
byte[] bytes = new byte[16];
471+
472+
// Fill with random data first
473+
#if NETSTANDARD2_0
474+
using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
475+
{
476+
rng.GetBytes(bytes);
477+
}
478+
#else
479+
System.Security.Cryptography.RandomNumberGenerator.Fill(bytes);
480+
#endif
481+
482+
// Set timestamp (48 bits, big-endian) in first 6 bytes
483+
bytes[0] = (byte)(timestamp >> 40);
484+
bytes[1] = (byte)(timestamp >> 32);
485+
bytes[2] = (byte)(timestamp >> 24);
486+
bytes[3] = (byte)(timestamp >> 16);
487+
bytes[4] = (byte)(timestamp >> 8);
488+
bytes[5] = (byte)timestamp;
489+
490+
// Set version 7 (0111) in high nibble of byte 6
491+
bytes[6] = (byte)((bytes[6] & 0x0F) | 0x70);
492+
493+
// Set variant (10) in high 2 bits of byte 8
494+
bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80);
495+
496+
// Convert from big-endian byte array to Guid
497+
// Guid constructor expects bytes in a specific order for the first 8 bytes
498+
// (little-endian for the first three components on Windows)
499+
// We need to swap bytes to match the Guid's internal layout
500+
return new Guid(
501+
(int)(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]), // a (big-endian to int)
502+
(short)(bytes[4] << 8 | bytes[5]), // b (big-endian to short)
503+
(short)(bytes[6] << 8 | bytes[7]), // c (big-endian to short)
504+
bytes[8], bytes[9], bytes[10], bytes[11],
505+
bytes[12], bytes[13], bytes[14], bytes[15]);
506+
}
507+
#endif
452508

453509
private static bool IsTerminalStatus(McpTaskStatus status) =>
454510
status is McpTaskStatus.Completed or McpTaskStatus.Failed or McpTaskStatus.Cancelled;

0 commit comments

Comments
 (0)