portkey-admin-mcp 0.4.2 → 0.6.0

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 (4) hide show
  1. package/README.md +89 -34
  2. package/build/index.js +1243 -638
  3. package/build/server.js +2275 -845
  4. package/package.json +18 -14
package/build/server.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { pathToFileURL } from "node:url";
5
5
 
6
6
  // src/lib/http-app.ts
7
- import { randomUUID } from "node:crypto";
7
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
8
8
  import { readFileSync as readFileSync2 } from "node:fs";
9
9
  import {
10
10
  createServer as createHttpServer
@@ -12,7 +12,9 @@ import {
12
12
  import {
13
13
  createServer as createHttpsServer
14
14
  } from "node:https";
15
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
15
+ import {
16
+ StreamableHTTPServerTransport
17
+ } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
16
18
  import {
17
19
  isInitializeRequest,
18
20
  LATEST_PROTOCOL_VERSION,
@@ -20,6 +22,7 @@ import {
20
22
  } from "@modelcontextprotocol/sdk/types.js";
21
23
  import cors from "cors";
22
24
  import express from "express";
25
+ import { rateLimit as expressRateLimit } from "express-rate-limit";
23
26
  import helmet from "helmet";
24
27
 
25
28
  // src/services/base.service.ts
@@ -134,6 +137,7 @@ var Logger = {
134
137
  // src/services/base.service.ts
135
138
  var DEFAULT_BASE_URL = "https://api.portkey.ai/v1";
136
139
  var PRIVATE_BASE_URL_OVERRIDE_HINT = "Set PORTKEY_ALLOW_PRIVATE_BASE_URL=true to allow self-hosted gateways on loopback or private networks.";
140
+ var INSECURE_HTTP_OVERRIDE_HINT = "Set PORTKEY_ALLOW_INSECURE_HTTP=true only for a trusted self-hosted gateway when TLS is unavailable.";
137
141
  function isPrivateOrLocalHost(hostname) {
138
142
  const host = hostname.trim().toLowerCase().replace(/^\[|\]$/g, "");
139
143
  if (host === "localhost" || host.endsWith(".localhost")) {
@@ -200,6 +204,14 @@ function validateUrl(url) {
200
204
  `Refusing to use a loopback or private-network PORTKEY_BASE_URL host: ${parsed.hostname}. ${PRIVATE_BASE_URL_OVERRIDE_HINT}`
201
205
  );
202
206
  }
207
+ const allowInsecureHttp = /^(1|true|yes)$/i.test(
208
+ process.env.PORTKEY_ALLOW_INSECURE_HTTP?.trim() ?? ""
209
+ );
210
+ if (parsed.protocol === "http:" && !allowInsecureHttp) {
211
+ throw new Error(
212
+ `Refusing insecure HTTP PORTKEY_BASE_URL: ${parsed.origin}. ${INSECURE_HTTP_OVERRIDE_HINT}`
213
+ );
214
+ }
203
215
  }
204
216
  var BaseService = class {
205
217
  apiKey;
@@ -211,13 +223,9 @@ var BaseService = class {
211
223
  throw new Error("PORTKEY_API_KEY environment variable is not set");
212
224
  }
213
225
  this.apiKey = apiKey;
214
- if (baseUrlOverride !== void 0) {
215
- this.baseUrl = baseUrlOverride;
216
- } else {
217
- const baseUrl = process.env.PORTKEY_BASE_URL ?? DEFAULT_BASE_URL;
218
- validateUrl(baseUrl);
219
- this.baseUrl = baseUrl;
220
- }
226
+ const baseUrl = baseUrlOverride ?? process.env.PORTKEY_BASE_URL ?? DEFAULT_BASE_URL;
227
+ validateUrl(baseUrl);
228
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
221
229
  }
222
230
  encodePathSegment(value) {
223
231
  return encodeURIComponent(value);
@@ -253,6 +261,7 @@ var BaseService = class {
253
261
  try {
254
262
  const response = await fetchWithTimeout(url, {
255
263
  method,
264
+ redirect: "manual",
256
265
  headers: this.buildHeaders(method),
257
266
  body: this.serializeBody(options.body),
258
267
  timeout: this.timeout
@@ -781,6 +790,12 @@ var KeysService = class extends BaseService {
781
790
  await this.put(`/api-keys/${this.encodePathSegment(id)}`, data);
782
791
  return { success: true };
783
792
  }
793
+ async rotateApiKey(id, data = {}) {
794
+ return this.post(
795
+ `/api-keys/${this.encodePathSegment(id)}/rotate`,
796
+ data
797
+ );
798
+ }
784
799
  async deleteApiKey(id) {
785
800
  await this.delete(`/api-keys/${this.encodePathSegment(id)}`);
786
801
  return { success: true };
@@ -1490,6 +1505,31 @@ var ProvidersService = class extends BaseService {
1490
1505
  }
1491
1506
  };
1492
1507
 
1508
+ // src/services/secret-references.service.ts
1509
+ var SecretReferencesService = class extends BaseService {
1510
+ async createSecretReference(data) {
1511
+ return this.post("/secret-references", data);
1512
+ }
1513
+ async listSecretReferences(params) {
1514
+ return this.get("/secret-references", params);
1515
+ }
1516
+ async getSecretReference(id) {
1517
+ return this.get(
1518
+ `/secret-references/${this.encodePathSegment(id)}`
1519
+ );
1520
+ }
1521
+ async updateSecretReference(id, data) {
1522
+ return this.put(
1523
+ `/secret-references/${this.encodePathSegment(id)}`,
1524
+ data
1525
+ );
1526
+ }
1527
+ async deleteSecretReference(id) {
1528
+ await this.delete(`/secret-references/${this.encodePathSegment(id)}`);
1529
+ return { success: true };
1530
+ }
1531
+ };
1532
+
1493
1533
  // src/services/tracing.service.ts
1494
1534
  var TracingService = class extends BaseService {
1495
1535
  // Feedback endpoints
@@ -1640,8 +1680,9 @@ var WorkspacesService = class extends BaseService {
1640
1680
  };
1641
1681
 
1642
1682
  // src/services/index.ts
1643
- import crypto2 from "node:crypto";
1683
+ import { createHmac, randomBytes } from "node:crypto";
1644
1684
  var MISSING_API_KEY_PLACEHOLDER = "__PORTKEY_API_KEY_NOT_CONFIGURED__";
1685
+ var SERVICE_CACHE_HMAC_KEY = randomBytes(32);
1645
1686
  function resolvePortkeyApiKey(apiKey) {
1646
1687
  const resolvedApiKey = apiKey ?? process.env.PORTKEY_API_KEY;
1647
1688
  if (!resolvedApiKey) {
@@ -1655,7 +1696,7 @@ function resolveSharedPortkeyApiKey(apiKey) {
1655
1696
  return apiKey ?? process.env.PORTKEY_API_KEY ?? MISSING_API_KEY_PLACEHOLDER;
1656
1697
  }
1657
1698
  function getSharedServiceCacheKey(apiKey) {
1658
- const keyDigest = crypto2.createHash("sha256").update(apiKey).digest("hex");
1699
+ const keyDigest = createHmac("sha256", SERVICE_CACHE_HMAC_KEY).update(apiKey).digest("hex");
1659
1700
  return JSON.stringify({
1660
1701
  apiKey: keyDigest,
1661
1702
  baseUrl: process.env.PORTKEY_BASE_URL?.trim() || ""
@@ -1679,6 +1720,7 @@ var PortkeyService = class {
1679
1720
  tracing;
1680
1721
  logging;
1681
1722
  providers;
1723
+ secretReferences;
1682
1724
  mcpIntegrations;
1683
1725
  mcpServers;
1684
1726
  health;
@@ -1705,6 +1747,10 @@ var PortkeyService = class {
1705
1747
  this.tracing = new TracingService(resolvedApiKey, resolvedBaseUrl);
1706
1748
  this.logging = new LoggingService(resolvedApiKey, resolvedBaseUrl);
1707
1749
  this.providers = new ProvidersService(resolvedApiKey, resolvedBaseUrl);
1750
+ this.secretReferences = new SecretReferencesService(
1751
+ resolvedApiKey,
1752
+ resolvedBaseUrl
1753
+ );
1708
1754
  this.mcpIntegrations = new McpIntegrationsService(
1709
1755
  resolvedApiKey,
1710
1756
  resolvedBaseUrl
@@ -1726,52 +1772,92 @@ function getSharedPortkeyService(apiKey) {
1726
1772
  }
1727
1773
 
1728
1774
  // src/tools/index.ts
1729
- import { z as z20 } from "zod";
1775
+ import { z as z22 } from "zod";
1730
1776
 
1731
1777
  // src/lib/type-guards.ts
1732
1778
  function isRecord(value) {
1733
1779
  return typeof value === "object" && value !== null;
1734
1780
  }
1735
1781
 
1736
- // src/tools/analytics.tools.ts
1782
+ // src/schemas/contracts/secret-references.contract.ts
1737
1783
  import { z } from "zod";
1784
+ var SecretManagerTypeSchema = z.enum([
1785
+ "aws_sm",
1786
+ "azure_kv",
1787
+ "hashicorp_vault"
1788
+ ]);
1789
+ var SecretReferenceListItemSchema = z.object({
1790
+ id: z.string().uuid(),
1791
+ name: z.string(),
1792
+ slug: z.string(),
1793
+ manager_type: SecretManagerTypeSchema,
1794
+ status: z.literal("ACTIVE"),
1795
+ created_at: z.iso.datetime(),
1796
+ last_updated_at: z.iso.datetime(),
1797
+ object: z.literal("secret-reference")
1798
+ });
1799
+ var ListSecretReferencesResponseSchema = z.object({
1800
+ object: z.literal("list"),
1801
+ total: z.number().int().nonnegative(),
1802
+ data: z.array(SecretReferenceListItemSchema)
1803
+ });
1804
+ var SecretReferenceDetailSchema = SecretReferenceListItemSchema.extend(
1805
+ {
1806
+ organisation_id: z.string().uuid(),
1807
+ description: z.string().nullable(),
1808
+ secret_path: z.string(),
1809
+ secret_key: z.string().nullable(),
1810
+ allow_all_workspaces: z.boolean(),
1811
+ tags: z.record(z.string(), z.string()).nullable(),
1812
+ created_by: z.string(),
1813
+ auth_config: z.record(z.string(), z.unknown())
1814
+ }
1815
+ );
1816
+ var CreateSecretReferenceResponseSchema = z.object({
1817
+ id: z.string().uuid(),
1818
+ slug: z.string(),
1819
+ object: z.literal("secret-reference")
1820
+ });
1821
+
1822
+ // src/tools/analytics.tools.ts
1823
+ import { z as z2 } from "zod";
1738
1824
  var baseAnalyticsSchema = {
1739
- time_of_generation_min: z.string().describe(
1825
+ time_of_generation_min: z2.string().describe(
1740
1826
  "Start time for the analytics period (ISO8601 format, e.g., '2024-01-01T00:00:00Z')"
1741
1827
  ),
1742
- time_of_generation_max: z.string().describe(
1828
+ time_of_generation_max: z2.string().describe(
1743
1829
  "End time for the analytics period (ISO8601 format, e.g., '2024-02-01T00:00:00Z')"
1744
1830
  ),
1745
- total_units_min: z.coerce.number().positive().optional().describe("Minimum number of total tokens to filter by"),
1746
- total_units_max: z.coerce.number().positive().optional().describe("Maximum number of total tokens to filter by"),
1747
- cost_min: z.coerce.number().positive().optional().describe("Minimum cost in cents to filter by"),
1748
- cost_max: z.coerce.number().positive().optional().describe("Maximum cost in cents to filter by"),
1749
- prompt_token_min: z.coerce.number().positive().optional().describe("Minimum number of prompt tokens"),
1750
- prompt_token_max: z.coerce.number().positive().optional().describe("Maximum number of prompt tokens"),
1751
- completion_token_min: z.coerce.number().positive().optional().describe("Minimum number of completion tokens"),
1752
- completion_token_max: z.coerce.number().positive().optional().describe("Maximum number of completion tokens"),
1753
- status_code: z.string().optional().describe(
1831
+ total_units_min: z2.coerce.number().positive().optional().describe("Minimum number of total tokens to filter by"),
1832
+ total_units_max: z2.coerce.number().positive().optional().describe("Maximum number of total tokens to filter by"),
1833
+ cost_min: z2.coerce.number().positive().optional().describe("Minimum cost in cents to filter by"),
1834
+ cost_max: z2.coerce.number().positive().optional().describe("Maximum cost in cents to filter by"),
1835
+ prompt_token_min: z2.coerce.number().positive().optional().describe("Minimum number of prompt tokens"),
1836
+ prompt_token_max: z2.coerce.number().positive().optional().describe("Maximum number of prompt tokens"),
1837
+ completion_token_min: z2.coerce.number().positive().optional().describe("Minimum number of completion tokens"),
1838
+ completion_token_max: z2.coerce.number().positive().optional().describe("Maximum number of completion tokens"),
1839
+ status_code: z2.string().optional().describe(
1754
1840
  "Legacy Portkey query param for HTTP status codes. Comma-separated string; prefer status_codes for structured inputs."
1755
1841
  ),
1756
- weighted_feedback_min: z.coerce.number().min(-10).max(10).optional().describe("Minimum weighted feedback score (-10 to 10)"),
1757
- weighted_feedback_max: z.coerce.number().min(-10).max(10).optional().describe("Maximum weighted feedback score (-10 to 10)"),
1758
- virtual_keys: z.string().optional().describe(
1842
+ weighted_feedback_min: z2.coerce.number().min(-10).max(10).optional().describe("Minimum weighted feedback score (-10 to 10)"),
1843
+ weighted_feedback_max: z2.coerce.number().min(-10).max(10).optional().describe("Maximum weighted feedback score (-10 to 10)"),
1844
+ virtual_keys: z2.string().optional().describe(
1759
1845
  "Legacy Portkey query param for virtual key slugs. Comma-separated string; prefer virtual_key_slugs for structured inputs."
1760
1846
  ),
1761
- configs: z.string().optional().describe(
1847
+ configs: z2.string().optional().describe(
1762
1848
  "Legacy Portkey query param for config slugs. Comma-separated string; prefer config_slugs for structured inputs."
1763
1849
  ),
1764
- status_codes: z.array(z.string()).optional().describe(
1850
+ status_codes: z2.array(z2.string()).optional().describe(
1765
1851
  "Structured alias for status_code. Use an array of HTTP status codes; normalized to the legacy comma-separated Portkey query param."
1766
1852
  ),
1767
- virtual_key_slugs: z.array(z.string()).optional().describe(
1853
+ virtual_key_slugs: z2.array(z2.string()).optional().describe(
1768
1854
  "Structured alias for virtual_keys. Use an array of virtual key slugs; normalized to the legacy comma-separated Portkey query param."
1769
1855
  ),
1770
- config_slugs: z.array(z.string()).optional().describe(
1856
+ config_slugs: z2.array(z2.string()).optional().describe(
1771
1857
  "Structured alias for configs. Use an array of config slugs; normalized to the legacy comma-separated Portkey query param."
1772
1858
  ),
1773
- workspace_slug: z.string().optional().describe("Filter by specific workspace"),
1774
- api_key_ids: z.preprocess((value) => {
1859
+ workspace_slug: z2.string().optional().describe("Filter by specific workspace"),
1860
+ api_key_ids: z2.preprocess((value) => {
1775
1861
  if (value == null) {
1776
1862
  return value;
1777
1863
  }
@@ -1779,43 +1865,43 @@ var baseAnalyticsSchema = {
1779
1865
  return value.map((item) => String(item)).join(",");
1780
1866
  }
1781
1867
  return value;
1782
- }, z.string().optional()).describe(
1868
+ }, z2.string().optional()).describe(
1783
1869
  "Legacy Portkey query param for API key UUIDs. Comma-separated string; request_analytics also accepts an array and normalizes it to this form."
1784
1870
  ),
1785
- metadata: z.string().optional().describe(
1871
+ metadata: z2.string().optional().describe(
1786
1872
  `Legacy Portkey query param for metadata filtering. Stringified JSON object, e.g. '{"env":"prod","app":"myapp"}'; prefer metadata_filter for structured inputs.`
1787
1873
  ),
1788
- ai_org_model: z.string().optional().describe(
1874
+ ai_org_model: z2.string().optional().describe(
1789
1875
  "Legacy Portkey query param for provider/model pairs. Format: 'provider__model' with double underscore, e.g. 'openai__gpt-4' or 'anthropic__claude-3-opus'. Comma-separated string; prefer provider_models for structured inputs."
1790
1876
  ),
1791
- provider_models: z.array(z.string()).optional().describe(
1877
+ provider_models: z2.array(z2.string()).optional().describe(
1792
1878
  "Structured alias for ai_org_model. Use provider__model strings in an array; normalized to the legacy comma-separated Portkey query param."
1793
1879
  ),
1794
- trace_id: z.string().optional().describe(
1880
+ trace_id: z2.string().optional().describe(
1795
1881
  "Legacy Portkey query param for trace IDs. Comma-separated string; prefer trace_ids for structured inputs."
1796
1882
  ),
1797
- trace_ids: z.array(z.string()).optional().describe(
1883
+ trace_ids: z2.array(z2.string()).optional().describe(
1798
1884
  "Structured alias for trace_id. Use an array of trace IDs; normalized to the legacy comma-separated Portkey query param."
1799
1885
  ),
1800
- span_id: z.string().optional().describe(
1886
+ span_id: z2.string().optional().describe(
1801
1887
  "Legacy Portkey query param for span IDs. Comma-separated string; prefer span_ids for structured inputs."
1802
1888
  ),
1803
- span_ids: z.array(z.string()).optional().describe(
1889
+ span_ids: z2.array(z2.string()).optional().describe(
1804
1890
  "Structured alias for span_id. Use an array of span IDs; normalized to the legacy comma-separated Portkey query param."
1805
1891
  ),
1806
- metadata_filter: z.record(z.string(), z.unknown()).optional().describe(
1892
+ metadata_filter: z2.record(z2.string(), z2.unknown()).optional().describe(
1807
1893
  "Structured alias for metadata. Use an object such as { env: 'prod' }; normalized to a JSON string before the request is sent."
1808
1894
  ),
1809
- prompt_slug: z.string().optional().describe("Filter by prompt slug")
1895
+ prompt_slug: z2.string().optional().describe("Filter by prompt slug")
1810
1896
  };
1811
1897
  var paginatedAnalyticsSchema = {
1812
1898
  ...baseAnalyticsSchema,
1813
- current_page: z.coerce.number().positive().optional().describe("Page number for pagination"),
1814
- page_size: z.coerce.number().int().positive().max(100).optional().describe("Results per page (max 100)")
1899
+ current_page: z2.coerce.number().int().positive().optional().describe("Page number for pagination"),
1900
+ page_size: z2.coerce.number().int().positive().max(100).optional().describe("Results per page (max 100)")
1815
1901
  };
1816
1902
  var analyticsGroupMetadataSchema = {
1817
1903
  ...paginatedAnalyticsSchema,
1818
- metadata_key: z.string().describe("The metadata key to group by (e.g., 'env', 'app', 'client_id')")
1904
+ metadata_key: z2.string().describe("The metadata key to group by (e.g., 'env', 'app', 'client_id')")
1819
1905
  };
1820
1906
  function formatGraphAnalytics(summary, dataPoints) {
1821
1907
  return {
@@ -2398,26 +2484,26 @@ function registerAnalyticsTools(server, service) {
2398
2484
  }
2399
2485
 
2400
2486
  // src/tools/audit.tools.ts
2401
- import { z as z2 } from "zod";
2487
+ import { z as z3 } from "zod";
2402
2488
  var AUDIT_TOOL_SCHEMAS = {
2403
2489
  listAuditLogs: {
2404
- workspace_id: z2.string().optional().describe("Filter audit logs by workspace ID"),
2405
- actor_id: z2.string().optional().describe("Filter by the user ID who performed the action"),
2406
- action: z2.string().optional().describe(
2490
+ workspace_id: z3.string().optional().describe("Filter audit logs by workspace ID"),
2491
+ actor_id: z3.string().optional().describe("Filter by the user ID who performed the action"),
2492
+ action: z3.string().optional().describe(
2407
2493
  "Filter by action type (e.g., 'create', 'update', 'delete', 'login')"
2408
2494
  ),
2409
- resource_type: z2.string().optional().describe(
2495
+ resource_type: z3.string().optional().describe(
2410
2496
  "Filter by resource type (e.g., 'user', 'workspace', 'config', 'virtual_key')"
2411
2497
  ),
2412
- resource_id: z2.string().optional().describe("Filter by specific resource ID"),
2413
- start_time: z2.string().optional().describe(
2498
+ resource_id: z3.string().optional().describe("Filter by specific resource ID"),
2499
+ start_time: z3.string().optional().describe(
2414
2500
  "Start of time range filter (ISO 8601 format, e.g., '2024-01-01T00:00:00Z')"
2415
2501
  ),
2416
- end_time: z2.string().optional().describe(
2502
+ end_time: z3.string().optional().describe(
2417
2503
  "End of time range filter (ISO 8601 format, e.g., '2024-01-31T23:59:59Z')"
2418
2504
  ),
2419
- current_page: z2.coerce.number().positive().optional().describe("Page number for pagination (starts at 1)"),
2420
- page_size: z2.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)")
2505
+ current_page: z3.coerce.number().int().positive().optional().describe("Page number for pagination (starts at 1)"),
2506
+ page_size: z3.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)")
2421
2507
  }
2422
2508
  };
2423
2509
  function registerAuditTools(server, service) {
@@ -2470,30 +2556,30 @@ function registerAuditTools(server, service) {
2470
2556
  }
2471
2557
 
2472
2558
  // src/tools/collections.tools.ts
2473
- import { z as z3 } from "zod";
2559
+ import { z as z4 } from "zod";
2474
2560
  var COLLECTIONS_TOOL_SCHEMAS = {
2475
2561
  listCollections: {
2476
- workspace_id: z3.string().optional().describe("Filter by workspace ID"),
2477
- search: z3.string().optional().describe("Search collections by name"),
2478
- current_page: z3.coerce.number().positive().optional().describe("Page number for pagination"),
2479
- page_size: z3.coerce.number().positive().max(100).optional().describe("Results per page (max 100)")
2562
+ workspace_id: z4.string().optional().describe("Filter by workspace ID"),
2563
+ search: z4.string().optional().describe("Search collections by name"),
2564
+ current_page: z4.coerce.number().int().positive().optional().describe("Page number for pagination"),
2565
+ page_size: z4.coerce.number().int().positive().max(100).optional().describe("Results per page (max 100)")
2480
2566
  },
2481
2567
  createCollection: {
2482
- name: z3.string().describe(
2568
+ name: z4.string().describe(
2483
2569
  "Collection name (e.g., 'hourlink', 'apizone', 'research-pilot')"
2484
2570
  ),
2485
- workspace_id: z3.string().optional().describe("Workspace ID to create collection in")
2571
+ workspace_id: z4.string().optional().describe("Workspace ID to create collection in")
2486
2572
  },
2487
2573
  getCollection: {
2488
- collection_id: z3.string().describe("Collection ID or slug to retrieve")
2574
+ collection_id: z4.string().describe("Collection ID or slug to retrieve")
2489
2575
  },
2490
2576
  updateCollection: {
2491
- collection_id: z3.string().describe("Collection ID to update"),
2492
- name: z3.string().optional().describe("New name for the collection"),
2493
- description: z3.string().optional().describe("New description for the collection")
2577
+ collection_id: z4.string().describe("Collection ID to update"),
2578
+ name: z4.string().optional().describe("New name for the collection"),
2579
+ description: z4.string().optional().describe("New description for the collection")
2494
2580
  },
2495
2581
  deleteCollection: {
2496
- collection_id: z3.string().describe("Collection ID to delete")
2582
+ collection_id: z4.string().describe("Collection ID to delete")
2497
2583
  }
2498
2584
  };
2499
2585
  function registerCollectionsTools(server, service) {
@@ -2614,69 +2700,69 @@ function registerCollectionsTools(server, service) {
2614
2700
  }
2615
2701
 
2616
2702
  // src/tools/configs.tools.ts
2617
- import { z as z4 } from "zod";
2703
+ import { z as z5 } from "zod";
2618
2704
  var CONFIGS_TOOL_SCHEMAS = {
2619
2705
  listConfigs: {
2620
- current_page: z4.coerce.number().positive().optional().describe("Page number for pagination"),
2621
- page_size: z4.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)")
2706
+ current_page: z5.coerce.number().int().positive().optional().describe("Page number for pagination"),
2707
+ page_size: z5.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)")
2622
2708
  },
2623
2709
  getConfig: {
2624
- slug: z4.string().describe(
2710
+ slug: z5.string().describe(
2625
2711
  "The unique identifier (slug) of the configuration to retrieve. This can be found in the configuration's URL or from the list_configs tool response"
2626
2712
  )
2627
2713
  },
2628
2714
  createConfig: {
2629
- name: z4.string().describe("Name for the new configuration"),
2630
- workspace_id: z4.string().optional().describe("Workspace ID to create config in"),
2631
- cache_mode: z4.enum(["simple", "semantic"]).optional().describe("Cache mode: 'simple' or 'semantic'"),
2632
- cache_max_age: z4.coerce.number().positive().optional().describe("Cache max age in seconds"),
2633
- retry_attempts: z4.coerce.number().positive().max(5).optional().describe("Number of retry attempts (1-5)"),
2634
- retry_on_status_codes: z4.array(z4.coerce.number()).optional().describe("HTTP status codes to retry on (e.g., [429, 500, 502, 503])"),
2635
- strategy_mode: z4.enum(["loadbalance", "fallback"]).optional().describe("Routing strategy: 'loadbalance' or 'fallback'"),
2636
- targets: z4.array(
2637
- z4.object({
2638
- provider: z4.string().optional(),
2639
- virtual_key: z4.string().optional()
2715
+ name: z5.string().describe("Name for the new configuration"),
2716
+ workspace_id: z5.string().optional().describe("Workspace ID to create config in"),
2717
+ cache_mode: z5.enum(["simple", "semantic"]).optional().describe("Cache mode: 'simple' or 'semantic'"),
2718
+ cache_max_age: z5.coerce.number().positive().optional().describe("Cache max age in seconds"),
2719
+ retry_attempts: z5.coerce.number().positive().max(5).optional().describe("Number of retry attempts (1-5)"),
2720
+ retry_on_status_codes: z5.array(z5.coerce.number()).optional().describe("HTTP status codes to retry on (e.g., [429, 500, 502, 503])"),
2721
+ strategy_mode: z5.enum(["loadbalance", "fallback"]).optional().describe("Routing strategy: 'loadbalance' or 'fallback'"),
2722
+ targets: z5.array(
2723
+ z5.object({
2724
+ provider: z5.string().optional(),
2725
+ virtual_key: z5.string().optional()
2640
2726
  }).refine((t) => t.provider || t.virtual_key, {
2641
2727
  message: "Each target must have at least provider or virtual_key"
2642
2728
  })
2643
2729
  ).optional().describe("Array of target providers with virtual keys")
2644
2730
  },
2645
2731
  updateConfig: {
2646
- slug: z4.string().describe("Configuration slug to update"),
2647
- name: z4.string().optional().describe("New name for the configuration"),
2648
- status: z4.enum(["active", "inactive"]).optional().describe("Configuration status"),
2649
- cache_mode: z4.enum(["simple", "semantic"]).optional().describe("Cache mode: 'simple' or 'semantic'"),
2650
- cache_max_age: z4.coerce.number().positive().optional().describe("Cache max age in seconds"),
2651
- retry_attempts: z4.coerce.number().positive().max(5).optional().describe("Number of retry attempts (1-5)"),
2652
- retry_on_status_codes: z4.array(z4.coerce.number()).optional().describe("HTTP status codes to retry on"),
2653
- strategy_mode: z4.enum(["loadbalance", "fallback"]).optional().describe("Routing strategy"),
2654
- targets: z4.array(
2655
- z4.object({
2656
- provider: z4.string().optional(),
2657
- virtual_key: z4.string().optional()
2732
+ slug: z5.string().describe("Configuration slug to update"),
2733
+ name: z5.string().optional().describe("New name for the configuration"),
2734
+ status: z5.enum(["active", "inactive"]).optional().describe("Configuration status"),
2735
+ cache_mode: z5.enum(["simple", "semantic"]).optional().describe("Cache mode: 'simple' or 'semantic'"),
2736
+ cache_max_age: z5.coerce.number().positive().optional().describe("Cache max age in seconds"),
2737
+ retry_attempts: z5.coerce.number().positive().max(5).optional().describe("Number of retry attempts (1-5)"),
2738
+ retry_on_status_codes: z5.array(z5.coerce.number()).optional().describe("HTTP status codes to retry on"),
2739
+ strategy_mode: z5.enum(["loadbalance", "fallback"]).optional().describe("Routing strategy"),
2740
+ targets: z5.array(
2741
+ z5.object({
2742
+ provider: z5.string().optional(),
2743
+ virtual_key: z5.string().optional()
2658
2744
  }).refine((t) => t.provider || t.virtual_key, {
2659
2745
  message: "Each target must have at least provider or virtual_key"
2660
2746
  })
2661
2747
  ).optional().describe("Array of target providers")
2662
2748
  },
2663
2749
  deleteConfig: {
2664
- slug: z4.string().describe("Configuration slug to delete")
2750
+ slug: z5.string().describe("Configuration slug to delete")
2665
2751
  },
2666
2752
  listConfigVersions: {
2667
- slug: z4.string().describe("Configuration slug to list versions for")
2753
+ slug: z5.string().describe("Configuration slug to list versions for")
2668
2754
  }
2669
2755
  };
2670
- var configPayloadSchema = z4.object({
2671
- cache_mode: z4.enum(["simple", "semantic"]).optional(),
2672
- cache_max_age: z4.coerce.number().positive().optional(),
2673
- retry_attempts: z4.coerce.number().positive().max(5).optional(),
2674
- retry_on_status_codes: z4.array(z4.coerce.number()).optional(),
2675
- strategy_mode: z4.enum(["loadbalance", "fallback"]).optional(),
2676
- targets: z4.array(
2677
- z4.object({
2678
- provider: z4.string().optional(),
2679
- virtual_key: z4.string().optional()
2756
+ var configPayloadSchema = z5.object({
2757
+ cache_mode: z5.enum(["simple", "semantic"]).optional(),
2758
+ cache_max_age: z5.coerce.number().positive().optional(),
2759
+ retry_attempts: z5.coerce.number().positive().max(5).optional(),
2760
+ retry_on_status_codes: z5.array(z5.coerce.number()).optional(),
2761
+ strategy_mode: z5.enum(["loadbalance", "fallback"]).optional(),
2762
+ targets: z5.array(
2763
+ z5.object({
2764
+ provider: z5.string().optional(),
2765
+ virtual_key: z5.string().optional()
2680
2766
  })
2681
2767
  ).optional()
2682
2768
  });
@@ -2893,53 +2979,53 @@ function registerConfigsTools(server, service) {
2893
2979
  }
2894
2980
 
2895
2981
  // src/tools/guardrails.tools.ts
2896
- import { z as z5 } from "zod";
2897
- var guardrailCheckSchema = z5.object({
2898
- id: z5.string().describe("Check identifier (e.g., 'default.jwt', 'default.pii')"),
2899
- name: z5.string().optional().describe("Display name for the check"),
2900
- is_enabled: z5.boolean().optional().describe("Whether the check is enabled"),
2901
- parameters: z5.record(z5.string(), z5.unknown()).optional().describe("Check-specific configuration parameters")
2982
+ import { z as z6 } from "zod";
2983
+ var guardrailCheckSchema = z6.object({
2984
+ id: z6.string().describe("Check identifier (e.g., 'default.jwt', 'default.pii')"),
2985
+ name: z6.string().optional().describe("Display name for the check"),
2986
+ is_enabled: z6.boolean().optional().describe("Whether the check is enabled"),
2987
+ parameters: z6.record(z6.string(), z6.unknown()).optional().describe("Check-specific configuration parameters")
2902
2988
  });
2903
- var guardrailFeedbackSchema = z5.object({
2904
- value: z5.coerce.number().optional().describe("Feedback value"),
2905
- weight: z5.coerce.number().optional().describe("Feedback weight"),
2906
- metadata: z5.record(z5.string(), z5.unknown()).optional().describe("Additional metadata")
2989
+ var guardrailFeedbackSchema = z6.object({
2990
+ value: z6.coerce.number().optional().describe("Feedback value"),
2991
+ weight: z6.coerce.number().optional().describe("Feedback weight"),
2992
+ metadata: z6.record(z6.string(), z6.unknown()).optional().describe("Additional metadata")
2907
2993
  });
2908
- var guardrailActionSchema = z5.object({
2909
- deny: z5.boolean().optional().describe("Whether to deny the request on check failure"),
2910
- async: z5.boolean().optional().describe("Whether to run checks asynchronously"),
2994
+ var guardrailActionSchema = z6.object({
2995
+ deny: z6.boolean().optional().describe("Whether to deny the request on check failure"),
2996
+ async: z6.boolean().optional().describe("Whether to run checks asynchronously"),
2911
2997
  on_success: guardrailFeedbackSchema.optional().describe("Feedback configuration for successful checks"),
2912
2998
  on_fail: guardrailFeedbackSchema.optional().describe("Feedback configuration for failed checks"),
2913
- on_fail_action: z5.string().optional().describe("Simple action on failure (e.g., 'block')"),
2914
- message: z5.string().optional().describe("Message to return when guardrail triggers")
2999
+ on_fail_action: z6.string().optional().describe("Simple action on failure (e.g., 'block')"),
3000
+ message: z6.string().optional().describe("Message to return when guardrail triggers")
2915
3001
  });
2916
3002
  var GUARDRAILS_TOOL_SCHEMAS = {
2917
3003
  listGuardrails: {
2918
- workspace_id: z5.string().optional().describe("Filter guardrails by workspace ID"),
2919
- organisation_id: z5.string().optional().describe("Filter guardrails by organization ID"),
2920
- page_size: z5.coerce.number().min(1).max(100).optional().describe("Number of items per page (1-100, default: 100)"),
2921
- current_page: z5.coerce.number().positive().optional().describe("Page number for pagination")
3004
+ workspace_id: z6.string().optional().describe("Filter guardrails by workspace ID"),
3005
+ organisation_id: z6.string().optional().describe("Filter guardrails by organization ID"),
3006
+ page_size: z6.coerce.number().int().min(1).max(100).optional().describe("Number of items per page (1-100, default: 100)"),
3007
+ current_page: z6.coerce.number().int().positive().optional().describe("Page number for pagination")
2922
3008
  },
2923
3009
  getGuardrail: {
2924
- guardrail_id: z5.string().describe("The guardrail UUID or slug (with guard_ prefix) to retrieve")
3010
+ guardrail_id: z6.string().describe("The guardrail UUID or slug (with guard_ prefix) to retrieve")
2925
3011
  },
2926
3012
  createGuardrail: {
2927
- name: z5.string().describe("Name of the guardrail"),
2928
- checks: z5.array(guardrailCheckSchema).min(1).describe("Array of checks to apply (at least one required)"),
3013
+ name: z6.string().describe("Name of the guardrail"),
3014
+ checks: z6.array(guardrailCheckSchema).min(1).describe("Array of checks to apply (at least one required)"),
2929
3015
  actions: guardrailActionSchema.describe(
2930
3016
  "Actions to take when guardrail checks pass or fail"
2931
3017
  ),
2932
- workspace_id: z5.string().optional().describe("Workspace ID to create the guardrail in"),
2933
- organisation_id: z5.string().optional().describe("Organisation ID (required if workspace_id not provided)")
3018
+ workspace_id: z6.string().optional().describe("Workspace ID to create the guardrail in"),
3019
+ organisation_id: z6.string().optional().describe("Organisation ID (required if workspace_id not provided)")
2934
3020
  },
2935
3021
  updateGuardrail: {
2936
- guardrail_id: z5.string().describe("The guardrail UUID or slug to update"),
2937
- name: z5.string().optional().describe("New name for the guardrail"),
2938
- checks: z5.array(guardrailCheckSchema).min(1).optional().describe("Updated array of checks to apply"),
3022
+ guardrail_id: z6.string().describe("The guardrail UUID or slug to update"),
3023
+ name: z6.string().optional().describe("New name for the guardrail"),
3024
+ checks: z6.array(guardrailCheckSchema).min(1).optional().describe("Updated array of checks to apply"),
2939
3025
  actions: guardrailActionSchema.optional().describe("Updated actions configuration")
2940
3026
  },
2941
3027
  deleteGuardrail: {
2942
- guardrail_id: z5.string().describe("The guardrail UUID or slug to delete")
3028
+ guardrail_id: z6.string().describe("The guardrail UUID or slug to delete")
2943
3029
  }
2944
3030
  };
2945
3031
  function registerGuardrailsTools(server, service) {
@@ -3089,7 +3175,7 @@ function registerGuardrailsTools(server, service) {
3089
3175
  }
3090
3176
 
3091
3177
  // src/tools/integrations.tools.ts
3092
- import { z as z6 } from "zod";
3178
+ import { z as z7 } from "zod";
3093
3179
 
3094
3180
  // src/lib/limits.ts
3095
3181
  var PERIODIC_RESET_DEFAULT = "monthly";
@@ -3134,91 +3220,91 @@ function buildRateLimits(params) {
3134
3220
  // src/tools/integrations.tools.ts
3135
3221
  var INTEGRATIONS_TOOL_SCHEMAS = {
3136
3222
  listIntegrations: {
3137
- current_page: z6.coerce.number().positive().optional().describe("Page number for pagination"),
3138
- page_size: z6.coerce.number().int().positive().max(100).optional().describe("Number of results per page (default 100, max 100)"),
3139
- workspace_id: z6.string().optional().describe("Filter integrations accessible by a specific workspace"),
3140
- type: z6.enum(["workspace", "organisation", "all"]).optional().describe(
3223
+ current_page: z7.coerce.number().int().positive().optional().describe("Page number for pagination"),
3224
+ page_size: z7.coerce.number().int().positive().max(100).optional().describe("Number of results per page (default 100, max 100)"),
3225
+ workspace_id: z7.string().optional().describe("Filter integrations accessible by a specific workspace"),
3226
+ type: z7.enum(["workspace", "organisation", "all"]).optional().describe(
3141
3227
  "Filter by integration type: 'workspace', 'organisation', or 'all' (default)"
3142
3228
  )
3143
3229
  },
3144
3230
  createIntegration: {
3145
- name: z6.string().describe("Human-readable name for the integration"),
3146
- ai_provider_id: z6.string().describe(
3231
+ name: z7.string().describe("Human-readable name for the integration"),
3232
+ ai_provider_id: z7.string().describe(
3147
3233
  "ID of the AI provider (e.g., 'openai', 'anthropic', 'azure-openai', 'aws-bedrock', 'vertex-ai')"
3148
3234
  ),
3149
- slug: z6.string().optional().describe(
3235
+ slug: z7.string().optional().describe(
3150
3236
  "URL-friendly identifier (auto-generated from name if not provided)"
3151
3237
  ),
3152
- key: z6.string().optional().describe("API key for the provider (if required)"),
3153
- description: z6.string().optional().describe("Optional description of the integration"),
3154
- workspace_id: z6.string().optional().describe("Workspace ID for workspace-scoped integrations"),
3155
- api_version: z6.string().optional().describe("API version (for Azure OpenAI)"),
3156
- resource_name: z6.string().optional().describe("Resource name (for Azure OpenAI)"),
3157
- deployment_name: z6.string().optional().describe("Deployment name (for Azure OpenAI)"),
3158
- aws_region: z6.string().optional().describe("AWS region (for AWS Bedrock)"),
3159
- aws_access_key_id: z6.string().optional().describe("AWS access key ID (for AWS Bedrock)"),
3160
- aws_secret_access_key: z6.string().optional().describe("AWS secret access key (for AWS Bedrock)"),
3161
- vertex_project_id: z6.string().optional().describe("GCP project ID (for Vertex AI)"),
3162
- vertex_region: z6.string().optional().describe("GCP region (for Vertex AI)"),
3163
- custom_host: z6.string().optional().describe("Custom base URL for the provider")
3238
+ key: z7.string().optional().describe("API key for the provider (if required)"),
3239
+ description: z7.string().optional().describe("Optional description of the integration"),
3240
+ workspace_id: z7.string().optional().describe("Workspace ID for workspace-scoped integrations"),
3241
+ api_version: z7.string().optional().describe("API version (for Azure OpenAI)"),
3242
+ resource_name: z7.string().optional().describe("Resource name (for Azure OpenAI)"),
3243
+ deployment_name: z7.string().optional().describe("Deployment name (for Azure OpenAI)"),
3244
+ aws_region: z7.string().optional().describe("AWS region (for AWS Bedrock)"),
3245
+ aws_access_key_id: z7.string().optional().describe("AWS access key ID (for AWS Bedrock)"),
3246
+ aws_secret_access_key: z7.string().optional().describe("AWS secret access key (for AWS Bedrock)"),
3247
+ vertex_project_id: z7.string().optional().describe("GCP project ID (for Vertex AI)"),
3248
+ vertex_region: z7.string().optional().describe("GCP region (for Vertex AI)"),
3249
+ custom_host: z7.string().optional().describe("Custom base URL for the provider")
3164
3250
  },
3165
3251
  getIntegration: {
3166
- slug: z6.string().describe("The unique slug identifier of the integration to retrieve")
3252
+ slug: z7.string().describe("The unique slug identifier of the integration to retrieve")
3167
3253
  },
3168
3254
  updateIntegration: {
3169
- slug: z6.string().describe("The slug of the integration to update"),
3170
- name: z6.string().optional().describe("New human-readable name for the integration"),
3171
- key: z6.string().optional().describe("New API key for the provider"),
3172
- description: z6.string().optional().describe("New description for the integration"),
3173
- api_version: z6.string().optional().describe("New API version (for Azure OpenAI)"),
3174
- resource_name: z6.string().optional().describe("New resource name (for Azure OpenAI)"),
3175
- deployment_name: z6.string().optional().describe("New deployment name (for Azure OpenAI)"),
3176
- aws_region: z6.string().optional().describe("New AWS region (for AWS Bedrock)"),
3177
- aws_access_key_id: z6.string().optional().describe("New AWS access key ID (for AWS Bedrock)"),
3178
- aws_secret_access_key: z6.string().optional().describe("New AWS secret access key (for AWS Bedrock)"),
3179
- vertex_project_id: z6.string().optional().describe("New GCP project ID (for Vertex AI)"),
3180
- vertex_region: z6.string().optional().describe("New GCP region (for Vertex AI)"),
3181
- custom_host: z6.string().optional().describe("New custom base URL for the provider")
3255
+ slug: z7.string().describe("The slug of the integration to update"),
3256
+ name: z7.string().optional().describe("New human-readable name for the integration"),
3257
+ key: z7.string().optional().describe("New API key for the provider"),
3258
+ description: z7.string().optional().describe("New description for the integration"),
3259
+ api_version: z7.string().optional().describe("New API version (for Azure OpenAI)"),
3260
+ resource_name: z7.string().optional().describe("New resource name (for Azure OpenAI)"),
3261
+ deployment_name: z7.string().optional().describe("New deployment name (for Azure OpenAI)"),
3262
+ aws_region: z7.string().optional().describe("New AWS region (for AWS Bedrock)"),
3263
+ aws_access_key_id: z7.string().optional().describe("New AWS access key ID (for AWS Bedrock)"),
3264
+ aws_secret_access_key: z7.string().optional().describe("New AWS secret access key (for AWS Bedrock)"),
3265
+ vertex_project_id: z7.string().optional().describe("New GCP project ID (for Vertex AI)"),
3266
+ vertex_region: z7.string().optional().describe("New GCP region (for Vertex AI)"),
3267
+ custom_host: z7.string().optional().describe("New custom base URL for the provider")
3182
3268
  },
3183
3269
  deleteIntegration: {
3184
- slug: z6.string().describe("The slug of the integration to delete")
3270
+ slug: z7.string().describe("The slug of the integration to delete")
3185
3271
  },
3186
3272
  listIntegrationModels: {
3187
- slug: z6.string().describe("The slug of the integration"),
3188
- current_page: z6.coerce.number().positive().optional().describe("Page number for pagination"),
3189
- page_size: z6.coerce.number().int().positive().max(100).optional().describe("Number of results per page")
3273
+ slug: z7.string().describe("The slug of the integration"),
3274
+ current_page: z7.coerce.number().int().positive().optional().describe("Page number for pagination"),
3275
+ page_size: z7.coerce.number().int().positive().max(100).optional().describe("Number of results per page")
3190
3276
  },
3191
3277
  updateIntegrationModels: {
3192
- slug: z6.string().describe("The slug of the integration"),
3193
- models: z6.array(
3194
- z6.object({
3195
- slug: z6.string().describe("The model slug identifier"),
3196
- model_name: z6.string().optional().describe(
3278
+ slug: z7.string().describe("The slug of the integration"),
3279
+ models: z7.array(
3280
+ z7.object({
3281
+ slug: z7.string().describe("The model slug identifier"),
3282
+ model_name: z7.string().optional().describe(
3197
3283
  "Display name for the model (required for custom models)"
3198
3284
  ),
3199
- enabled: z6.boolean().describe("Whether the model is enabled"),
3200
- is_custom: z6.boolean().optional().describe("Whether this is a custom model (default: false)")
3285
+ enabled: z7.boolean().describe("Whether the model is enabled"),
3286
+ is_custom: z7.boolean().optional().describe("Whether this is a custom model (default: false)")
3201
3287
  })
3202
3288
  ).describe("Array of model configurations to update")
3203
3289
  },
3204
3290
  deleteIntegrationModel: {
3205
- slug: z6.string().describe("The slug of the integration"),
3206
- model_slug: z6.string().describe("The slug of the model to delete")
3291
+ slug: z7.string().describe("The slug of the integration"),
3292
+ model_slug: z7.string().describe("The slug of the model to delete")
3207
3293
  },
3208
3294
  listIntegrationWorkspaces: {
3209
- slug: z6.string().describe("The slug of the integration"),
3210
- current_page: z6.coerce.number().positive().optional().describe("Page number for pagination"),
3211
- page_size: z6.coerce.number().int().positive().max(100).optional().describe("Number of results per page")
3295
+ slug: z7.string().describe("The slug of the integration"),
3296
+ current_page: z7.coerce.number().int().positive().optional().describe("Page number for pagination"),
3297
+ page_size: z7.coerce.number().int().positive().max(100).optional().describe("Number of results per page")
3212
3298
  },
3213
3299
  updateIntegrationWorkspaces: {
3214
- slug: z6.string().describe("The slug of the integration"),
3215
- workspaces: z6.array(
3216
- z6.object({
3217
- id: z6.string().describe("The workspace ID"),
3218
- enabled: z6.boolean().describe("Whether the workspace has access to this integration"),
3219
- credit_limit: z6.coerce.number().positive().optional().describe("Credit limit for this workspace"),
3220
- alert_threshold: z6.coerce.number().min(0).max(100).optional().describe("Alert threshold percentage (0-100)"),
3221
- rate_limit_rpm: z6.coerce.number().positive().optional().describe("Rate limit in requests per minute")
3300
+ slug: z7.string().describe("The slug of the integration"),
3301
+ workspaces: z7.array(
3302
+ z7.object({
3303
+ id: z7.string().describe("The workspace ID"),
3304
+ enabled: z7.boolean().describe("Whether the workspace has access to this integration"),
3305
+ credit_limit: z7.coerce.number().positive().optional().describe("Credit limit for this workspace"),
3306
+ alert_threshold: z7.coerce.number().min(0).max(100).optional().describe("Alert threshold percentage (0-100)"),
3307
+ rate_limit_rpm: z7.coerce.number().positive().optional().describe("Rate limit in requests per minute")
3222
3308
  })
3223
3309
  ).describe("Array of workspace configurations to update")
3224
3310
  }
@@ -3551,104 +3637,110 @@ function registerIntegrationsTools(server, service) {
3551
3637
  }
3552
3638
 
3553
3639
  // src/tools/keys.tools.ts
3554
- import { z as z7 } from "zod";
3640
+ import { z as z8 } from "zod";
3555
3641
  var KEYS_TOOL_SCHEMAS = {
3556
3642
  listVirtualKeys: {
3557
- current_page: z7.coerce.number().positive().optional().describe("Page number for pagination"),
3558
- page_size: z7.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)")
3643
+ current_page: z8.coerce.number().int().positive().optional().describe("Page number for pagination"),
3644
+ page_size: z8.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)")
3559
3645
  },
3560
3646
  createVirtualKey: {
3561
- name: z7.string().describe("Display name for the virtual key"),
3562
- provider: z7.string().describe(
3647
+ name: z8.string().describe("Display name for the virtual key"),
3648
+ provider: z8.string().describe(
3563
3649
  "Provider slug (e.g., 'openai', 'anthropic', 'azure-openai', 'google')"
3564
3650
  ),
3565
- key: z7.string().describe("The actual provider API key to store"),
3566
- note: z7.string().optional().describe("Optional note or description for this key"),
3567
- workspace_id: z7.string().optional().describe("Workspace ID to create the key in"),
3568
- api_version: z7.string().optional().describe("API version (for Azure OpenAI)"),
3569
- resource_name: z7.string().optional().describe("Resource name (for Azure OpenAI)"),
3570
- deployment_name: z7.string().optional().describe("Deployment name (for Azure OpenAI)"),
3571
- credit_limit: z7.coerce.number().positive().optional().describe("Maximum usage cost threshold"),
3572
- alert_threshold: z7.coerce.number().min(0).max(100).optional().describe(
3651
+ key: z8.string().describe("The actual provider API key to store"),
3652
+ note: z8.string().optional().describe("Optional note or description for this key"),
3653
+ workspace_id: z8.string().optional().describe("Workspace ID to create the key in"),
3654
+ api_version: z8.string().optional().describe("API version (for Azure OpenAI)"),
3655
+ resource_name: z8.string().optional().describe("Resource name (for Azure OpenAI)"),
3656
+ deployment_name: z8.string().optional().describe("Deployment name (for Azure OpenAI)"),
3657
+ credit_limit: z8.coerce.number().positive().optional().describe("Maximum usage cost threshold"),
3658
+ alert_threshold: z8.coerce.number().min(0).max(100).optional().describe(
3573
3659
  "Percentage of credit_limit at which to send alert emails (0-100)"
3574
3660
  ),
3575
- rate_limit_rpm: z7.coerce.number().positive().optional().describe("Requests per minute limit")
3661
+ rate_limit_rpm: z8.coerce.number().positive().optional().describe("Requests per minute limit")
3576
3662
  },
3577
3663
  getVirtualKey: {
3578
- slug: z7.string().describe("The unique slug identifier of the virtual key to retrieve")
3664
+ slug: z8.string().describe("The unique slug identifier of the virtual key to retrieve")
3579
3665
  },
3580
3666
  updateVirtualKey: {
3581
- slug: z7.string().describe("The slug of the virtual key to update"),
3582
- name: z7.string().optional().describe("New display name for the key"),
3583
- key: z7.string().optional().describe("New provider API key value"),
3584
- note: z7.string().optional().describe("New note or description"),
3585
- credit_limit: z7.coerce.number().positive().optional().describe("New credit limit for usage"),
3586
- alert_threshold: z7.coerce.number().min(0).max(100).optional().describe("New alert threshold percentage (0-100)"),
3587
- rate_limit_rpm: z7.coerce.number().positive().optional().describe("New rate limit in requests per minute")
3667
+ slug: z8.string().describe("The slug of the virtual key to update"),
3668
+ name: z8.string().optional().describe("New display name for the key"),
3669
+ key: z8.string().optional().describe("New provider API key value"),
3670
+ note: z8.string().optional().describe("New note or description"),
3671
+ credit_limit: z8.coerce.number().positive().optional().describe("New credit limit for usage"),
3672
+ alert_threshold: z8.coerce.number().min(0).max(100).optional().describe("New alert threshold percentage (0-100)"),
3673
+ rate_limit_rpm: z8.coerce.number().positive().optional().describe("New rate limit in requests per minute")
3588
3674
  },
3589
3675
  deleteVirtualKey: {
3590
- slug: z7.string().describe("The slug of the virtual key to delete")
3676
+ slug: z8.string().describe("The slug of the virtual key to delete")
3591
3677
  },
3592
3678
  createApiKey: {
3593
- type: z7.enum(["organisation", "workspace"]).describe(
3679
+ type: z8.enum(["organisation", "workspace"]).describe(
3594
3680
  "Key type: 'organisation' for org-wide access or 'workspace' for workspace-scoped"
3595
3681
  ),
3596
- sub_type: z7.enum(["user", "service"]).describe(
3682
+ sub_type: z8.enum(["user", "service"]).describe(
3597
3683
  "Sub-type: 'user' for user-associated keys or 'service' for service accounts"
3598
3684
  ),
3599
- name: z7.string().describe("Display name for the API key"),
3600
- description: z7.string().optional().describe("Optional description for the key"),
3601
- workspace_id: z7.string().optional().describe("Workspace ID (required for workspace-type keys)"),
3602
- user_id: z7.string().optional().describe("User ID (required for user sub-type keys)"),
3603
- scopes: z7.array(z7.string()).describe(
3685
+ name: z8.string().describe("Display name for the API key"),
3686
+ description: z8.string().optional().describe("Optional description for the key"),
3687
+ workspace_id: z8.string().optional().describe("Workspace ID (required for workspace-type keys)"),
3688
+ user_id: z8.string().optional().describe("User ID (required for user sub-type keys)"),
3689
+ scopes: z8.array(z8.string()).describe(
3604
3690
  "Permission scopes for the key (e.g., ['logs.read', 'analytics.read'])"
3605
3691
  ),
3606
- credit_limit: z7.coerce.number().positive().optional().describe("Credit limit for usage"),
3607
- alert_threshold: z7.coerce.number().min(0).max(100).optional().describe("Alert threshold percentage (0-100)"),
3608
- rate_limit_rpm: z7.coerce.number().positive().optional().describe("Rate limit in requests per minute"),
3609
- default_config_id: z7.string().optional().describe("Default configuration ID to use with this key"),
3610
- default_metadata: z7.record(z7.string(), z7.string()).optional().describe("Default metadata key-value pairs"),
3611
- alert_emails: z7.array(z7.string()).optional().describe("Email addresses for alerts"),
3612
- expires_at: z7.string().optional().describe("Expiration date in ISO 8601 format")
3692
+ credit_limit: z8.coerce.number().positive().optional().describe("Credit limit for usage"),
3693
+ alert_threshold: z8.coerce.number().min(0).max(100).optional().describe("Alert threshold percentage (0-100)"),
3694
+ rate_limit_rpm: z8.coerce.number().positive().optional().describe("Rate limit in requests per minute"),
3695
+ default_config_id: z8.string().optional().describe("Default configuration ID to use with this key"),
3696
+ default_metadata: z8.record(z8.string(), z8.string()).optional().describe("Default metadata key-value pairs"),
3697
+ alert_emails: z8.array(z8.string()).optional().describe("Email addresses for alerts"),
3698
+ expires_at: z8.string().optional().describe("Expiration date in ISO 8601 format")
3613
3699
  },
3614
3700
  listApiKeys: {
3615
- page_size: z7.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)"),
3616
- current_page: z7.coerce.number().positive().optional().describe("Page number for pagination"),
3617
- workspace_id: z7.string().optional().describe("Filter by workspace ID")
3701
+ page_size: z8.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)"),
3702
+ current_page: z8.coerce.number().int().positive().optional().describe("Page number for pagination"),
3703
+ workspace_id: z8.string().optional().describe("Filter by workspace ID")
3618
3704
  },
3619
3705
  getApiKey: {
3620
- id: z7.string().uuid().describe("The UUID of the API key to retrieve")
3706
+ id: z8.string().uuid().describe("The UUID of the API key to retrieve")
3621
3707
  },
3622
3708
  updateApiKey: {
3623
- id: z7.string().uuid().describe("The UUID of the API key to update"),
3624
- name: z7.string().optional().describe("New display name for the key"),
3625
- description: z7.string().optional().describe("New description for the key"),
3626
- scopes: z7.array(z7.string()).optional().describe("New permission scopes for the key"),
3627
- credit_limit: z7.coerce.number().positive().optional().describe("New credit limit for usage"),
3628
- alert_threshold: z7.coerce.number().min(0).max(100).optional().describe("New alert threshold percentage (0-100)"),
3629
- rate_limit_rpm: z7.coerce.number().positive().optional().describe("New rate limit in requests per minute"),
3630
- default_config_id: z7.string().optional().describe("New default configuration ID"),
3631
- default_metadata: z7.record(z7.string(), z7.string()).optional().describe("New default metadata key-value pairs"),
3632
- alert_emails: z7.array(z7.string()).optional().describe("New email addresses for alerts"),
3633
- expires_at: z7.string().nullable().optional().describe(
3709
+ id: z8.string().uuid().describe("The UUID of the API key to update"),
3710
+ name: z8.string().optional().describe("New display name for the key"),
3711
+ description: z8.string().optional().describe("New description for the key"),
3712
+ scopes: z8.array(z8.string()).optional().describe("New permission scopes for the key"),
3713
+ credit_limit: z8.coerce.number().positive().optional().describe("New credit limit for usage"),
3714
+ alert_threshold: z8.coerce.number().min(0).max(100).optional().describe("New alert threshold percentage (0-100)"),
3715
+ rate_limit_rpm: z8.coerce.number().positive().optional().describe("New rate limit in requests per minute"),
3716
+ default_config_id: z8.string().optional().describe("New default configuration ID"),
3717
+ default_metadata: z8.record(z8.string(), z8.string()).optional().describe("New default metadata key-value pairs"),
3718
+ alert_emails: z8.array(z8.string()).optional().describe("New email addresses for alerts"),
3719
+ expires_at: z8.string().nullable().optional().describe(
3634
3720
  "New expiration date in ISO 8601 format, or null to remove expiration"
3635
3721
  )
3636
3722
  },
3723
+ rotateApiKey: {
3724
+ id: z8.string().uuid().describe("API key UUID obtained from list_api_keys"),
3725
+ key_transition_period_ms: z8.coerce.number().int().min(18e5).optional().describe(
3726
+ "Overlap in milliseconds while the previous key remains valid; minimum 1,800,000 (30 minutes), for example 3,600,000 for 1 hour"
3727
+ )
3728
+ },
3637
3729
  deleteApiKey: {
3638
- id: z7.string().uuid().describe("The UUID of the API key to delete")
3730
+ id: z8.string().uuid().describe("The UUID of the API key to delete")
3639
3731
  }
3640
3732
  };
3641
- var createApiKeySchema = z7.object(KEYS_TOOL_SCHEMAS.createApiKey).superRefine((value, ctx) => {
3733
+ var createApiKeySchema = z8.object(KEYS_TOOL_SCHEMAS.createApiKey).superRefine((value, ctx) => {
3642
3734
  if (value.type === "workspace" && !value.workspace_id) {
3643
3735
  ctx.addIssue({
3644
- code: z7.ZodIssueCode.custom,
3736
+ code: z8.ZodIssueCode.custom,
3645
3737
  path: ["workspace_id"],
3646
3738
  message: "workspace_id is required when type is 'workspace'"
3647
3739
  });
3648
3740
  }
3649
3741
  if (value.sub_type === "user" && !value.user_id) {
3650
3742
  ctx.addIssue({
3651
- code: z7.ZodIssueCode.custom,
3743
+ code: z8.ZodIssueCode.custom,
3652
3744
  path: ["user_id"],
3653
3745
  message: "user_id is required when sub_type is 'user'"
3654
3746
  });
@@ -4004,40 +4096,71 @@ function registerKeysTools(server, service) {
4004
4096
  };
4005
4097
  }
4006
4098
  );
4099
+ server.tool(
4100
+ "rotate_api_key",
4101
+ "Rotate an API key without changing its identity or scopes. The new secret is returned once and exposed to this MCP transcript, while the previous secret remains valid until key_transition_expires_at. Store the new key securely, update callers during the transition window, and never log either secret.",
4102
+ KEYS_TOOL_SCHEMAS.rotateApiKey,
4103
+ {
4104
+ title: "Rotate API Key",
4105
+ readOnlyHint: false,
4106
+ destructiveHint: true,
4107
+ idempotentHint: false,
4108
+ openWorldHint: true
4109
+ },
4110
+ async (params) => {
4111
+ const result = await service.keys.rotateApiKey(params.id, {
4112
+ key_transition_period_ms: params.key_transition_period_ms
4113
+ });
4114
+ return {
4115
+ content: [
4116
+ {
4117
+ type: "text",
4118
+ text: JSON.stringify({
4119
+ message: `Successfully rotated API key "${result.id}"`,
4120
+ warning: "Copy this new key now; it is returned only once. Replace callers before key_transition_expires_at and never log either key.",
4121
+ id: result.id,
4122
+ key: result.key,
4123
+ key_transition_expires_at: result.key_transition_expires_at
4124
+ })
4125
+ }
4126
+ ]
4127
+ };
4128
+ }
4129
+ );
4007
4130
  }
4008
4131
 
4009
4132
  // src/tools/labels.tools.ts
4010
- import { z as z8 } from "zod";
4133
+ import { z as z9 } from "zod";
4011
4134
  var LABELS_TOOL_SCHEMAS = {
4012
4135
  createPromptLabel: {
4013
- name: z8.string().describe("Name of the label"),
4014
- organisation_id: z8.string().optional().describe("Organisation ID to create the label in"),
4015
- workspace_id: z8.string().optional().describe("Workspace ID to create the label in"),
4016
- description: z8.string().optional().describe("Description of the label"),
4017
- color_code: z8.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe(
4136
+ name: z9.string().describe("Name of the label"),
4137
+ organisation_id: z9.string().optional().describe("Organisation ID to create the label in"),
4138
+ workspace_id: z9.string().optional().describe("Workspace ID to create the label in"),
4139
+ description: z9.string().optional().describe("Description of the label"),
4140
+ color_code: z9.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe(
4018
4141
  "Hex format, e.g. '#FF5733'. Optional \u2014 omit for default color."
4019
4142
  )
4020
4143
  },
4021
4144
  listPromptLabels: {
4022
- organisation_id: z8.string().optional().describe("Filter by organisation ID"),
4023
- workspace_id: z8.string().optional().describe("Filter by workspace ID"),
4024
- search: z8.string().optional().describe("Search labels by name"),
4025
- current_page: z8.coerce.number().positive().optional().describe("Page number for pagination"),
4026
- page_size: z8.coerce.number().positive().max(100).optional().describe("Results per page (max 100)")
4145
+ organisation_id: z9.string().optional().describe("Filter by organisation ID"),
4146
+ workspace_id: z9.string().optional().describe("Filter by workspace ID"),
4147
+ search: z9.string().optional().describe("Search labels by name"),
4148
+ current_page: z9.coerce.number().int().positive().optional().describe("Page number for pagination"),
4149
+ page_size: z9.coerce.number().int().positive().max(100).optional().describe("Results per page (max 100)")
4027
4150
  },
4028
4151
  getPromptLabel: {
4029
- label_id: z8.string().describe("Label ID to retrieve"),
4030
- organisation_id: z8.string().optional().describe("Organisation ID for filtering"),
4031
- workspace_id: z8.string().optional().describe("Workspace ID for filtering")
4152
+ label_id: z9.string().describe("Label ID to retrieve"),
4153
+ organisation_id: z9.string().optional().describe("Organisation ID for filtering"),
4154
+ workspace_id: z9.string().optional().describe("Workspace ID for filtering")
4032
4155
  },
4033
4156
  updatePromptLabel: {
4034
- label_id: z8.string().describe("Label ID to update"),
4035
- name: z8.string().optional().describe("New name for the label"),
4036
- description: z8.string().optional().describe("New description"),
4037
- color_code: z8.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("New hex color code (e.g., '#FF5733')")
4157
+ label_id: z9.string().describe("Label ID to update"),
4158
+ name: z9.string().optional().describe("New name for the label"),
4159
+ description: z9.string().optional().describe("New description"),
4160
+ color_code: z9.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("New hex color code (e.g., '#FF5733')")
4038
4161
  },
4039
4162
  deletePromptLabel: {
4040
- label_id: z8.string().describe("Label ID to delete")
4163
+ label_id: z9.string().describe("Label ID to delete")
4041
4164
  }
4042
4165
  };
4043
4166
  function registerLabelsTools(server, service) {
@@ -4177,86 +4300,86 @@ function registerLabelsTools(server, service) {
4177
4300
  }
4178
4301
 
4179
4302
  // src/tools/limits.tools.ts
4180
- import { z as z9 } from "zod";
4181
- var conditionSchema = z9.object({
4182
- field: z9.string().describe(
4303
+ import { z as z10 } from "zod";
4304
+ var conditionSchema = z10.object({
4305
+ field: z10.string().describe(
4183
4306
  "The field to match on (e.g., 'virtual_key', 'api_key', 'user_id', 'metadata.key')"
4184
4307
  ),
4185
- operator: z9.string().describe("The comparison operator (e.g., 'is', 'contains', 'is_not')"),
4186
- value: z9.string().describe("The value to match against")
4308
+ operator: z10.string().describe("The comparison operator (e.g., 'is', 'contains', 'is_not')"),
4309
+ value: z10.string().describe("The value to match against")
4187
4310
  });
4188
4311
  var LIMITS_TOOL_SCHEMAS = {
4189
4312
  listRateLimits: {
4190
- workspace_id: z9.string().optional().describe("Filter rate limits by workspace ID")
4313
+ workspace_id: z10.string().optional().describe("Filter rate limits by workspace ID")
4191
4314
  },
4192
4315
  getRateLimit: {
4193
- id: z9.string().describe("The unique identifier of the rate limit")
4316
+ id: z10.string().describe("The unique identifier of the rate limit")
4194
4317
  },
4195
4318
  createRateLimit: {
4196
- conditions: z9.array(conditionSchema).describe(
4319
+ conditions: z10.array(conditionSchema).describe(
4197
4320
  "Array of conditions that determine which requests this rate limit applies to"
4198
4321
  ),
4199
- group_by: z9.array(z9.string()).describe(
4322
+ group_by: z10.array(z10.string()).describe(
4200
4323
  "Array of fields to group the rate limit by (e.g., ['virtual_key'], ['api_key', 'user_id'])"
4201
4324
  ),
4202
- type: z9.enum(["requests", "tokens"]).describe("What to rate limit: 'requests' or 'tokens'"),
4203
- unit: z9.enum(["rpm", "rph", "rpd"]).describe(
4325
+ type: z10.enum(["requests", "tokens"]).describe("What to rate limit: 'requests' or 'tokens'"),
4326
+ unit: z10.enum(["rpm", "rph", "rpd"]).describe(
4204
4327
  "Time unit: 'rpm' (per minute), 'rph' (per hour), or 'rpd' (per day)"
4205
4328
  ),
4206
- value: z9.coerce.number().positive().describe("The maximum allowed value per unit (e.g., 100 rpm)"),
4207
- name: z9.string().optional().describe("Optional name for the rate limit"),
4208
- workspace_id: z9.string().optional().describe("Workspace ID to scope the limit to"),
4209
- organisation_id: z9.string().optional().describe("Organisation ID to scope the limit to")
4329
+ value: z10.coerce.number().positive().describe("The maximum allowed value per unit (e.g., 100 rpm)"),
4330
+ name: z10.string().optional().describe("Optional name for the rate limit"),
4331
+ workspace_id: z10.string().optional().describe("Workspace ID to scope the limit to"),
4332
+ organisation_id: z10.string().optional().describe("Organisation ID to scope the limit to")
4210
4333
  },
4211
4334
  updateRateLimit: {
4212
- id: z9.string().describe("The unique identifier of the rate limit"),
4213
- name: z9.string().optional().describe("New name for the rate limit"),
4214
- unit: z9.enum(["rpm", "rph", "rpd"]).optional().describe(
4335
+ id: z10.string().describe("The unique identifier of the rate limit"),
4336
+ name: z10.string().optional().describe("New name for the rate limit"),
4337
+ unit: z10.enum(["rpm", "rph", "rpd"]).optional().describe(
4215
4338
  "New time unit: 'rpm' (per minute), 'rph' (per hour), or 'rpd' (per day)"
4216
4339
  ),
4217
- value: z9.coerce.number().positive().optional().describe("New maximum allowed value per unit")
4340
+ value: z10.coerce.number().positive().optional().describe("New maximum allowed value per unit")
4218
4341
  },
4219
4342
  deleteRateLimit: {
4220
- id: z9.string().describe("The unique identifier of the rate limit")
4343
+ id: z10.string().describe("The unique identifier of the rate limit")
4221
4344
  },
4222
4345
  listUsageLimits: {
4223
- workspace_id: z9.string().optional().describe("Filter usage limits by workspace ID")
4346
+ workspace_id: z10.string().optional().describe("Filter usage limits by workspace ID")
4224
4347
  },
4225
4348
  getUsageLimit: {
4226
- id: z9.string().describe("The unique identifier of the usage limit")
4349
+ id: z10.string().describe("The unique identifier of the usage limit")
4227
4350
  },
4228
4351
  createUsageLimit: {
4229
- conditions: z9.array(conditionSchema).describe(
4352
+ conditions: z10.array(conditionSchema).describe(
4230
4353
  "Array of conditions that determine which requests this usage limit applies to"
4231
4354
  ),
4232
- group_by: z9.array(z9.string()).describe(
4355
+ group_by: z10.array(z10.string()).describe(
4233
4356
  "Array of fields to group the usage limit by (e.g., ['virtual_key'], ['api_key', 'user_id'])"
4234
4357
  ),
4235
- type: z9.enum(["cost", "tokens"]).describe("What to limit: 'cost' (in dollars) or 'tokens'"),
4236
- credit_limit: z9.coerce.number().positive().describe("The maximum allowed usage (cost in dollars or token count)"),
4237
- name: z9.string().optional().describe("Optional name for the usage limit"),
4238
- alert_threshold: z9.coerce.number().optional().describe("Percentage threshold (0-100) at which to send an alert"),
4239
- periodic_reset: z9.enum(["monthly", "weekly"]).optional().describe("Automatically reset usage counters on this schedule"),
4240
- workspace_id: z9.string().optional().describe("Workspace ID to scope the limit to"),
4241
- organisation_id: z9.string().optional().describe("Organisation ID to scope the limit to")
4358
+ type: z10.enum(["cost", "tokens"]).describe("What to limit: 'cost' (in dollars) or 'tokens'"),
4359
+ credit_limit: z10.coerce.number().positive().describe("The maximum allowed usage (cost in dollars or token count)"),
4360
+ name: z10.string().optional().describe("Optional name for the usage limit"),
4361
+ alert_threshold: z10.coerce.number().optional().describe("Percentage threshold (0-100) at which to send an alert"),
4362
+ periodic_reset: z10.enum(["monthly", "weekly"]).optional().describe("Automatically reset usage counters on this schedule"),
4363
+ workspace_id: z10.string().optional().describe("Workspace ID to scope the limit to"),
4364
+ organisation_id: z10.string().optional().describe("Organisation ID to scope the limit to")
4242
4365
  },
4243
4366
  updateUsageLimit: {
4244
- id: z9.string().describe("The unique identifier of the usage limit"),
4245
- name: z9.string().optional().describe("New name for the usage limit"),
4246
- credit_limit: z9.coerce.number().positive().optional().describe("New maximum allowed usage value"),
4247
- alert_threshold: z9.coerce.number().optional().describe("New alert threshold percentage (0-100)"),
4248
- periodic_reset: z9.enum(["monthly", "weekly"]).optional().describe("New periodic reset schedule"),
4249
- reset_usage_for_value: z9.string().optional().describe("Reset usage counters for a specific group_by value")
4367
+ id: z10.string().describe("The unique identifier of the usage limit"),
4368
+ name: z10.string().optional().describe("New name for the usage limit"),
4369
+ credit_limit: z10.coerce.number().positive().optional().describe("New maximum allowed usage value"),
4370
+ alert_threshold: z10.coerce.number().optional().describe("New alert threshold percentage (0-100)"),
4371
+ periodic_reset: z10.enum(["monthly", "weekly"]).optional().describe("New periodic reset schedule"),
4372
+ reset_usage_for_value: z10.string().optional().describe("Reset usage counters for a specific group_by value")
4250
4373
  },
4251
4374
  deleteUsageLimit: {
4252
- id: z9.string().describe("The unique identifier of the usage limit")
4375
+ id: z10.string().describe("The unique identifier of the usage limit")
4253
4376
  },
4254
4377
  listUsageLimitEntities: {
4255
- limit_id: z9.string().describe("Usage limit policy ID")
4378
+ limit_id: z10.string().describe("Usage limit policy ID")
4256
4379
  },
4257
4380
  resetUsageLimitEntity: {
4258
- limit_id: z9.string().describe("Usage limit policy ID"),
4259
- entity_id: z9.string().describe("Entity ID to reset usage for")
4381
+ limit_id: z10.string().describe("Usage limit policy ID"),
4382
+ entity_id: z10.string().describe("Entity ID to reset usage for")
4260
4383
  }
4261
4384
  };
4262
4385
  function formatRateLimit(limit) {
@@ -4562,8 +4685,8 @@ function registerLimitsTools(server, service) {
4562
4685
  }
4563
4686
 
4564
4687
  // src/tools/logging.tools.ts
4565
- import { z as z10 } from "zod";
4566
- var logExportFieldSchema = z10.enum([
4688
+ import { z as z11 } from "zod";
4689
+ var logExportFieldSchema = z11.enum([
4567
4690
  "id",
4568
4691
  "trace_id",
4569
4692
  "created_at",
@@ -4587,64 +4710,64 @@ var logExportFieldSchema = z10.enum([
4587
4710
  ]);
4588
4711
  var LOGGING_TOOL_SCHEMAS = {
4589
4712
  insertLog: {
4590
- request_url: z10.string().optional().describe("The endpoint URL being called"),
4591
- request_provider: z10.string().optional().describe("AI provider name (e.g., 'openai', 'anthropic')"),
4592
- request_method: z10.string().optional().default("post").describe("HTTP method used (defaults to 'post')"),
4593
- request_headers: z10.record(z10.string(), z10.string()).optional().describe("Request headers as key-value pairs"),
4594
- request_body: z10.record(z10.string(), z10.unknown()).optional().describe("Request payload/body"),
4595
- response_status: z10.coerce.number().optional().default(200).describe("HTTP response status code (defaults to 200)"),
4596
- response_headers: z10.record(z10.string(), z10.string()).optional().describe("Response headers as key-value pairs"),
4597
- response_body: z10.record(z10.string(), z10.unknown()).optional().describe("Response payload/body"),
4598
- response_time: z10.coerce.number().optional().describe("Response latency in milliseconds"),
4599
- streaming_mode: z10.boolean().optional().default(false).describe("Whether the response was streamed"),
4600
- metadata_organization: z10.string().optional().describe("Organization identifier for the log"),
4601
- metadata_user: z10.string().optional().describe("User identifier for the log"),
4602
- metadata_trace_id: z10.string().optional().describe("Trace ID for distributed tracing"),
4603
- metadata_span_id: z10.string().optional().describe("Span ID for tracing"),
4604
- metadata_span_name: z10.string().optional().describe("Span name for tracing"),
4605
- metadata_parent_span_id: z10.string().optional().describe("Parent span ID for tracing"),
4606
- metadata_custom: z10.record(z10.string(), z10.unknown()).optional().describe("Additional custom metadata key-value pairs")
4713
+ request_url: z11.string().optional().describe("The endpoint URL being called"),
4714
+ request_provider: z11.string().optional().describe("AI provider name (e.g., 'openai', 'anthropic')"),
4715
+ request_method: z11.string().optional().default("post").describe("HTTP method used (defaults to 'post')"),
4716
+ request_headers: z11.record(z11.string(), z11.string()).optional().describe("Request headers as key-value pairs"),
4717
+ request_body: z11.record(z11.string(), z11.unknown()).optional().describe("Request payload/body"),
4718
+ response_status: z11.coerce.number().optional().default(200).describe("HTTP response status code (defaults to 200)"),
4719
+ response_headers: z11.record(z11.string(), z11.string()).optional().describe("Response headers as key-value pairs"),
4720
+ response_body: z11.record(z11.string(), z11.unknown()).optional().describe("Response payload/body"),
4721
+ response_time: z11.coerce.number().optional().describe("Response latency in milliseconds"),
4722
+ streaming_mode: z11.boolean().optional().default(false).describe("Whether the response was streamed"),
4723
+ metadata_organization: z11.string().optional().describe("Organization identifier for the log"),
4724
+ metadata_user: z11.string().optional().describe("User identifier for the log"),
4725
+ metadata_trace_id: z11.string().optional().describe("Trace ID for distributed tracing"),
4726
+ metadata_span_id: z11.string().optional().describe("Span ID for tracing"),
4727
+ metadata_span_name: z11.string().optional().describe("Span name for tracing"),
4728
+ metadata_parent_span_id: z11.string().optional().describe("Parent span ID for tracing"),
4729
+ metadata_custom: z11.record(z11.string(), z11.unknown()).optional().describe("Additional custom metadata key-value pairs")
4607
4730
  },
4608
4731
  createLogExport: {
4609
- workspace_id: z10.string().optional().describe("Workspace ID for the export"),
4610
- description: z10.string().optional().describe("Human-readable description for the export job"),
4611
- time_min: z10.string().describe(
4732
+ workspace_id: z11.string().optional().describe("Workspace ID for the export"),
4733
+ description: z11.string().optional().describe("Human-readable description for the export job"),
4734
+ time_min: z11.string().describe(
4612
4735
  "Minimum time filter in date format (e.g., '2024-01-01' or ISO 8601)"
4613
4736
  ),
4614
- time_max: z10.string().describe(
4737
+ time_max: z11.string().describe(
4615
4738
  "Maximum time filter in date format (e.g., '2024-01-31' or ISO 8601)"
4616
4739
  ),
4617
- cost_min: z10.coerce.number().optional().describe("Minimum cost filter"),
4618
- cost_max: z10.coerce.number().optional().describe("Maximum cost filter"),
4619
- total_units_min: z10.coerce.number().optional().describe("Minimum total units (tokens) filter"),
4620
- total_units_max: z10.coerce.number().optional().describe("Maximum total units (tokens) filter"),
4621
- ai_model: z10.array(z10.string()).optional().describe("Filter by specific AI model names"),
4622
- requested_fields: z10.array(logExportFieldSchema).describe(
4740
+ cost_min: z11.coerce.number().optional().describe("Minimum cost filter"),
4741
+ cost_max: z11.coerce.number().optional().describe("Maximum cost filter"),
4742
+ total_units_min: z11.coerce.number().optional().describe("Minimum total units (tokens) filter"),
4743
+ total_units_max: z11.coerce.number().optional().describe("Maximum total units (tokens) filter"),
4744
+ ai_model: z11.array(z11.string()).optional().describe("Filter by specific AI model names"),
4745
+ requested_fields: z11.array(logExportFieldSchema).describe(
4623
4746
  "Fields to include in export: id, trace_id, created_at, request, response, is_success, ai_org, ai_model, req_units, res_units, total_units, request_url, cost, cost_currency, response_time, response_status_code, mode, config, prompt_slug, metadata"
4624
4747
  )
4625
4748
  },
4626
4749
  listLogExports: {
4627
- workspace_id: z10.string().describe("Workspace ID to list exports for (required)")
4750
+ workspace_id: z11.string().describe("Workspace ID to list exports for (required)")
4628
4751
  },
4629
4752
  getLogExport: {
4630
- export_id: z10.string().describe("The unique ID of the log export")
4753
+ export_id: z11.string().describe("The unique ID of the log export")
4631
4754
  },
4632
4755
  startLogExport: {
4633
- export_id: z10.string().describe("The unique ID of the log export to start")
4756
+ export_id: z11.string().describe("The unique ID of the log export to start")
4634
4757
  },
4635
4758
  cancelLogExport: {
4636
- export_id: z10.string().describe("The unique ID of the log export to cancel")
4759
+ export_id: z11.string().describe("The unique ID of the log export to cancel")
4637
4760
  },
4638
4761
  downloadLogExport: {
4639
- export_id: z10.string().describe("The unique ID of the log export to download")
4762
+ export_id: z11.string().describe("The unique ID of the log export to download")
4640
4763
  },
4641
4764
  updateLogExport: {
4642
- export_id: z10.string().describe("The unique ID of the log export to update"),
4643
- workspace_id: z10.string().optional().describe("Workspace ID for the export"),
4644
- time_of_generation_max: z10.string().optional().describe(
4765
+ export_id: z11.string().describe("The unique ID of the log export to update"),
4766
+ workspace_id: z11.string().optional().describe("Workspace ID for the export"),
4767
+ time_of_generation_max: z11.string().optional().describe(
4645
4768
  "Maximum time filter in date format (e.g., '2024-07-25' or ISO 8601)"
4646
4769
  ),
4647
- requested_fields: z10.array(logExportFieldSchema).optional().describe(
4770
+ requested_fields: z11.array(logExportFieldSchema).optional().describe(
4648
4771
  "Fields to include in export: id, trace_id, created_at, request, response, is_success, ai_org, ai_model, req_units, res_units, total_units, request_url, cost, cost_currency, response_time, response_status_code, mode, config, prompt_slug, metadata"
4649
4772
  )
4650
4773
  }
@@ -4885,73 +5008,73 @@ function registerLoggingTools(server, service) {
4885
5008
  }
4886
5009
 
4887
5010
  // src/tools/mcp-integrations.tools.ts
4888
- import { z as z11 } from "zod";
5011
+ import { z as z12 } from "zod";
4889
5012
  var MCP_INTEGRATIONS_TOOL_SCHEMAS = {
4890
5013
  listMcpIntegrations: {
4891
- current_page: z11.coerce.number().positive().optional().describe("Page number for pagination"),
4892
- page_size: z11.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)"),
4893
- workspace_id: z11.string().optional().describe("Filter by workspace ID")
5014
+ current_page: z12.coerce.number().int().positive().optional().describe("Page number for pagination"),
5015
+ page_size: z12.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)"),
5016
+ workspace_id: z12.string().optional().describe("Filter by workspace ID")
4894
5017
  },
4895
5018
  createMcpIntegration: {
4896
- name: z11.string().describe("Display name for the MCP integration"),
4897
- url: z11.string().describe("URL endpoint of the MCP server to integrate"),
4898
- auth_type: z11.enum(["oauth_auto", "headers", "none"]).describe(
5019
+ name: z12.string().describe("Display name for the MCP integration"),
5020
+ url: z12.string().describe("URL endpoint of the MCP server to integrate"),
5021
+ auth_type: z12.enum(["oauth_auto", "headers", "none"]).describe(
4899
5022
  "Authentication type: 'none', 'headers' (custom headers), or 'oauth_auto' (OAuth)"
4900
5023
  ),
4901
- transport: z11.enum(["http", "sse"]).describe(
5024
+ transport: z12.enum(["http", "sse"]).describe(
4902
5025
  "MCP transport protocol: 'http' (streamable HTTP) or 'sse' (server-sent events)"
4903
5026
  ),
4904
- slug: z11.string().optional().describe("Custom slug. Auto-generated if omitted"),
4905
- description: z11.string().optional().describe("Description of the MCP integration"),
4906
- custom_headers: z11.record(z11.string(), z11.string()).optional().describe(
5027
+ slug: z12.string().optional().describe("Custom slug. Auto-generated if omitted"),
5028
+ description: z12.string().optional().describe("Description of the MCP integration"),
5029
+ custom_headers: z12.record(z12.string(), z12.string()).optional().describe(
4907
5030
  'Custom headers for authentication (e.g. { "Authorization": "Bearer xxx" }). Sent via configurations.custom_headers'
4908
5031
  ),
4909
- workspace_id: z11.string().optional().describe(
5032
+ workspace_id: z12.string().optional().describe(
4910
5033
  "Workspace ID \u2014 required when using organization admin API keys"
4911
5034
  )
4912
5035
  },
4913
5036
  getMcpIntegration: {
4914
- id: z11.string().describe("The MCP integration ID or slug to retrieve")
5037
+ id: z12.string().describe("The MCP integration ID or slug to retrieve")
4915
5038
  },
4916
5039
  updateMcpIntegration: {
4917
- id: z11.string().describe("The MCP integration ID or slug to update"),
4918
- name: z11.string().optional().describe("New display name"),
4919
- description: z11.string().optional().describe("New description"),
4920
- url: z11.string().optional().describe("New URL endpoint"),
4921
- auth_type: z11.enum(["oauth_auto", "headers", "none"]).optional().describe("New authentication type"),
4922
- transport: z11.enum(["http", "sse"]).optional().describe("New transport protocol"),
4923
- custom_headers: z11.record(z11.string(), z11.string()).optional().describe(
5040
+ id: z12.string().describe("The MCP integration ID or slug to update"),
5041
+ name: z12.string().optional().describe("New display name"),
5042
+ description: z12.string().optional().describe("New description"),
5043
+ url: z12.string().optional().describe("New URL endpoint"),
5044
+ auth_type: z12.enum(["oauth_auto", "headers", "none"]).optional().describe("New authentication type"),
5045
+ transport: z12.enum(["http", "sse"]).optional().describe("New transport protocol"),
5046
+ custom_headers: z12.record(z12.string(), z12.string()).optional().describe(
4924
5047
  "New custom headers for authentication. Sent via configurations.custom_headers"
4925
5048
  )
4926
5049
  },
4927
5050
  deleteMcpIntegration: {
4928
- id: z11.string().describe("The MCP integration ID or slug to delete")
5051
+ id: z12.string().describe("The MCP integration ID or slug to delete")
4929
5052
  },
4930
5053
  getMcpIntegrationMetadata: {
4931
- id: z11.string().describe("The MCP integration ID or slug")
5054
+ id: z12.string().describe("The MCP integration ID or slug")
4932
5055
  },
4933
5056
  listMcpIntegrationCapabilities: {
4934
- id: z11.string().describe("The MCP integration ID or slug")
5057
+ id: z12.string().describe("The MCP integration ID or slug")
4935
5058
  },
4936
5059
  updateMcpIntegrationCapabilities: {
4937
- id: z11.string().describe("The MCP integration ID or slug"),
4938
- capabilities: z11.array(
4939
- z11.object({
4940
- name: z11.string().describe("Capability name"),
4941
- type: z11.enum(["tool", "prompt", "resource"]).describe("Capability type"),
4942
- enabled: z11.boolean().describe("Whether to enable the capability")
5060
+ id: z12.string().describe("The MCP integration ID or slug"),
5061
+ capabilities: z12.array(
5062
+ z12.object({
5063
+ name: z12.string().describe("Capability name"),
5064
+ type: z12.enum(["tool", "prompt", "resource"]).describe("Capability type"),
5065
+ enabled: z12.boolean().describe("Whether to enable the capability")
4943
5066
  })
4944
5067
  ).min(1).describe("Array of capability updates")
4945
5068
  },
4946
5069
  listMcpIntegrationWorkspaces: {
4947
- id: z11.string().describe("The MCP integration ID or slug")
5070
+ id: z12.string().describe("The MCP integration ID or slug")
4948
5071
  },
4949
5072
  updateMcpIntegrationWorkspaces: {
4950
- id: z11.string().describe("The MCP integration ID or slug"),
4951
- workspaces: z11.array(
4952
- z11.object({
4953
- id: z11.string().describe("Workspace ID"),
4954
- enabled: z11.boolean().describe("Whether workspace has access")
5073
+ id: z12.string().describe("The MCP integration ID or slug"),
5074
+ workspaces: z12.array(
5075
+ z12.object({
5076
+ id: z12.string().describe("Workspace ID"),
5077
+ enabled: z12.boolean().describe("Whether workspace has access")
4955
5078
  })
4956
5079
  ).min(1).describe("Array of workspace access updates")
4957
5080
  }
@@ -5243,7 +5366,7 @@ function registerMcpIntegrationsTools(server, service) {
5243
5366
  }
5244
5367
 
5245
5368
  // src/tools/mcp-servers.tools.ts
5246
- import { z as z12 } from "zod";
5369
+ import { z as z13 } from "zod";
5247
5370
 
5248
5371
  // src/tools/utils.ts
5249
5372
  function formatFullName(firstName, lastName) {
@@ -5253,56 +5376,56 @@ function formatFullName(firstName, lastName) {
5253
5376
  // src/tools/mcp-servers.tools.ts
5254
5377
  var MCP_SERVERS_TOOL_SCHEMAS = {
5255
5378
  listMcpServers: {
5256
- current_page: z12.coerce.number().positive().optional().describe("Page number for pagination"),
5257
- page_size: z12.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)"),
5258
- workspace_id: z12.string().optional().describe("Filter by workspace ID")
5379
+ current_page: z13.coerce.number().int().positive().optional().describe("Page number for pagination"),
5380
+ page_size: z13.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)"),
5381
+ workspace_id: z13.string().optional().describe("Filter by workspace ID")
5259
5382
  },
5260
5383
  createMcpServer: {
5261
- name: z12.string().describe("Display name for the MCP server"),
5262
- mcp_integration_id: z12.string().describe("ID or slug of the MCP integration this server belongs to"),
5263
- slug: z12.string().optional().describe("Custom slug. Auto-generated if omitted"),
5264
- description: z12.string().optional().describe("Description of the MCP server")
5384
+ name: z13.string().describe("Display name for the MCP server"),
5385
+ mcp_integration_id: z13.string().describe("ID or slug of the MCP integration this server belongs to"),
5386
+ slug: z13.string().optional().describe("Custom slug. Auto-generated if omitted"),
5387
+ description: z13.string().optional().describe("Description of the MCP server")
5265
5388
  },
5266
5389
  getMcpServer: {
5267
- id: z12.string().describe("The MCP server ID or slug to retrieve")
5390
+ id: z13.string().describe("The MCP server ID or slug to retrieve")
5268
5391
  },
5269
5392
  updateMcpServer: {
5270
- id: z12.string().describe("The MCP server ID or slug to update"),
5271
- name: z12.string().optional().describe("New display name"),
5272
- description: z12.string().optional().describe("New description")
5393
+ id: z13.string().describe("The MCP server ID or slug to update"),
5394
+ name: z13.string().optional().describe("New display name"),
5395
+ description: z13.string().optional().describe("New description")
5273
5396
  },
5274
5397
  deleteMcpServer: {
5275
- id: z12.string().describe("The MCP server ID or slug to delete")
5398
+ id: z13.string().describe("The MCP server ID or slug to delete")
5276
5399
  },
5277
5400
  testMcpServer: {
5278
- id: z12.string().describe("The MCP server ID or slug to test")
5401
+ id: z13.string().describe("The MCP server ID or slug to test")
5279
5402
  },
5280
5403
  listMcpServerCapabilities: {
5281
- id: z12.string().describe("The MCP server ID or slug"),
5282
- current_page: z12.coerce.number().positive().optional().describe("Page number for pagination"),
5283
- page_size: z12.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)")
5404
+ id: z13.string().describe("The MCP server ID or slug"),
5405
+ current_page: z13.coerce.number().int().positive().optional().describe("Page number for pagination"),
5406
+ page_size: z13.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)")
5284
5407
  },
5285
5408
  updateMcpServerCapabilities: {
5286
- id: z12.string().describe("The MCP server ID or slug"),
5287
- capabilities: z12.array(
5288
- z12.object({
5289
- name: z12.string().describe("Capability name"),
5290
- type: z12.enum(["tool", "prompt", "resource"]).describe("Capability type"),
5291
- enabled: z12.boolean().describe("Whether to enable the capability")
5409
+ id: z13.string().describe("The MCP server ID or slug"),
5410
+ capabilities: z13.array(
5411
+ z13.object({
5412
+ name: z13.string().describe("Capability name"),
5413
+ type: z13.enum(["tool", "prompt", "resource"]).describe("Capability type"),
5414
+ enabled: z13.boolean().describe("Whether to enable the capability")
5292
5415
  })
5293
5416
  ).min(1).describe("Array of capability updates")
5294
5417
  },
5295
5418
  listMcpServerUserAccess: {
5296
- id: z12.string().describe("The MCP server ID or slug"),
5297
- current_page: z12.coerce.number().positive().optional().describe("Page number for pagination"),
5298
- page_size: z12.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)")
5419
+ id: z13.string().describe("The MCP server ID or slug"),
5420
+ current_page: z13.coerce.number().int().positive().optional().describe("Page number for pagination"),
5421
+ page_size: z13.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)")
5299
5422
  },
5300
5423
  updateMcpServerUserAccess: {
5301
- id: z12.string().describe("The MCP server ID or slug"),
5302
- users: z12.array(
5303
- z12.object({
5304
- user_id: z12.string().describe("User ID"),
5305
- enabled: z12.boolean().describe("Whether user has access")
5424
+ id: z13.string().describe("The MCP server ID or slug"),
5425
+ users: z13.array(
5426
+ z13.object({
5427
+ user_id: z13.string().describe("User ID"),
5428
+ enabled: z13.boolean().describe("Whether user has access")
5306
5429
  })
5307
5430
  ).min(1).describe("Array of user access updates")
5308
5431
  }
@@ -5546,40 +5669,40 @@ function registerMcpServersTools(server, service) {
5546
5669
  }
5547
5670
 
5548
5671
  // src/tools/partials.tools.ts
5549
- import { z as z13 } from "zod";
5672
+ import { z as z14 } from "zod";
5550
5673
  var PARTIALS_TOOL_SCHEMAS = {
5551
5674
  createPromptPartial: {
5552
- name: z13.string().describe("Display name for the partial"),
5553
- string: z13.string().describe("The partial content/template string"),
5554
- workspace_id: z13.string().optional().describe(
5675
+ name: z14.string().describe("Display name for the partial"),
5676
+ string: z14.string().describe("The partial content/template string"),
5677
+ workspace_id: z14.string().optional().describe(
5555
5678
  "Workspace ID to create partial in (required for org-level API keys)"
5556
5679
  ),
5557
- version_description: z13.string().optional().describe("Description for this version")
5680
+ version_description: z14.string().optional().describe("Description for this version")
5558
5681
  },
5559
5682
  listPromptPartials: {
5560
- collection_id: z13.string().optional().describe(
5683
+ collection_id: z14.string().optional().describe(
5561
5684
  "Filter by collection ID. Optional \u2014 omit to list all partials across collections"
5562
5685
  )
5563
5686
  },
5564
5687
  getPromptPartial: {
5565
- prompt_partial_id: z13.string().describe("Prompt partial ID or slug to retrieve")
5688
+ prompt_partial_id: z14.string().describe("Prompt partial ID or slug to retrieve")
5566
5689
  },
5567
5690
  updatePromptPartial: {
5568
- prompt_partial_id: z13.string().describe("Prompt partial ID or slug to update"),
5569
- name: z13.string().optional().describe("New display name for the partial"),
5570
- string: z13.string().optional().describe("New content for the partial"),
5571
- description: z13.string().optional().describe("Description for this version"),
5572
- status: z13.enum(["active", "archived"]).optional().describe("New status for the partial")
5691
+ prompt_partial_id: z14.string().describe("Prompt partial ID or slug to update"),
5692
+ name: z14.string().optional().describe("New display name for the partial"),
5693
+ string: z14.string().optional().describe("New content for the partial"),
5694
+ description: z14.string().optional().describe("Description for this version"),
5695
+ status: z14.enum(["active", "archived"]).optional().describe("New status for the partial")
5573
5696
  },
5574
5697
  deletePromptPartial: {
5575
- prompt_partial_id: z13.string().describe("Prompt partial ID or slug to delete")
5698
+ prompt_partial_id: z14.string().describe("Prompt partial ID or slug to delete")
5576
5699
  },
5577
5700
  listPartialVersions: {
5578
- prompt_partial_id: z13.string().describe("Prompt partial ID or slug to list versions for")
5701
+ prompt_partial_id: z14.string().describe("Prompt partial ID or slug to list versions for")
5579
5702
  },
5580
5703
  publishPartial: {
5581
- prompt_partial_id: z13.string().describe("Prompt partial ID or slug"),
5582
- version: z13.coerce.number().positive().describe("Version number to publish as default")
5704
+ prompt_partial_id: z14.string().describe("Prompt partial ID or slug"),
5705
+ version: z14.coerce.number().positive().describe("Version number to publish as default")
5583
5706
  }
5584
5707
  };
5585
5708
  function registerPartialsTools(server, service) {
@@ -5765,57 +5888,57 @@ function registerPartialsTools(server, service) {
5765
5888
  }
5766
5889
 
5767
5890
  // src/tools/prompts.tools.ts
5768
- import { z as z15 } from "zod";
5891
+ import { z as z16 } from "zod";
5769
5892
 
5770
5893
  // src/lib/schemas.ts
5771
- import { z as z14 } from "zod";
5772
- var PromptFunctionSchema = z14.object({
5773
- name: z14.string().describe("Function name"),
5774
- description: z14.string().optional().describe("Function description"),
5775
- parameters: z14.record(z14.string(), z14.unknown()).optional().describe("Function parameters schema")
5894
+ import { z as z15 } from "zod";
5895
+ var PromptFunctionSchema = z15.object({
5896
+ name: z15.string().describe("Function name"),
5897
+ description: z15.string().optional().describe("Function description"),
5898
+ parameters: z15.record(z15.string(), z15.unknown()).optional().describe("Function parameters schema")
5776
5899
  });
5777
- var PromptToolSchema = z14.object({
5778
- type: z14.literal("function").describe("Tool type"),
5900
+ var PromptToolSchema = z15.object({
5901
+ type: z15.literal("function").describe("Tool type"),
5779
5902
  function: PromptFunctionSchema.describe("Function definition")
5780
5903
  });
5781
- var HyperparametersSchema = z14.object({
5782
- max_tokens: z14.coerce.number().positive().optional().describe("Maximum tokens to generate"),
5783
- temperature: z14.coerce.number().min(0).max(2).optional().describe("Sampling temperature (0-2)"),
5784
- top_p: z14.coerce.number().min(0).max(1).optional().describe("Top-p sampling (0-1)"),
5785
- top_k: z14.coerce.number().positive().optional().describe("Top-k sampling"),
5786
- presence_penalty: z14.coerce.number().min(-2).max(2).optional().describe("Presence penalty (-2 to 2)"),
5787
- frequency_penalty: z14.coerce.number().min(-2).max(2).optional().describe("Frequency penalty (-2 to 2)"),
5788
- stop: z14.array(z14.string()).optional().describe("Stop sequences")
5904
+ var HyperparametersSchema = z15.object({
5905
+ max_tokens: z15.coerce.number().positive().optional().describe("Maximum tokens to generate"),
5906
+ temperature: z15.coerce.number().min(0).max(2).optional().describe("Sampling temperature (0-2)"),
5907
+ top_p: z15.coerce.number().min(0).max(1).optional().describe("Top-p sampling (0-1)"),
5908
+ top_k: z15.coerce.number().positive().optional().describe("Top-k sampling"),
5909
+ presence_penalty: z15.coerce.number().min(-2).max(2).optional().describe("Presence penalty (-2 to 2)"),
5910
+ frequency_penalty: z15.coerce.number().min(-2).max(2).optional().describe("Frequency penalty (-2 to 2)"),
5911
+ stop: z15.array(z15.string()).optional().describe("Stop sequences")
5789
5912
  });
5790
- var PromptAppIdentifierSchema = z14.string().min(1).describe(
5913
+ var PromptAppIdentifierSchema = z15.string().min(1).describe(
5791
5914
  "App identifier (REQUIRED). Use your deployed app name, for example 'hourlink' or 'support-console'."
5792
5915
  );
5793
- var PromptEnvironmentIdentifierSchema = z14.string().min(1).describe(
5916
+ var PromptEnvironmentIdentifierSchema = z15.string().min(1).describe(
5794
5917
  "Environment identifier (REQUIRED). Use your environment name, for example 'dev', 'staging', 'prod', or 'qa'."
5795
5918
  );
5796
- var BillingMetadataSchema = z14.object({
5797
- client_id: z14.string().describe("Client ID for billing attribution (REQUIRED)"),
5919
+ var BillingMetadataSchema = z15.object({
5920
+ client_id: z15.string().describe("Client ID for billing attribution (REQUIRED)"),
5798
5921
  app: PromptAppIdentifierSchema,
5799
5922
  env: PromptEnvironmentIdentifierSchema,
5800
- project_id: z14.string().optional().describe("Project ID for granular billing"),
5801
- feature: z14.string().optional().describe("Feature name for tracking")
5923
+ project_id: z15.string().optional().describe("Project ID for granular billing"),
5924
+ feature: z15.string().optional().describe("Feature name for tracking")
5802
5925
  });
5803
- var ToolChoiceSchema = z14.object({
5804
- mode: z14.enum(["auto", "none", "function"]).describe(
5926
+ var ToolChoiceSchema = z15.object({
5927
+ mode: z15.enum(["auto", "none", "function"]).describe(
5805
5928
  "Tool choice mode: auto lets the model decide, none disables tool use, function forces one function by name"
5806
5929
  ),
5807
- function_name: z14.string().optional().describe("Required when mode is 'function'; ignored otherwise")
5930
+ function_name: z15.string().optional().describe("Required when mode is 'function'; ignored otherwise")
5808
5931
  }).superRefine((value, ctx) => {
5809
5932
  if (value.mode === "function" && !value.function_name) {
5810
5933
  ctx.addIssue({
5811
- code: z14.ZodIssueCode.custom,
5934
+ code: z15.ZodIssueCode.custom,
5812
5935
  path: ["function_name"],
5813
5936
  message: "function_name is required when mode is 'function'"
5814
5937
  });
5815
5938
  }
5816
5939
  if (value.mode !== "function" && value.function_name !== void 0) {
5817
5940
  ctx.addIssue({
5818
- code: z14.ZodIssueCode.custom,
5941
+ code: z15.ZodIssueCode.custom,
5819
5942
  path: ["function_name"],
5820
5943
  message: "function_name is only allowed when mode is 'function'"
5821
5944
  });
@@ -5837,98 +5960,98 @@ function toPromptToolChoice(toolChoice) {
5837
5960
  }
5838
5961
 
5839
5962
  // src/tools/prompts.tools.ts
5840
- var PROMPT_VARIABLES_SCHEMA = z15.record(z15.string(), z15.union([z15.string(), z15.coerce.number(), z15.boolean()])).describe("Variable values to substitute into the template");
5841
- var PROMPT_TEMPLATE_CONTENT_BLOCK_SCHEMA = z15.object({
5842
- type: z15.string().describe("Content block type"),
5843
- text: z15.string().optional().describe("Text content for text-based blocks")
5963
+ var PROMPT_VARIABLES_SCHEMA = z16.record(z16.string(), z16.union([z16.string(), z16.coerce.number(), z16.boolean()])).describe("Variable values to substitute into the template");
5964
+ var PROMPT_TEMPLATE_CONTENT_BLOCK_SCHEMA = z16.object({
5965
+ type: z16.string().describe("Content block type"),
5966
+ text: z16.string().optional().describe("Text content for text-based blocks")
5844
5967
  }).passthrough().describe("Content block within a structured chat message");
5845
- var PROMPT_TEMPLATE_MESSAGE_SCHEMA = z15.object({
5846
- role: z15.enum(["system", "user", "assistant"]).describe("Message role in the chat template"),
5847
- content: z15.array(PROMPT_TEMPLATE_CONTENT_BLOCK_SCHEMA).describe("Message content blocks")
5968
+ var PROMPT_TEMPLATE_MESSAGE_SCHEMA = z16.object({
5969
+ role: z16.enum(["system", "user", "assistant"]).describe("Message role in the chat template"),
5970
+ content: z16.array(PROMPT_TEMPLATE_CONTENT_BLOCK_SCHEMA).describe("Message content blocks")
5848
5971
  }).passthrough().describe("Structured chat message in a prompt template");
5849
5972
  var PROMPTS_TOOL_SCHEMAS = {
5850
5973
  createPrompt: {
5851
- name: z15.string().describe("Display name for the prompt"),
5852
- collection_id: z15.string().describe(
5974
+ name: z16.string().describe("Display name for the prompt"),
5975
+ collection_id: z16.string().describe(
5853
5976
  "Collection ID to organize the prompt in (use list_collections to find)"
5854
5977
  ),
5855
- string: z15.string().optional().describe(
5978
+ string: z16.string().optional().describe(
5856
5979
  "Legacy prompt template string. Use plain text for single-message prompts, or a JSON-encoded messages array string for multi-message chat prompts."
5857
5980
  ),
5858
- messages: z15.array(PROMPT_TEMPLATE_MESSAGE_SCHEMA).optional().describe(
5981
+ messages: z16.array(PROMPT_TEMPLATE_MESSAGE_SCHEMA).optional().describe(
5859
5982
  "Structured chat template alias. Serialized to the legacy string format before creation."
5860
5983
  ),
5861
- parameters: z15.record(z15.string(), z15.unknown()).describe("Default values for template variables"),
5862
- virtual_key: z15.string().describe("Virtual key slug for model access"),
5863
- model: z15.string().optional().describe(
5984
+ parameters: z16.record(z16.string(), z16.unknown()).describe("Default values for template variables"),
5985
+ virtual_key: z16.string().describe("Virtual key slug for model access"),
5986
+ model: z16.string().optional().describe(
5864
5987
  "Model identifier (e.g., 'gpt-4', 'claude-3-opus'). Required unless ai_model_id or finetune_id is provided"
5865
5988
  ),
5866
- ai_model_id: z15.string().optional().describe(
5989
+ ai_model_id: z16.string().optional().describe(
5867
5990
  "AI model ID (alternative to model). Required unless model or finetune_id is provided"
5868
5991
  ),
5869
- finetune_id: z15.string().optional().describe(
5992
+ finetune_id: z16.string().optional().describe(
5870
5993
  "Fine-tune ID (alternative to model). Required unless model or ai_model_id is provided"
5871
5994
  ),
5872
- version_description: z15.string().optional().describe("Description for this prompt version"),
5873
- template_metadata: z15.record(z15.string(), z15.unknown()).optional().describe("Custom metadata (app, env, source_file, etc.)"),
5874
- functions: z15.array(PromptFunctionSchema).optional().describe("Function definitions for function calling"),
5875
- tools: z15.array(PromptToolSchema).optional().describe("Tool definitions for tool use"),
5995
+ version_description: z16.string().optional().describe("Description for this prompt version"),
5996
+ template_metadata: z16.record(z16.string(), z16.unknown()).optional().describe("Custom metadata (app, env, source_file, etc.)"),
5997
+ functions: z16.array(PromptFunctionSchema).optional().describe("Function definitions for function calling"),
5998
+ tools: z16.array(PromptToolSchema).optional().describe("Tool definitions for tool use"),
5876
5999
  tool_choice: ToolChoiceSchema.optional().describe("Tool choice strategy"),
5877
- dry_run: z15.boolean().optional().describe("When true, validate without creating")
6000
+ dry_run: z16.boolean().optional().describe("When true, validate without creating")
5878
6001
  },
5879
6002
  listPrompts: {
5880
- collection_id: z15.string().optional().describe(
6003
+ collection_id: z16.string().optional().describe(
5881
6004
  "Filter by collection ID (recommended for app-specific prompts)"
5882
6005
  ),
5883
- workspace_id: z15.string().optional().describe("Filter by workspace ID"),
5884
- search: z15.string().optional().describe("Search prompts by name"),
5885
- current_page: z15.coerce.number().positive().optional().describe("Page number for pagination"),
5886
- page_size: z15.coerce.number().positive().max(100).optional().describe("Results per page (max 100)")
6006
+ workspace_id: z16.string().optional().describe("Filter by workspace ID"),
6007
+ search: z16.string().optional().describe("Search prompts by name"),
6008
+ current_page: z16.coerce.number().int().positive().optional().describe("Page number for pagination"),
6009
+ page_size: z16.coerce.number().int().positive().max(100).optional().describe("Results per page (max 100)")
5887
6010
  },
5888
6011
  getPrompt: {
5889
- prompt_id: z15.string().describe("Prompt ID or slug to retrieve")
6012
+ prompt_id: z16.string().describe("Prompt ID or slug to retrieve")
5890
6013
  },
5891
6014
  updatePrompt: {
5892
- prompt_id: z15.string().describe("Prompt ID or slug to update"),
5893
- name: z15.string().optional().describe("New display name for the prompt"),
5894
- collection_id: z15.string().optional().describe("Move to a different collection"),
5895
- string: z15.string().optional().describe(
6015
+ prompt_id: z16.string().describe("Prompt ID or slug to update"),
6016
+ name: z16.string().optional().describe("New display name for the prompt"),
6017
+ collection_id: z16.string().optional().describe("Move to a different collection"),
6018
+ string: z16.string().optional().describe(
5896
6019
  "Legacy prompt template string. Use plain text for single-message prompts, or a JSON-encoded messages array string for multi-message chat prompts."
5897
6020
  ),
5898
- messages: z15.array(PROMPT_TEMPLATE_MESSAGE_SCHEMA).optional().describe(
6021
+ messages: z16.array(PROMPT_TEMPLATE_MESSAGE_SCHEMA).optional().describe(
5899
6022
  "Structured chat template alias for updates. Serialized to the legacy string format before the prompt is updated."
5900
6023
  ),
5901
- parameters: z15.record(z15.string(), z15.unknown()).optional().describe("New default values for template variables"),
5902
- model: z15.string().optional().describe("New model identifier"),
5903
- virtual_key: z15.string().optional().describe("New virtual key slug"),
5904
- version_description: z15.string().optional().describe("Description for this version"),
5905
- template_metadata: z15.record(z15.string(), z15.unknown()).optional().describe("New metadata"),
5906
- functions: z15.array(PromptFunctionSchema).optional().describe("New function definitions"),
5907
- tools: z15.array(PromptToolSchema).optional().describe("New tool definitions"),
6024
+ parameters: z16.record(z16.string(), z16.unknown()).optional().describe("New default values for template variables"),
6025
+ model: z16.string().optional().describe("New model identifier"),
6026
+ virtual_key: z16.string().optional().describe("New virtual key slug"),
6027
+ version_description: z16.string().optional().describe("Description for this version"),
6028
+ template_metadata: z16.record(z16.string(), z16.unknown()).optional().describe("New metadata"),
6029
+ functions: z16.array(PromptFunctionSchema).optional().describe("New function definitions"),
6030
+ tools: z16.array(PromptToolSchema).optional().describe("New tool definitions"),
5908
6031
  tool_choice: ToolChoiceSchema.optional().describe(
5909
6032
  "New tool choice strategy"
5910
6033
  ),
5911
- dry_run: z15.boolean().optional().describe("When true, validate without updating")
6034
+ dry_run: z16.boolean().optional().describe("When true, validate without updating")
5912
6035
  },
5913
6036
  deletePrompt: {
5914
- prompt_id: z15.string().describe("Prompt ID or slug to delete")
6037
+ prompt_id: z16.string().describe("Prompt ID or slug to delete")
5915
6038
  },
5916
6039
  publishPrompt: {
5917
- prompt_id: z15.string().describe("Prompt ID or slug to publish"),
5918
- version: z15.coerce.number().positive().describe("Version number to publish as the default")
6040
+ prompt_id: z16.string().describe("Prompt ID or slug to publish"),
6041
+ version: z16.coerce.number().positive().describe("Version number to publish as the default")
5919
6042
  },
5920
6043
  listPromptVersions: {
5921
- prompt_id: z15.string().describe("Prompt ID or slug to list versions for")
6044
+ prompt_id: z16.string().describe("Prompt ID or slug to list versions for")
5922
6045
  },
5923
6046
  renderPrompt: {
5924
- prompt_id: z15.string().describe("Prompt ID or slug to render"),
6047
+ prompt_id: z16.string().describe("Prompt ID or slug to render"),
5925
6048
  variables: PROMPT_VARIABLES_SCHEMA,
5926
6049
  hyperparameters: HyperparametersSchema.optional().describe(
5927
6050
  "Override default hyperparameters"
5928
6051
  )
5929
6052
  },
5930
6053
  runPromptCompletion: {
5931
- prompt_id: z15.string().describe("Prompt ID or slug to execute"),
6054
+ prompt_id: z16.string().describe("Prompt ID or slug to execute"),
5932
6055
  variables: PROMPT_VARIABLES_SCHEMA,
5933
6056
  metadata: BillingMetadataSchema.describe(
5934
6057
  "Billing metadata - client_id, app, env are REQUIRED for cost attribution"
@@ -5938,54 +6061,54 @@ var PROMPTS_TOOL_SCHEMAS = {
5938
6061
  )
5939
6062
  },
5940
6063
  migratePrompt: {
5941
- name: z15.string().describe("Prompt name to create or find for update"),
6064
+ name: z16.string().describe("Prompt name to create or find for update"),
5942
6065
  app: PromptAppIdentifierSchema,
5943
6066
  env: PromptEnvironmentIdentifierSchema,
5944
- collection_id: z15.string().describe("Collection ID to search in and create under"),
5945
- string: z15.string().optional().describe(
6067
+ collection_id: z16.string().describe("Collection ID to search in and create under"),
6068
+ string: z16.string().optional().describe(
5946
6069
  "Legacy prompt template string with {{variable}} mustache syntax."
5947
6070
  ),
5948
- messages: z15.array(PROMPT_TEMPLATE_MESSAGE_SCHEMA).optional().describe(
6071
+ messages: z16.array(PROMPT_TEMPLATE_MESSAGE_SCHEMA).optional().describe(
5949
6072
  "Structured chat template alias for migrations. Serialized to the legacy string format before the prompt is created or updated."
5950
6073
  ),
5951
- parameters: z15.record(z15.string(), z15.unknown()).describe("Default values for template variables"),
5952
- virtual_key: z15.string().describe("Virtual key slug for model access"),
5953
- model: z15.string().optional().describe("Model identifier"),
5954
- version_description: z15.string().optional().describe("Description for this version"),
5955
- template_metadata: z15.record(z15.string(), z15.unknown()).optional().describe("Additional custom metadata"),
5956
- functions: z15.array(PromptFunctionSchema).optional().describe("Function definitions"),
5957
- tools: z15.array(PromptToolSchema).optional().describe("Tool definitions"),
6074
+ parameters: z16.record(z16.string(), z16.unknown()).describe("Default values for template variables"),
6075
+ virtual_key: z16.string().describe("Virtual key slug for model access"),
6076
+ model: z16.string().optional().describe("Model identifier"),
6077
+ version_description: z16.string().optional().describe("Description for this version"),
6078
+ template_metadata: z16.record(z16.string(), z16.unknown()).optional().describe("Additional custom metadata"),
6079
+ functions: z16.array(PromptFunctionSchema).optional().describe("Function definitions"),
6080
+ tools: z16.array(PromptToolSchema).optional().describe("Tool definitions"),
5958
6081
  tool_choice: ToolChoiceSchema.optional().describe("Tool choice strategy"),
5959
- dry_run: z15.boolean().optional().describe(
6082
+ dry_run: z16.boolean().optional().describe(
5960
6083
  "When true, only check what action would be taken without making changes"
5961
6084
  )
5962
6085
  },
5963
6086
  promotePrompt: {
5964
- source_prompt_id: z15.string().describe("Source prompt ID or slug (e.g., staging prompt)"),
5965
- target_collection_id: z15.string().describe("Target collection ID for the promoted prompt"),
5966
- target_name: z15.string().optional().describe(
6087
+ source_prompt_id: z16.string().describe("Source prompt ID or slug (e.g., staging prompt)"),
6088
+ target_collection_id: z16.string().describe("Target collection ID for the promoted prompt"),
6089
+ target_name: z16.string().optional().describe(
5967
6090
  "Target prompt name (defaults to source name with env suffix replaced)"
5968
6091
  ),
5969
6092
  target_env: PromptEnvironmentIdentifierSchema,
5970
- virtual_key: z15.string().optional().describe(
6093
+ virtual_key: z16.string().optional().describe(
5971
6094
  "Virtual key ID to use (defaults to source prompt's virtual_key)"
5972
6095
  )
5973
6096
  },
5974
6097
  validateCompletionMetadata: {
5975
- client_id: z15.string().optional().describe("Client ID for billing attribution"),
6098
+ client_id: z16.string().optional().describe("Client ID for billing attribution"),
5976
6099
  app: PromptAppIdentifierSchema.optional(),
5977
6100
  env: PromptEnvironmentIdentifierSchema.optional(),
5978
- project_id: z15.string().optional().describe("Project ID for granular billing"),
5979
- feature: z15.string().optional().describe("Feature name for tracking")
6101
+ project_id: z16.string().optional().describe("Project ID for granular billing"),
6102
+ feature: z16.string().optional().describe("Feature name for tracking")
5980
6103
  },
5981
6104
  getPromptVersion: {
5982
- prompt_id: z15.string().describe("Prompt ID or slug"),
5983
- version_id: z15.string().describe("Version UUID to retrieve")
6105
+ prompt_id: z16.string().describe("Prompt ID or slug"),
6106
+ version_id: z16.string().describe("Version UUID to retrieve")
5984
6107
  },
5985
6108
  updatePromptVersion: {
5986
- prompt_id: z15.string().describe("Prompt ID or slug"),
5987
- version_id: z15.string().describe("Version UUID to update"),
5988
- label_id: z15.string().nullable().describe(
6109
+ prompt_id: z16.string().describe("Prompt ID or slug"),
6110
+ version_id: z16.string().describe("Version UUID to update"),
6111
+ label_id: z16.string().nullable().describe(
5989
6112
  "Label ID to assign to this version, or null to remove the label"
5990
6113
  )
5991
6114
  }
@@ -6575,68 +6698,68 @@ function registerPromptsTools(server, service) {
6575
6698
  }
6576
6699
 
6577
6700
  // src/tools/providers.tools.ts
6578
- import { z as z16 } from "zod";
6701
+ import { z as z17 } from "zod";
6579
6702
  var PROVIDERS_TOOL_SCHEMAS = {
6580
6703
  listProviders: {
6581
- current_page: z16.coerce.number().positive().optional().describe("Page number for pagination"),
6582
- page_size: z16.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100, default 50)"),
6583
- workspace_id: z16.string().optional().describe(
6704
+ current_page: z17.coerce.number().int().positive().optional().describe("Page number for pagination"),
6705
+ page_size: z17.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100, default 50)"),
6706
+ workspace_id: z17.string().optional().describe(
6584
6707
  "Workspace ID - required when using organization admin keys, optional with workspace API keys"
6585
6708
  )
6586
6709
  },
6587
6710
  createProvider: {
6588
- name: z16.string().describe("Display name for the provider"),
6589
- integration_id: z16.string().describe(
6711
+ name: z17.string().describe("Display name for the provider"),
6712
+ integration_id: z17.string().describe(
6590
6713
  "Integration slug for the provider (e.g., 'openai', 'anthropic', 'azure-openai')"
6591
6714
  ),
6592
- workspace_id: z16.string().optional().describe(
6715
+ workspace_id: z17.string().optional().describe(
6593
6716
  "Workspace ID - required when using organization admin API keys"
6594
6717
  ),
6595
- slug: z16.string().optional().describe(
6718
+ slug: z17.string().optional().describe(
6596
6719
  "Custom slug for the provider. Auto-generated with random suffix if omitted"
6597
6720
  ),
6598
- note: z16.string().optional().describe("Optional note or description for the provider"),
6599
- credit_limit: z16.coerce.number().positive().optional().describe("Credit limit for usage"),
6600
- alert_threshold: z16.coerce.number().min(0).max(100).optional().describe("Alert threshold percentage (0-100)"),
6601
- usage_limit_type: z16.enum(["cost", "tokens"]).optional().describe(
6721
+ note: z17.string().optional().describe("Optional note or description for the provider"),
6722
+ credit_limit: z17.coerce.number().positive().optional().describe("Credit limit for usage"),
6723
+ alert_threshold: z17.coerce.number().min(0).max(100).optional().describe("Alert threshold percentage (0-100)"),
6724
+ usage_limit_type: z17.enum(["cost", "tokens"]).optional().describe(
6602
6725
  "Type of usage limit: 'cost' (monetary) or 'tokens' (token count). Defaults to 'cost'."
6603
6726
  ),
6604
- periodic_reset: z16.enum(["monthly", "weekly"]).optional().describe(
6727
+ periodic_reset: z17.enum(["monthly", "weekly"]).optional().describe(
6605
6728
  "Period for resetting usage limits: 'monthly' or 'weekly'. Defaults to 'monthly'."
6606
6729
  ),
6607
- rate_limit_value: z16.coerce.number().positive().optional().describe("Must be provided together with rate_limit_unit."),
6608
- rate_limit_unit: z16.enum(["rpm", "rph", "rpd"]).optional().describe(
6730
+ rate_limit_value: z17.coerce.number().positive().optional().describe("Must be provided together with rate_limit_unit."),
6731
+ rate_limit_unit: z17.enum(["rpm", "rph", "rpd"]).optional().describe(
6609
6732
  "Must be provided together with rate_limit_value. Values: 'rpm' (requests/min), 'rph' (requests/hour), or 'rpd' (requests/day)."
6610
6733
  ),
6611
- expires_at: z16.string().optional().describe("Expiration date in ISO 8601 format")
6734
+ expires_at: z17.string().optional().describe("Expiration date in ISO 8601 format")
6612
6735
  },
6613
6736
  getProvider: {
6614
- slug: z16.string().describe("The unique slug identifier of the provider to retrieve"),
6615
- workspace_id: z16.string().optional().describe("Workspace ID - required when using organization admin keys")
6737
+ slug: z17.string().describe("The unique slug identifier of the provider to retrieve"),
6738
+ workspace_id: z17.string().optional().describe("Workspace ID - required when using organization admin keys")
6616
6739
  },
6617
6740
  updateProvider: {
6618
- slug: z16.string().describe("The slug of the provider to update"),
6619
- workspace_id: z16.string().optional().describe("Workspace ID - required when using organization admin keys"),
6620
- name: z16.string().optional().describe("New display name for the provider"),
6621
- note: z16.string().optional().describe("New note or description for the provider"),
6622
- credit_limit: z16.coerce.number().positive().optional().describe("New credit limit for usage"),
6623
- alert_threshold: z16.coerce.number().min(0).max(100).optional().describe("New alert threshold percentage (0-100)"),
6624
- usage_limit_type: z16.enum(["cost", "tokens"]).optional().describe(
6741
+ slug: z17.string().describe("The slug of the provider to update"),
6742
+ workspace_id: z17.string().optional().describe("Workspace ID - required when using organization admin keys"),
6743
+ name: z17.string().optional().describe("New display name for the provider"),
6744
+ note: z17.string().optional().describe("New note or description for the provider"),
6745
+ credit_limit: z17.coerce.number().positive().optional().describe("New credit limit for usage"),
6746
+ alert_threshold: z17.coerce.number().min(0).max(100).optional().describe("New alert threshold percentage (0-100)"),
6747
+ usage_limit_type: z17.enum(["cost", "tokens"]).optional().describe(
6625
6748
  "Type of usage limit: 'cost' (monetary) or 'tokens' (token count). Defaults to 'cost'."
6626
6749
  ),
6627
- periodic_reset: z16.enum(["monthly", "weekly"]).optional().describe(
6750
+ periodic_reset: z17.enum(["monthly", "weekly"]).optional().describe(
6628
6751
  "Period for resetting usage limits: 'monthly' or 'weekly'. Defaults to 'monthly'."
6629
6752
  ),
6630
- rate_limit_value: z16.coerce.number().positive().optional().describe("New rate limit value"),
6631
- rate_limit_unit: z16.enum(["rpm", "rph", "rpd"]).optional().describe(
6753
+ rate_limit_value: z17.coerce.number().positive().optional().describe("New rate limit value"),
6754
+ rate_limit_unit: z17.enum(["rpm", "rph", "rpd"]).optional().describe(
6632
6755
  "Rate limit unit: 'rpm' (requests per minute), 'rph' (requests per hour), or 'rpd' (requests per day)"
6633
6756
  ),
6634
- expires_at: z16.string().optional().describe("New expiration date in ISO 8601 format"),
6635
- reset_usage: z16.boolean().optional().describe("Set to true to reset accumulated usage metrics")
6757
+ expires_at: z17.string().optional().describe("New expiration date in ISO 8601 format"),
6758
+ reset_usage: z17.boolean().optional().describe("Set to true to reset accumulated usage metrics")
6636
6759
  },
6637
6760
  deleteProvider: {
6638
- slug: z16.string().describe("The slug of the provider to delete"),
6639
- workspace_id: z16.string().optional().describe("Workspace ID - required when using organization admin keys")
6761
+ slug: z17.string().describe("The slug of the provider to delete"),
6762
+ workspace_id: z17.string().optional().describe("Workspace ID - required when using organization admin keys")
6640
6763
  }
6641
6764
  };
6642
6765
  function registerProvidersTools(server, service) {
@@ -6817,52 +6940,275 @@ function registerProvidersTools(server, service) {
6817
6940
  );
6818
6941
  }
6819
6942
 
6820
- // src/tools/tracing.tools.ts
6821
- import { z as z17 } from "zod";
6822
- var TRACING_TOOL_SCHEMAS = {
6823
- createFeedback: {
6824
- trace_id: z17.string().describe(
6825
- "The trace ID to associate the feedback with. This links feedback to a specific request/generation."
6943
+ // src/tools/secret-references.tools.ts
6944
+ import { z as z18 } from "zod";
6945
+ var authFields = {
6946
+ aws_auth_type: z18.enum(["accessKey", "assumedRole", "serviceRole"]).describe(
6947
+ "AWS authentication mode: accessKey uses a key pair, assumedRole uses an IAM role, and serviceRole uses the runtime workload identity"
6948
+ ),
6949
+ aws_access_key_id: z18.string().min(1).describe("AWS access key ID; required when aws_auth_type is accessKey"),
6950
+ aws_secret_access_key: z18.string().min(1).describe(
6951
+ "AWS secret access key; required when aws_auth_type is accessKey and exposed to the MCP transcript"
6952
+ ),
6953
+ aws_region: z18.string().min(1).describe("AWS region containing the secret, for example us-east-1"),
6954
+ aws_role_arn: z18.string().min(1).describe(
6955
+ "IAM role ARN to assume; required when aws_auth_type is assumedRole"
6956
+ ),
6957
+ aws_external_id: z18.string().nullable().describe("Optional external ID required by the target IAM role"),
6958
+ azure_auth_mode: z18.enum(["entra", "managed", "default"]).describe(
6959
+ "Azure authentication mode: entra uses a client secret, managed uses managed identity, and default uses the runtime credential chain"
6960
+ ),
6961
+ azure_entra_tenant_id: z18.string().min(1).describe("Microsoft Entra tenant ID; required for entra authentication"),
6962
+ azure_entra_client_id: z18.string().min(1).describe(
6963
+ "Microsoft Entra application client ID; required for entra authentication"
6964
+ ),
6965
+ azure_entra_client_secret: z18.string().min(1).describe(
6966
+ "Microsoft Entra client secret; required for entra authentication and exposed to the MCP transcript"
6967
+ ),
6968
+ azure_managed_client_id: z18.string().min(1).describe(
6969
+ "Optional client ID of a user-assigned managed identity; omit for the system-assigned identity"
6970
+ ),
6971
+ azure_vault_url: z18.url().describe(
6972
+ "Azure Key Vault URL, for example https://example.vault.azure.net"
6973
+ ),
6974
+ vault_auth_type: z18.enum(["token", "approle", "kubernetes"]).describe(
6975
+ "HashiCorp Vault authentication mode: token, AppRole, or Kubernetes"
6976
+ ),
6977
+ vault_addr: z18.url().describe("Base URL of the HashiCorp Vault server"),
6978
+ vault_token: z18.string().min(1).describe(
6979
+ "HashiCorp Vault token; required for token authentication and exposed to the MCP transcript"
6980
+ ),
6981
+ vault_namespace: z18.string().min(1).describe("Optional HashiCorp Vault Enterprise namespace"),
6982
+ vault_role_id: z18.string().min(1).describe(
6983
+ "HashiCorp Vault AppRole role ID; required for approle authentication"
6984
+ ),
6985
+ vault_secret_id: z18.string().min(1).describe(
6986
+ "HashiCorp Vault AppRole secret ID; required for approle authentication and exposed to the MCP transcript"
6987
+ ),
6988
+ vault_role: z18.string().min(1).describe("HashiCorp Vault Kubernetes auth role name")
6989
+ };
6990
+ var authConfigSchema = z18.union([
6991
+ z18.object({
6992
+ aws_auth_type: z18.literal("accessKey").describe("Use an AWS access key ID and secret access key"),
6993
+ aws_access_key_id: authFields.aws_access_key_id,
6994
+ aws_secret_access_key: authFields.aws_secret_access_key,
6995
+ aws_region: authFields.aws_region
6996
+ }).strict(),
6997
+ z18.object({
6998
+ aws_auth_type: z18.literal("assumedRole").describe("Assume the specified AWS IAM role"),
6999
+ aws_role_arn: authFields.aws_role_arn,
7000
+ aws_external_id: authFields.aws_external_id.optional(),
7001
+ aws_region: authFields.aws_region
7002
+ }).strict(),
7003
+ z18.object({
7004
+ aws_auth_type: z18.literal("serviceRole").describe("Use the AWS workload or service role available at runtime"),
7005
+ aws_region: z18.string().optional().describe(
7006
+ "Optional AWS region containing the secret, for example us-east-1"
7007
+ )
7008
+ }).strict(),
7009
+ z18.object({
7010
+ azure_auth_mode: z18.literal("entra").describe(
7011
+ "Authenticate with a Microsoft Entra application client secret"
6826
7012
  ),
6827
- value: z17.coerce.number().describe(
6828
- "Feedback value/rating. Common patterns: 1 for positive (thumbs up), 0 for negative (thumbs down), or use a scale like 1-5."
7013
+ azure_entra_tenant_id: authFields.azure_entra_tenant_id,
7014
+ azure_entra_client_id: authFields.azure_entra_client_id,
7015
+ azure_entra_client_secret: authFields.azure_entra_client_secret,
7016
+ azure_vault_url: authFields.azure_vault_url
7017
+ }).strict(),
7018
+ z18.object({
7019
+ azure_auth_mode: z18.literal("managed").describe("Authenticate with an Azure managed identity"),
7020
+ azure_managed_client_id: authFields.azure_managed_client_id.optional(),
7021
+ azure_vault_url: authFields.azure_vault_url
7022
+ }).strict(),
7023
+ z18.object({
7024
+ azure_auth_mode: z18.literal("default").describe("Use Azure's default runtime credential chain"),
7025
+ azure_vault_url: authFields.azure_vault_url
7026
+ }).strict(),
7027
+ z18.object({
7028
+ vault_auth_type: z18.literal("token").describe("Authenticate to HashiCorp Vault with a token"),
7029
+ vault_addr: authFields.vault_addr,
7030
+ vault_token: authFields.vault_token,
7031
+ vault_namespace: authFields.vault_namespace.optional()
7032
+ }).strict(),
7033
+ z18.object({
7034
+ vault_auth_type: z18.literal("approle").describe("Authenticate to HashiCorp Vault with AppRole credentials"),
7035
+ vault_addr: authFields.vault_addr,
7036
+ vault_role_id: authFields.vault_role_id,
7037
+ vault_secret_id: authFields.vault_secret_id,
7038
+ vault_namespace: authFields.vault_namespace.optional()
7039
+ }).strict(),
7040
+ z18.object({
7041
+ vault_auth_type: z18.literal("kubernetes").describe(
7042
+ "Authenticate to HashiCorp Vault with a Kubernetes service account"
6829
7043
  ),
6830
- weight: z17.coerce.number().positive().optional().describe(
6831
- "Optional weighting factor for the feedback. Use to give more importance to certain feedback."
7044
+ vault_addr: authFields.vault_addr,
7045
+ vault_role: authFields.vault_role,
7046
+ vault_namespace: authFields.vault_namespace.optional()
7047
+ }).strict()
7048
+ ]);
7049
+ var updateAuthConfigSchema = z18.union([
7050
+ z18.object({
7051
+ aws_auth_type: authFields.aws_auth_type.optional(),
7052
+ aws_access_key_id: authFields.aws_access_key_id.optional(),
7053
+ aws_secret_access_key: authFields.aws_secret_access_key.optional(),
7054
+ aws_region: z18.string().optional().describe("AWS region containing the secret, for example us-east-1"),
7055
+ aws_role_arn: authFields.aws_role_arn.optional(),
7056
+ aws_external_id: authFields.aws_external_id.optional()
7057
+ }).strict(),
7058
+ z18.object({
7059
+ azure_auth_mode: authFields.azure_auth_mode.optional(),
7060
+ azure_entra_tenant_id: authFields.azure_entra_tenant_id.optional(),
7061
+ azure_entra_client_id: authFields.azure_entra_client_id.optional(),
7062
+ azure_entra_client_secret: authFields.azure_entra_client_secret.optional(),
7063
+ azure_managed_client_id: z18.string().optional().describe(
7064
+ "Optional client ID of a user-assigned managed identity; omit for the system-assigned identity"
6832
7065
  ),
6833
- metadata: z17.record(z17.string(), z17.unknown()).optional().describe(
6834
- "Optional custom metadata for categorization and analysis (e.g., feedback_source, category, user_segment)."
7066
+ azure_vault_url: authFields.azure_vault_url.optional()
7067
+ }).strict(),
7068
+ z18.object({
7069
+ vault_auth_type: authFields.vault_auth_type.optional(),
7070
+ vault_addr: authFields.vault_addr.optional(),
7071
+ vault_token: authFields.vault_token.optional(),
7072
+ vault_namespace: z18.string().optional().describe("Optional HashiCorp Vault Enterprise namespace"),
7073
+ vault_role_id: authFields.vault_role_id.optional(),
7074
+ vault_secret_id: authFields.vault_secret_id.optional(),
7075
+ vault_role: authFields.vault_role.optional()
7076
+ }).strict()
7077
+ ]);
7078
+ var managerTypeSchema = z18.enum(["aws_sm", "azure_kv", "hashicorp_vault"]).describe(
7079
+ "External secret manager: aws_sm for AWS Secrets Manager, azure_kv for Azure Key Vault, or hashicorp_vault for HashiCorp Vault; must match the auth_config field family"
7080
+ );
7081
+ var tagsSchema = z18.record(z18.string(), z18.string()).describe("Tag filters or metadata as string key-value pairs");
7082
+ var SECRET_REFERENCE_TOOL_SCHEMAS = {
7083
+ create: {
7084
+ name: z18.string().min(1).max(255).describe("Human-readable name"),
7085
+ manager_type: managerTypeSchema,
7086
+ auth_config: authConfigSchema.describe(
7087
+ "Choose exactly one documented authentication shape matching manager_type. Credentials are sent to Portkey and visible in the MCP transcript; prefer workload identity or short-lived credentials when available."
7088
+ ),
7089
+ secret_path: z18.string().max(1024).describe("Path of the secret in the external secret manager"),
7090
+ organisation_id: z18.string().uuid().optional().describe(
7091
+ "Organisation UUID; required when the MCP server is not authenticated with a Portkey API key"
7092
+ ),
7093
+ slug: z18.string().max(255).regex(/^[a-zA-Z0-9_-]+$/).optional().describe(
7094
+ "Optional stable slug containing only letters, numbers, underscores, or hyphens; Portkey generates one when omitted"
7095
+ ),
7096
+ description: z18.string().max(1024).nullable().optional().describe("Optional description; use null to leave it empty"),
7097
+ secret_key: z18.string().max(255).nullable().optional().describe(
7098
+ "Optional key within a structured external secret; null uses the whole secret value"
7099
+ ),
7100
+ allow_all_workspaces: z18.boolean().optional().describe(
7101
+ "Whether every workspace may use this reference; defaults to true and cannot be true with allowed_workspaces"
7102
+ ),
7103
+ allowed_workspaces: z18.array(z18.string()).min(1).optional().describe(
7104
+ "Workspace UUIDs or slugs allowed to use this reference; omit when allow_all_workspaces is true"
7105
+ ),
7106
+ tags: tagsSchema.nullable().optional().describe(
7107
+ "Optional string key-value tags for filtering and organization"
6835
7108
  )
6836
7109
  },
6837
- updateFeedback: {
6838
- id: z17.string().describe("The unique identifier of the feedback to update"),
6839
- value: z17.coerce.number().optional().describe(
6840
- "New feedback value/rating. Common patterns: 1 for positive, 0 for negative."
7110
+ list: {
7111
+ manager_type: managerTypeSchema.optional().describe(
7112
+ "Filter by external secret manager: aws_sm, azure_kv, or hashicorp_vault"
6841
7113
  ),
6842
- weight: z17.coerce.number().positive().optional().describe("New weighting factor for the feedback"),
6843
- metadata: z17.record(z17.string(), z17.unknown()).optional().describe("New or updated custom metadata for the feedback")
7114
+ tags: tagsSchema.optional().describe("Return references matching these tags"),
7115
+ search: z18.string().min(1).max(255).optional().describe("Search references by name"),
7116
+ current_page: z18.coerce.number().int().nonnegative().optional().describe("Zero-based page number; defaults to 0"),
7117
+ page_size: z18.coerce.number().int().min(1).max(100).optional().describe("References per page, from 1 to 100; defaults to 20")
7118
+ },
7119
+ get: {
7120
+ id: z18.string().min(1).describe("Secret Reference UUID or slug")
7121
+ },
7122
+ update: {
7123
+ id: z18.string().min(1).describe("Secret Reference UUID or slug"),
7124
+ name: z18.string().min(1).max(255).optional().describe("New human-readable name"),
7125
+ description: z18.string().max(1024).nullable().optional().describe("New description, or null to clear it"),
7126
+ auth_config: updateAuthConfigSchema.optional().describe(
7127
+ "Provide at least one changed authentication field to merge into the existing configuration; choose one AWS, Azure, or HashiCorp field family and do not mix families"
7128
+ ),
7129
+ secret_path: z18.string().max(1024).optional().describe("New path of the secret in the external secret manager"),
7130
+ secret_key: z18.string().max(255).nullable().optional().describe(
7131
+ "New key within a structured secret, or null to use the whole value"
7132
+ ),
7133
+ allow_all_workspaces: z18.boolean().optional().describe(
7134
+ "Whether every workspace may use this reference; cannot be true with allowed_workspaces"
7135
+ ),
7136
+ allowed_workspaces: z18.array(z18.string()).min(1).optional().describe(
7137
+ "Replacement list of workspace UUIDs or slugs allowed to use this reference; supplying it sets allow_all_workspaces=false"
7138
+ ),
7139
+ tags: tagsSchema.nullable().optional().describe("Replacement string key-value tags, or null to clear them")
7140
+ },
7141
+ delete: {
7142
+ id: z18.string().min(1).describe("Secret Reference UUID or slug")
6844
7143
  }
6845
7144
  };
6846
- function registerTracingTools(server, service) {
7145
+ function validateWorkspaceAccess(value) {
7146
+ if (value.allow_all_workspaces === true && value.allowed_workspaces) {
7147
+ throw new Error(
7148
+ "allow_all_workspaces=true is mutually exclusive with allowed_workspaces"
7149
+ );
7150
+ }
7151
+ }
7152
+ function validateManagerAuth(managerType, authConfig) {
7153
+ const matches = managerType === "aws_sm" && "aws_auth_type" in authConfig || managerType === "azure_kv" && "azure_auth_mode" in authConfig || managerType === "hashicorp_vault" && "vault_auth_type" in authConfig;
7154
+ if (!matches) {
7155
+ throw new Error(`auth_config does not match manager_type ${managerType}`);
7156
+ }
7157
+ }
7158
+ function validateAuthUpdate(authConfig) {
7159
+ if (Object.keys(authConfig).length === 0) {
7160
+ throw new Error("auth_config update must contain at least one field");
7161
+ }
7162
+ const families = new Set(
7163
+ Object.keys(authConfig).map((key) => {
7164
+ if (key.startsWith("aws_")) return "AWS";
7165
+ if (key.startsWith("azure_")) return "Azure";
7166
+ if (key.startsWith("vault_")) return "HashiCorp Vault";
7167
+ return "unknown";
7168
+ })
7169
+ );
7170
+ if (families.size > 1) {
7171
+ throw new Error(
7172
+ "auth_config update must contain fields for exactly one secret-manager family"
7173
+ );
7174
+ }
7175
+ }
7176
+ var SENSITIVE_AUTH_FIELD = /(?:secret|token|password|credential|access_key_id)/i;
7177
+ function redactAuthConfig(authConfig) {
7178
+ return Object.fromEntries(
7179
+ Object.entries(authConfig).map(([key, value]) => {
7180
+ if (SENSITIVE_AUTH_FIELD.test(key)) return [key, "[REDACTED]"];
7181
+ if (value && typeof value === "object" && !Array.isArray(value)) {
7182
+ return [key, redactAuthConfig(value)];
7183
+ }
7184
+ return [key, value];
7185
+ })
7186
+ );
7187
+ }
7188
+ function registerSecretReferencesTools(server, service) {
6847
7189
  server.tool(
6848
- "create_feedback",
6849
- "Create feedback for a trace or request. Writes a new feedback record linked by trace_id, returns the created feedback IDs and status, and takes effect immediately; use update_feedback when correcting an existing record.",
6850
- TRACING_TOOL_SCHEMAS.createFeedback,
7190
+ "create_secret_reference",
7191
+ "Create a reference to a secret stored in AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault. Authentication credentials are sent to Portkey and exposed to this MCP transcript; use short-lived or workload identity modes when possible. Returns the new UUID and slug, never the resolved external secret.",
7192
+ SECRET_REFERENCE_TOOL_SCHEMAS.create,
7193
+ {
7194
+ title: "Create Secret Reference",
7195
+ readOnlyHint: false,
7196
+ destructiveHint: false,
7197
+ idempotentHint: false,
7198
+ openWorldHint: true
7199
+ },
6851
7200
  async (params) => {
6852
- const result = await service.tracing.createFeedback({
6853
- trace_id: params.trace_id,
6854
- value: params.value,
6855
- weight: params.weight,
6856
- metadata: params.metadata
6857
- });
7201
+ validateWorkspaceAccess(params);
7202
+ validateManagerAuth(params.manager_type, params.auth_config);
7203
+ const result = await service.secretReferences.createSecretReference(params);
6858
7204
  return {
6859
7205
  content: [
6860
7206
  {
6861
7207
  type: "text",
6862
7208
  text: JSON.stringify({
6863
- message: `Successfully created feedback for trace "${params.trace_id}"`,
6864
- status: result.status,
6865
- feedback_ids: result.feedback_ids
7209
+ message: `Successfully created Secret Reference "${result.slug}"`,
7210
+ id: result.id,
7211
+ slug: result.slug
6866
7212
  })
6867
7213
  }
6868
7214
  ]
@@ -6870,105 +7216,279 @@ function registerTracingTools(server, service) {
6870
7216
  }
6871
7217
  );
6872
7218
  server.tool(
6873
- "update_feedback",
6874
- "Update an existing feedback record by ID. Returns the updated status and feedback IDs, changes only value, weight, and metadata, and leaves the trace linkage immutable; use create_feedback only for a new record.",
6875
- TRACING_TOOL_SCHEMAS.updateFeedback,
7219
+ "list_secret_references",
7220
+ "List Secret References without returning authentication configuration or resolved secret values. Filter by manager type, tags, or name and use a returned UUID or slug with get_secret_reference.",
7221
+ SECRET_REFERENCE_TOOL_SCHEMAS.list,
7222
+ {
7223
+ title: "List Secret References",
7224
+ readOnlyHint: true,
7225
+ destructiveHint: false,
7226
+ idempotentHint: true,
7227
+ openWorldHint: true
7228
+ },
6876
7229
  async (params) => {
6877
- const result = await service.tracing.updateFeedback(params.id, {
6878
- value: params.value,
6879
- weight: params.weight,
6880
- metadata: params.metadata
7230
+ const result = await service.secretReferences.listSecretReferences({
7231
+ ...params,
7232
+ tags: params.tags ? JSON.stringify(params.tags) : void 0
6881
7233
  });
6882
7234
  return {
6883
7235
  content: [
6884
7236
  {
6885
7237
  type: "text",
6886
7238
  text: JSON.stringify({
6887
- message: `Successfully updated feedback "${params.id}"`,
6888
- status: result.status,
6889
- feedback_ids: result.feedback_ids
7239
+ total: result.total,
7240
+ secret_references: result.data.map((reference) => ({
7241
+ id: reference.id,
7242
+ name: reference.name,
7243
+ slug: reference.slug,
7244
+ manager_type: reference.manager_type,
7245
+ status: reference.status,
7246
+ created_at: reference.created_at,
7247
+ last_updated_at: reference.last_updated_at
7248
+ }))
6890
7249
  })
6891
7250
  }
6892
7251
  ]
6893
7252
  };
6894
7253
  }
6895
7254
  );
6896
- }
6897
-
6898
- // src/tools/users.tools.ts
6899
- import { z as z18 } from "zod";
6900
- var USERS_TOOL_SCHEMAS = {
6901
- listAllUsers: {
6902
- current_page: z18.coerce.number().positive().optional().describe("Page number for pagination"),
6903
- page_size: z18.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)")
6904
- },
6905
- inviteUser: {
6906
- email: z18.string().email().describe("Email address of the user to invite"),
6907
- role: z18.enum(["admin", "member"]).describe(
6908
- "Organization-level role: 'admin' for full access, 'member' for limited access"
6909
- ),
6910
- first_name: z18.string().optional().describe("User's first name"),
6911
- last_name: z18.string().optional().describe("User's last name"),
6912
- workspaces: z18.array(
6913
- z18.object({
6914
- id: z18.string().describe(
6915
- "Workspace ID/slug where the user will be granted access"
6916
- ),
6917
- role: z18.enum(["admin", "member", "manager"]).describe(
6918
- "Workspace-level role: 'admin' for full access, 'manager' for workspace management, 'member' for basic access"
6919
- )
6920
- })
6921
- ).describe(
6922
- "List of workspaces and corresponding roles to grant to the user"
6923
- ),
6924
- workspace_api_key_details: z18.object({
6925
- name: z18.string().optional().describe("Name of the API key to be created"),
6926
- expiry: z18.string().optional().describe("Expiration date for the API key (ISO8601 format)"),
6927
- metadata: z18.record(z18.string(), z18.string()).optional().describe("Additional metadata key-value pairs for the API key"),
6928
- scopes: z18.array(z18.string()).describe("List of permission scopes for the API key")
6929
- }).optional().describe("Optional API key to be created for the user")
6930
- },
6931
- getUserStats: {
6932
- time_of_generation_min: z18.string().describe(
6933
- "Start time for the analytics period (ISO8601 format, e.g., '2024-01-01T00:00:00Z')"
6934
- ),
6935
- time_of_generation_max: z18.string().describe(
6936
- "End time for the analytics period (ISO8601 format, e.g., '2024-02-01T00:00:00Z')"
6937
- ),
6938
- total_units_min: z18.coerce.number().positive().optional().describe("Minimum number of total tokens to filter by"),
6939
- total_units_max: z18.coerce.number().positive().optional().describe("Maximum number of total tokens to filter by"),
6940
- cost_min: z18.coerce.number().positive().optional().describe("Minimum cost in cents to filter by"),
6941
- cost_max: z18.coerce.number().positive().optional().describe("Maximum cost in cents to filter by"),
6942
- status_code: z18.string().optional().describe("Filter by specific HTTP status codes (comma-separated)"),
6943
- virtual_keys: z18.string().optional().describe("Filter by specific virtual key slugs (comma-separated)"),
6944
- page_size: z18.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)")
6945
- },
6946
- getUser: {
6947
- user_id: z18.string().describe("The user ID to retrieve")
6948
- },
6949
- updateUser: {
6950
- user_id: z18.string().describe("The user ID to update"),
6951
- first_name: z18.string().optional().describe("New first name"),
6952
- last_name: z18.string().optional().describe("New last name"),
6953
- role: z18.enum(["admin", "member"]).optional().describe("New organization-level role")
6954
- },
6955
- deleteUser: {
6956
- user_id: z18.string().describe("The user ID to delete")
6957
- },
6958
- listUserInvites: {
6959
- current_page: z18.coerce.number().positive().optional().describe("Page number for pagination"),
6960
- page_size: z18.coerce.number().positive().max(100).optional().describe("Number of results per page (max 100)")
6961
- },
6962
- getUserInvite: {
6963
- invite_id: z18.string().describe("The invite ID to retrieve")
6964
- },
6965
- deleteUserInvite: {
6966
- invite_id: z18.string().describe("The invite ID to delete")
6967
- },
6968
- resendUserInvite: {
6969
- invite_id: z18.string().describe("The invite ID to resend")
6970
- }
6971
- };
7255
+ server.tool(
7256
+ "get_secret_reference",
7257
+ "Retrieve one Secret Reference by UUID or slug. Portkey masks sensitive authentication fields for non-system users; the tool returns metadata and the masked auth configuration, not the resolved external secret value.",
7258
+ SECRET_REFERENCE_TOOL_SCHEMAS.get,
7259
+ {
7260
+ title: "Get Secret Reference",
7261
+ readOnlyHint: true,
7262
+ destructiveHint: false,
7263
+ idempotentHint: true,
7264
+ openWorldHint: true
7265
+ },
7266
+ async ({ id }) => {
7267
+ const result = await service.secretReferences.getSecretReference(id);
7268
+ return {
7269
+ content: [
7270
+ {
7271
+ type: "text",
7272
+ text: JSON.stringify({
7273
+ ...result,
7274
+ auth_config: redactAuthConfig(result.auth_config)
7275
+ })
7276
+ }
7277
+ ]
7278
+ };
7279
+ }
7280
+ );
7281
+ server.tool(
7282
+ "update_secret_reference",
7283
+ "Update at least one selected field on a Secret Reference. auth_config is merged into the existing manager-specific configuration. allowed_workspaces replaces all workspace mappings and sets allow_all_workspaces=false; allow_all_workspaces=true removes workspace-specific mappings. Credential values are sent to Portkey and exposed to the MCP transcript.",
7284
+ SECRET_REFERENCE_TOOL_SCHEMAS.update,
7285
+ {
7286
+ title: "Update Secret Reference",
7287
+ readOnlyHint: false,
7288
+ destructiveHint: true,
7289
+ idempotentHint: true,
7290
+ openWorldHint: true
7291
+ },
7292
+ async ({ id, ...updates }) => {
7293
+ validateWorkspaceAccess(updates);
7294
+ if (Object.keys(updates).length === 0) {
7295
+ throw new Error("Provide at least one field to update");
7296
+ }
7297
+ if (updates.auth_config) validateAuthUpdate(updates.auth_config);
7298
+ await service.secretReferences.updateSecretReference(id, updates);
7299
+ return {
7300
+ content: [
7301
+ {
7302
+ type: "text",
7303
+ text: JSON.stringify({
7304
+ message: `Successfully updated Secret Reference "${id}"`,
7305
+ success: true
7306
+ })
7307
+ }
7308
+ ]
7309
+ };
7310
+ }
7311
+ );
7312
+ server.tool(
7313
+ "delete_secret_reference",
7314
+ "Permanently delete a Secret Reference. This is irreversible and integrations or virtual keys using it can fail to resolve credentials immediately; confirm dependencies before deletion.",
7315
+ SECRET_REFERENCE_TOOL_SCHEMAS.delete,
7316
+ {
7317
+ title: "Delete Secret Reference",
7318
+ readOnlyHint: false,
7319
+ destructiveHint: true,
7320
+ idempotentHint: true,
7321
+ openWorldHint: true
7322
+ },
7323
+ async ({ id }) => {
7324
+ const result = await service.secretReferences.deleteSecretReference(id);
7325
+ return {
7326
+ content: [
7327
+ {
7328
+ type: "text",
7329
+ text: JSON.stringify({
7330
+ message: `Successfully deleted Secret Reference "${id}"`,
7331
+ success: result.success
7332
+ })
7333
+ }
7334
+ ]
7335
+ };
7336
+ }
7337
+ );
7338
+ }
7339
+
7340
+ // src/tools/tracing.tools.ts
7341
+ import { z as z19 } from "zod";
7342
+ var TRACING_TOOL_SCHEMAS = {
7343
+ createFeedback: {
7344
+ trace_id: z19.string().describe(
7345
+ "The trace ID to associate the feedback with. This links feedback to a specific request/generation."
7346
+ ),
7347
+ value: z19.coerce.number().describe(
7348
+ "Feedback value/rating. Common patterns: 1 for positive (thumbs up), 0 for negative (thumbs down), or use a scale like 1-5."
7349
+ ),
7350
+ weight: z19.coerce.number().positive().optional().describe(
7351
+ "Optional weighting factor for the feedback. Use to give more importance to certain feedback."
7352
+ ),
7353
+ metadata: z19.record(z19.string(), z19.unknown()).optional().describe(
7354
+ "Optional custom metadata for categorization and analysis (e.g., feedback_source, category, user_segment)."
7355
+ )
7356
+ },
7357
+ updateFeedback: {
7358
+ id: z19.string().describe("The unique identifier of the feedback to update"),
7359
+ value: z19.coerce.number().optional().describe(
7360
+ "New feedback value/rating. Common patterns: 1 for positive, 0 for negative."
7361
+ ),
7362
+ weight: z19.coerce.number().positive().optional().describe("New weighting factor for the feedback"),
7363
+ metadata: z19.record(z19.string(), z19.unknown()).optional().describe("New or updated custom metadata for the feedback")
7364
+ }
7365
+ };
7366
+ function registerTracingTools(server, service) {
7367
+ server.tool(
7368
+ "create_feedback",
7369
+ "Create feedback for a trace or request. Writes a new feedback record linked by trace_id, returns the created feedback IDs and status, and takes effect immediately; use update_feedback when correcting an existing record.",
7370
+ TRACING_TOOL_SCHEMAS.createFeedback,
7371
+ async (params) => {
7372
+ const result = await service.tracing.createFeedback({
7373
+ trace_id: params.trace_id,
7374
+ value: params.value,
7375
+ weight: params.weight,
7376
+ metadata: params.metadata
7377
+ });
7378
+ return {
7379
+ content: [
7380
+ {
7381
+ type: "text",
7382
+ text: JSON.stringify({
7383
+ message: `Successfully created feedback for trace "${params.trace_id}"`,
7384
+ status: result.status,
7385
+ feedback_ids: result.feedback_ids
7386
+ })
7387
+ }
7388
+ ]
7389
+ };
7390
+ }
7391
+ );
7392
+ server.tool(
7393
+ "update_feedback",
7394
+ "Update an existing feedback record by ID. Returns the updated status and feedback IDs, changes only value, weight, and metadata, and leaves the trace linkage immutable; use create_feedback only for a new record.",
7395
+ TRACING_TOOL_SCHEMAS.updateFeedback,
7396
+ async (params) => {
7397
+ const result = await service.tracing.updateFeedback(params.id, {
7398
+ value: params.value,
7399
+ weight: params.weight,
7400
+ metadata: params.metadata
7401
+ });
7402
+ return {
7403
+ content: [
7404
+ {
7405
+ type: "text",
7406
+ text: JSON.stringify({
7407
+ message: `Successfully updated feedback "${params.id}"`,
7408
+ status: result.status,
7409
+ feedback_ids: result.feedback_ids
7410
+ })
7411
+ }
7412
+ ]
7413
+ };
7414
+ }
7415
+ );
7416
+ }
7417
+
7418
+ // src/tools/users.tools.ts
7419
+ import { z as z20 } from "zod";
7420
+ var USERS_TOOL_SCHEMAS = {
7421
+ listAllUsers: {
7422
+ current_page: z20.coerce.number().int().positive().optional().describe("Page number for pagination"),
7423
+ page_size: z20.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)")
7424
+ },
7425
+ inviteUser: {
7426
+ email: z20.string().email().describe("Email address of the user to invite"),
7427
+ role: z20.enum(["admin", "member"]).describe(
7428
+ "Organization-level role: 'admin' for full access, 'member' for limited access"
7429
+ ),
7430
+ first_name: z20.string().optional().describe("User's first name"),
7431
+ last_name: z20.string().optional().describe("User's last name"),
7432
+ workspaces: z20.array(
7433
+ z20.object({
7434
+ id: z20.string().describe(
7435
+ "Workspace ID/slug where the user will be granted access"
7436
+ ),
7437
+ role: z20.enum(["admin", "member", "manager"]).describe(
7438
+ "Workspace-level role: 'admin' for full access, 'manager' for workspace management, 'member' for basic access"
7439
+ )
7440
+ })
7441
+ ).describe(
7442
+ "List of workspaces and corresponding roles to grant to the user"
7443
+ ),
7444
+ workspace_api_key_details: z20.object({
7445
+ name: z20.string().optional().describe("Name of the API key to be created"),
7446
+ expiry: z20.string().optional().describe("Expiration date for the API key (ISO8601 format)"),
7447
+ metadata: z20.record(z20.string(), z20.string()).optional().describe("Additional metadata key-value pairs for the API key"),
7448
+ scopes: z20.array(z20.string()).describe("List of permission scopes for the API key")
7449
+ }).optional().describe("Optional API key to be created for the user")
7450
+ },
7451
+ getUserStats: {
7452
+ time_of_generation_min: z20.string().describe(
7453
+ "Start time for the analytics period (ISO8601 format, e.g., '2024-01-01T00:00:00Z')"
7454
+ ),
7455
+ time_of_generation_max: z20.string().describe(
7456
+ "End time for the analytics period (ISO8601 format, e.g., '2024-02-01T00:00:00Z')"
7457
+ ),
7458
+ total_units_min: z20.coerce.number().positive().optional().describe("Minimum number of total tokens to filter by"),
7459
+ total_units_max: z20.coerce.number().positive().optional().describe("Maximum number of total tokens to filter by"),
7460
+ cost_min: z20.coerce.number().positive().optional().describe("Minimum cost in cents to filter by"),
7461
+ cost_max: z20.coerce.number().positive().optional().describe("Maximum cost in cents to filter by"),
7462
+ status_code: z20.string().optional().describe("Filter by specific HTTP status codes (comma-separated)"),
7463
+ virtual_keys: z20.string().optional().describe("Filter by specific virtual key slugs (comma-separated)"),
7464
+ page_size: z20.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)")
7465
+ },
7466
+ getUser: {
7467
+ user_id: z20.string().describe("The user ID to retrieve")
7468
+ },
7469
+ updateUser: {
7470
+ user_id: z20.string().describe("The user ID to update"),
7471
+ first_name: z20.string().optional().describe("New first name"),
7472
+ last_name: z20.string().optional().describe("New last name"),
7473
+ role: z20.enum(["admin", "member"]).optional().describe("New organization-level role")
7474
+ },
7475
+ deleteUser: {
7476
+ user_id: z20.string().describe("The user ID to delete")
7477
+ },
7478
+ listUserInvites: {
7479
+ current_page: z20.coerce.number().int().positive().optional().describe("Page number for pagination"),
7480
+ page_size: z20.coerce.number().int().positive().max(100).optional().describe("Number of results per page (max 100)")
7481
+ },
7482
+ getUserInvite: {
7483
+ invite_id: z20.string().describe("The invite ID to retrieve")
7484
+ },
7485
+ deleteUserInvite: {
7486
+ invite_id: z20.string().describe("The invite ID to delete")
7487
+ },
7488
+ resendUserInvite: {
7489
+ invite_id: z20.string().describe("The invite ID to resend")
7490
+ }
7491
+ };
6972
7492
  function formatUser(user) {
6973
7493
  return {
6974
7494
  id: user.id,
@@ -7192,59 +7712,59 @@ function registerUsersTools(server, service) {
7192
7712
  }
7193
7713
 
7194
7714
  // src/tools/workspaces.tools.ts
7195
- import { z as z19 } from "zod";
7715
+ import { z as z21 } from "zod";
7196
7716
  var WORKSPACES_TOOL_SCHEMAS = {
7197
7717
  listWorkspaces: {
7198
- page_size: z19.coerce.number().positive().max(100).optional().describe("Number of workspaces to return per page (max 100)"),
7199
- current_page: z19.coerce.number().positive().optional().describe("Page number to retrieve when results are paginated")
7718
+ page_size: z21.coerce.number().int().positive().max(100).optional().describe("Number of workspaces to return per page (max 100)"),
7719
+ current_page: z21.coerce.number().int().positive().optional().describe("Page number to retrieve when results are paginated")
7200
7720
  },
7201
7721
  getWorkspace: {
7202
- workspace_id: z19.string().describe(
7722
+ workspace_id: z21.string().describe(
7203
7723
  "The unique identifier of the workspace to retrieve. This can be found in the workspace's URL or from the list_workspaces tool response"
7204
7724
  )
7205
7725
  },
7206
7726
  createWorkspace: {
7207
- name: z19.string().describe("Name of the workspace"),
7208
- slug: z19.string().optional().describe("URL-friendly slug (auto-generated if not provided)"),
7209
- description: z19.string().optional().describe("Description of the workspace"),
7210
- is_default: z19.coerce.number().optional().describe("Set as default workspace (1 = yes, 0 = no)"),
7211
- metadata: z19.record(z19.string(), z19.string()).optional().describe("Custom metadata key-value pairs")
7727
+ name: z21.string().describe("Name of the workspace"),
7728
+ slug: z21.string().optional().describe("URL-friendly slug (auto-generated if not provided)"),
7729
+ description: z21.string().optional().describe("Description of the workspace"),
7730
+ is_default: z21.coerce.number().optional().describe("Set as default workspace (1 = yes, 0 = no)"),
7731
+ metadata: z21.record(z21.string(), z21.string()).optional().describe("Custom metadata key-value pairs")
7212
7732
  },
7213
7733
  updateWorkspace: {
7214
- workspace_id: z19.string().describe("The workspace ID to update"),
7215
- name: z19.string().optional().describe("New name for the workspace"),
7216
- slug: z19.string().optional().describe("New slug for the workspace"),
7217
- description: z19.string().optional().describe("New description"),
7218
- is_default: z19.coerce.number().optional().describe("Set as default workspace (1 = yes, 0 = no)"),
7219
- metadata: z19.record(z19.string(), z19.string()).optional().describe("New metadata key-value pairs")
7734
+ workspace_id: z21.string().describe("The workspace ID to update"),
7735
+ name: z21.string().optional().describe("New name for the workspace"),
7736
+ slug: z21.string().optional().describe("New slug for the workspace"),
7737
+ description: z21.string().optional().describe("New description"),
7738
+ is_default: z21.coerce.number().optional().describe("Set as default workspace (1 = yes, 0 = no)"),
7739
+ metadata: z21.record(z21.string(), z21.string()).optional().describe("New metadata key-value pairs")
7220
7740
  },
7221
7741
  deleteWorkspace: {
7222
- workspace_id: z19.string().describe("The workspace ID to delete")
7742
+ workspace_id: z21.string().describe("The workspace ID to delete")
7223
7743
  },
7224
7744
  addWorkspaceMember: {
7225
- workspace_id: z19.string().describe("The workspace ID to add the member to"),
7226
- user_id: z19.string().uuid(
7745
+ workspace_id: z21.string().describe("The workspace ID to add the member to"),
7746
+ user_id: z21.string().uuid(
7227
7747
  "user_id must be a valid UUID (use list_all_users to find user IDs)"
7228
7748
  ).describe(
7229
7749
  "The user ID to add (must be a valid UUID from list_all_users, not an email address)"
7230
7750
  ),
7231
- role: z19.enum(["admin", "member", "manager"]).describe("Role in the workspace")
7751
+ role: z21.enum(["admin", "member", "manager"]).describe("Role in the workspace")
7232
7752
  },
7233
7753
  listWorkspaceMembers: {
7234
- workspace_id: z19.string().describe("The workspace ID to list members for")
7754
+ workspace_id: z21.string().describe("The workspace ID to list members for")
7235
7755
  },
7236
7756
  getWorkspaceMember: {
7237
- workspace_id: z19.string().describe("The workspace ID"),
7238
- user_id: z19.string().describe("The user ID to retrieve")
7757
+ workspace_id: z21.string().describe("The workspace ID"),
7758
+ user_id: z21.string().describe("The user ID to retrieve")
7239
7759
  },
7240
7760
  updateWorkspaceMember: {
7241
- workspace_id: z19.string().describe("The workspace ID"),
7242
- user_id: z19.string().describe("The user ID to update"),
7243
- role: z19.enum(["admin", "member", "manager"]).describe("New role in the workspace")
7761
+ workspace_id: z21.string().describe("The workspace ID"),
7762
+ user_id: z21.string().describe("The user ID to update"),
7763
+ role: z21.enum(["admin", "member", "manager"]).describe("New role in the workspace")
7244
7764
  },
7245
7765
  removeWorkspaceMember: {
7246
- workspace_id: z19.string().describe("The workspace ID"),
7247
- user_id: z19.string().describe("The user ID to remove")
7766
+ workspace_id: z21.string().describe("The workspace ID"),
7767
+ user_id: z21.string().describe("The user ID to remove")
7248
7768
  }
7249
7769
  };
7250
7770
  function formatWorkspaceDefaults(defaults) {
@@ -7531,6 +8051,7 @@ var TOOL_DOMAIN_REGISTRARS = [
7531
8051
  ["tracing", registerTracingTools],
7532
8052
  ["logging", registerLoggingTools],
7533
8053
  ["providers", registerProvidersTools],
8054
+ ["secret-references", registerSecretReferencesTools],
7534
8055
  ["integrations", registerIntegrationsTools],
7535
8056
  ["mcp-integrations", registerMcpIntegrationsTools],
7536
8057
  ["mcp-servers", registerMcpServersTools]
@@ -7653,13 +8174,81 @@ function getToolErrorMessage(error) {
7653
8174
  return String(error);
7654
8175
  }
7655
8176
  var STANDARD_TOOL_OUTPUT_SCHEMA = {
7656
- ok: z20.boolean().describe("Whether the tool call succeeded and returned structured data"),
7657
- data: z20.unknown().optional().describe("Structured success payload when ok is true"),
7658
- error: z20.object({
7659
- message: z20.string().describe("Human-readable error message"),
7660
- details: z20.unknown().optional().describe("Optional structured error details")
8177
+ ok: z22.boolean().describe("Whether the tool call succeeded and returned structured data"),
8178
+ data: z22.unknown().optional().describe("Structured success payload when ok is true"),
8179
+ error: z22.object({
8180
+ message: z22.string().describe("Human-readable error message"),
8181
+ details: z22.unknown().optional().describe("Optional structured error details")
7661
8182
  }).optional().describe("Structured error payload when ok is false")
7662
8183
  };
8184
+ var TOOL_SUCCESS_DATA_SCHEMAS = {
8185
+ rotate_api_key: z22.object({
8186
+ message: z22.string().describe("Rotation confirmation"),
8187
+ warning: z22.string().describe("Required handling guidance for the one-time secret"),
8188
+ id: z22.string().uuid().describe("Rotated API key UUID"),
8189
+ key: z22.string().describe("New API key secret, returned only once"),
8190
+ key_transition_expires_at: z22.iso.datetime().describe("Time when the previous key stops working")
8191
+ }),
8192
+ create_secret_reference: z22.object({
8193
+ message: z22.string().describe("Creation confirmation"),
8194
+ id: z22.string().uuid().describe("New Secret Reference UUID"),
8195
+ slug: z22.string().describe("New Secret Reference slug")
8196
+ }),
8197
+ list_secret_references: z22.object({
8198
+ total: z22.number().int().nonnegative().describe("Total matching references"),
8199
+ secret_references: z22.array(
8200
+ z22.object({
8201
+ id: z22.string().uuid().describe("Secret Reference UUID"),
8202
+ name: z22.string().describe("Human-readable name"),
8203
+ slug: z22.string().describe("Stable Secret Reference slug"),
8204
+ manager_type: SecretManagerTypeSchema.describe(
8205
+ "External secret manager type"
8206
+ ),
8207
+ status: z22.literal("ACTIVE").describe("Current reference status"),
8208
+ created_at: z22.iso.datetime().describe("ISO 8601 creation timestamp"),
8209
+ last_updated_at: z22.iso.datetime().describe("ISO 8601 last-update timestamp")
8210
+ })
8211
+ ).describe(
8212
+ "Current page of Secret References without authentication values"
8213
+ )
8214
+ }),
8215
+ get_secret_reference: z22.object({
8216
+ id: z22.string().uuid().describe("Secret Reference UUID"),
8217
+ organisation_id: z22.string().uuid().describe("Owning organisation UUID"),
8218
+ name: z22.string().describe("Human-readable name"),
8219
+ slug: z22.string().describe("Stable Secret Reference slug"),
8220
+ description: z22.string().nullable().describe("Reference description"),
8221
+ manager_type: SecretManagerTypeSchema.describe(
8222
+ "External secret manager type"
8223
+ ),
8224
+ secret_path: z22.string().describe("Path in the external secret manager"),
8225
+ secret_key: z22.string().nullable().describe("Selected key within a structured secret"),
8226
+ allow_all_workspaces: z22.boolean().describe("Whether all workspaces may use the reference"),
8227
+ tags: z22.record(z22.string(), z22.string()).nullable().describe("String metadata tags"),
8228
+ status: z22.literal("ACTIVE").describe("Current reference status"),
8229
+ created_by: z22.string().describe("Creator identifier"),
8230
+ created_at: z22.iso.datetime().describe("ISO 8601 creation timestamp"),
8231
+ last_updated_at: z22.iso.datetime().describe("ISO 8601 last-update timestamp"),
8232
+ auth_config: z22.record(z22.string(), z22.unknown()).describe("Authentication configuration with sensitive values redacted"),
8233
+ object: z22.literal("secret-reference").describe("Portkey object type")
8234
+ }),
8235
+ update_secret_reference: z22.object({
8236
+ message: z22.string().describe("Update confirmation"),
8237
+ success: z22.literal(true).describe("Whether the update succeeded")
8238
+ }),
8239
+ delete_secret_reference: z22.object({
8240
+ message: z22.string().describe("Deletion confirmation"),
8241
+ success: z22.literal(true).describe("Whether the deletion succeeded")
8242
+ })
8243
+ };
8244
+ function getToolOutputSchema(toolName) {
8245
+ const dataSchema = TOOL_SUCCESS_DATA_SCHEMAS[toolName];
8246
+ if (!dataSchema) return STANDARD_TOOL_OUTPUT_SCHEMA;
8247
+ return {
8248
+ ...STANDARD_TOOL_OUTPUT_SCHEMA,
8249
+ data: dataSchema.optional().describe("Structured success payload when ok is true")
8250
+ };
8251
+ }
7663
8252
  function isStandardToolEnvelope(value) {
7664
8253
  if (!isRecord(value) || typeof value.ok !== "boolean") {
7665
8254
  return false;
@@ -7806,7 +8395,7 @@ function createSafeToolServer(server) {
7806
8395
  {
7807
8396
  ...registration.description ? { description: registration.description } : {},
7808
8397
  ...registration.inputSchema !== void 0 ? { inputSchema: registration.inputSchema } : {},
7809
- outputSchema: STANDARD_TOOL_OUTPUT_SCHEMA,
8398
+ outputSchema: getToolOutputSchema(name),
7810
8399
  annotations: registration.annotations
7811
8400
  },
7812
8401
  registration.callback
@@ -7841,8 +8430,13 @@ function registerAllTools(server, service, options = {}) {
7841
8430
  }
7842
8431
 
7843
8432
  // src/lib/auth.ts
7844
- import crypto3 from "node:crypto";
8433
+ import crypto2 from "node:crypto";
7845
8434
  import { createRemoteJWKSet, jwtVerify } from "jose";
8435
+ function getPrincipalOwnerKey(principal) {
8436
+ return crypto2.createHash("sha256").update(principal.id, "utf8").digest("hex");
8437
+ }
8438
+ var ClerkAuthorizationError = class extends Error {
8439
+ };
7846
8440
  var AUTH_SCHEMES = {
7847
8441
  bearer: "Bearer"
7848
8442
  };
@@ -7889,6 +8483,12 @@ function getHttpAuthConfigFromEnv() {
7889
8483
  process.env.CLERK_JWKS_URL?.trim(),
7890
8484
  issuer
7891
8485
  );
8486
+ const allowedSubjects = parseCsv(process.env.CLERK_ALLOWED_SUBJECTS);
8487
+ const allowedOrganizationIds = parseCsv(
8488
+ process.env.CLERK_ALLOWED_ORGANIZATION_IDS
8489
+ );
8490
+ const allowedRoles = parseCsv(process.env.CLERK_ALLOWED_ROLES);
8491
+ const requiredPermissions = parseCsv(process.env.CLERK_REQUIRED_PERMISSIONS);
7892
8492
  if (mode === "bearer" && !bearerToken) {
7893
8493
  throw new Error("MCP_AUTH_MODE=bearer requires MCP_AUTH_TOKEN to be set");
7894
8494
  }
@@ -7920,13 +8520,22 @@ function getHttpAuthConfigFromEnv() {
7920
8520
  `MCP_AUTH_MODE=clerk configuration error (${issues.join("; ")})`
7921
8521
  );
7922
8522
  }
8523
+ if (!allowedSubjects && !allowedOrganizationIds && !allowedRoles && !requiredPermissions) {
8524
+ throw new Error(
8525
+ "MCP_AUTH_MODE=clerk requires an explicit authorization policy. Set at least one of CLERK_ALLOWED_SUBJECTS, CLERK_ALLOWED_ORGANIZATION_IDS, CLERK_ALLOWED_ROLES, or CLERK_REQUIRED_PERMISSIONS."
8526
+ );
8527
+ }
7923
8528
  }
7924
8529
  return {
7925
8530
  mode,
7926
8531
  bearerToken,
7927
8532
  jwksUrl,
7928
8533
  issuer,
7929
- audience
8534
+ audience,
8535
+ allowedSubjects,
8536
+ allowedOrganizationIds,
8537
+ allowedRoles,
8538
+ requiredPermissions
7930
8539
  };
7931
8540
  }
7932
8541
  var HTTP_AUTH_CONFIG = getHttpAuthConfigFromEnv();
@@ -7956,11 +8565,70 @@ function extractBearerToken(req) {
7956
8565
  return token.trim();
7957
8566
  }
7958
8567
  function timingSafeEqual(a, b) {
7959
- const left = crypto3.createHash("sha256").update(a, "utf8").digest();
7960
- const right = crypto3.createHash("sha256").update(b, "utf8").digest();
7961
- return crypto3.timingSafeEqual(left, right);
8568
+ const left = crypto2.createHash("sha256").update(a, "utf8").digest();
8569
+ const right = crypto2.createHash("sha256").update(b, "utf8").digest();
8570
+ return crypto2.timingSafeEqual(left, right);
7962
8571
  }
7963
8572
  var jwksCache = /* @__PURE__ */ new Map();
8573
+ function asString(value) {
8574
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
8575
+ }
8576
+ function asStringArray(value) {
8577
+ if (!Array.isArray(value)) {
8578
+ return [];
8579
+ }
8580
+ return value.flatMap((item) => {
8581
+ const normalized = asString(item);
8582
+ return normalized ? [normalized] : [];
8583
+ });
8584
+ }
8585
+ function unique(values) {
8586
+ return [...new Set(values)];
8587
+ }
8588
+ function authorizeClerkClaims(payload, config) {
8589
+ const subject = asString(payload.sub);
8590
+ if (!subject) {
8591
+ throw new ClerkAuthorizationError("Clerk token is missing a subject");
8592
+ }
8593
+ const organization = typeof payload.o === "object" && payload.o !== null ? payload.o : void 0;
8594
+ const organizationId = asString(payload.org_id) ?? asString(organization?.id);
8595
+ const roles = unique(
8596
+ [
8597
+ asString(payload.org_role),
8598
+ asString(organization?.rol),
8599
+ ...asStringArray(payload.roles)
8600
+ ].filter((role) => Boolean(role))
8601
+ );
8602
+ const permissions = unique([
8603
+ ...asStringArray(payload.org_permissions),
8604
+ ...asStringArray(organization?.per),
8605
+ ...asStringArray(payload.permissions)
8606
+ ]);
8607
+ if (config.allowedSubjects && !config.allowedSubjects.includes(subject)) {
8608
+ throw new ClerkAuthorizationError("Clerk subject is not authorized");
8609
+ }
8610
+ if (config.allowedOrganizationIds && (!organizationId || !config.allowedOrganizationIds.includes(organizationId))) {
8611
+ throw new ClerkAuthorizationError("Clerk organization is not authorized");
8612
+ }
8613
+ if (config.allowedRoles && !roles.some((role) => config.allowedRoles?.includes(role))) {
8614
+ throw new ClerkAuthorizationError("Clerk role is not authorized");
8615
+ }
8616
+ if (config.requiredPermissions && !config.requiredPermissions.every(
8617
+ (permission) => permissions.includes(permission)
8618
+ )) {
8619
+ throw new ClerkAuthorizationError(
8620
+ "Clerk token is missing a required permission"
8621
+ );
8622
+ }
8623
+ return {
8624
+ id: `clerk:${config.issuer}:${subject}`,
8625
+ mode: "clerk",
8626
+ subject,
8627
+ organizationId,
8628
+ roles,
8629
+ permissions
8630
+ };
8631
+ }
7964
8632
  async function verifyClerkToken(token, config) {
7965
8633
  if (!config.jwksUrl) {
7966
8634
  throw new Error("Missing Clerk JWKS URL configuration");
@@ -7976,11 +8644,12 @@ async function verifyClerkToken(token, config) {
7976
8644
  jwks = createRemoteJWKSet(new URL(config.jwksUrl));
7977
8645
  jwksCache.set(config.jwksUrl, jwks);
7978
8646
  }
7979
- await jwtVerify(token, jwks, {
8647
+ const { payload } = await jwtVerify(token, jwks, {
7980
8648
  issuer: config.issuer,
7981
8649
  audience: config.audience,
7982
8650
  clockTolerance: "5s"
7983
8651
  });
8652
+ return authorizeClerkClaims(payload, config);
7984
8653
  }
7985
8654
  async function mcpAuthMiddleware(req, res, next) {
7986
8655
  if (!req.path.startsWith("/mcp")) {
@@ -7989,6 +8658,12 @@ async function mcpAuthMiddleware(req, res, next) {
7989
8658
  }
7990
8659
  const config = getHttpAuthConfig();
7991
8660
  if (config.mode === "none") {
8661
+ res.locals.authPrincipal = {
8662
+ id: "anonymous",
8663
+ mode: "none",
8664
+ roles: [],
8665
+ permissions: []
8666
+ };
7992
8667
  next();
7993
8668
  return;
7994
8669
  }
@@ -8000,13 +8675,21 @@ async function mcpAuthMiddleware(req, res, next) {
8000
8675
  return;
8001
8676
  }
8002
8677
  try {
8678
+ let principal;
8003
8679
  if (config.mode === "bearer") {
8004
8680
  if (!config.bearerToken || !timingSafeEqual(token, config.bearerToken)) {
8005
8681
  throw new Error("Bearer token mismatch");
8006
8682
  }
8683
+ principal = {
8684
+ id: `bearer:${crypto2.createHash("sha256").update(token, "utf8").digest("hex")}`,
8685
+ mode: "bearer",
8686
+ roles: [],
8687
+ permissions: []
8688
+ };
8007
8689
  } else {
8008
- await verifyClerkToken(token, config);
8690
+ principal = await verifyClerkToken(token, config);
8009
8691
  }
8692
+ res.locals.authPrincipal = principal;
8010
8693
  next();
8011
8694
  } catch (error) {
8012
8695
  Logger.warn("MCP auth failed", {
@@ -8017,15 +8700,26 @@ async function mcpAuthMiddleware(req, res, next) {
8017
8700
  reason: error instanceof Error ? error.message : "Unknown error"
8018
8701
  }
8019
8702
  });
8703
+ if (error instanceof ClerkAuthorizationError) {
8704
+ res.status(403).json({ error: "Forbidden: Principal is not authorized" });
8705
+ return;
8706
+ }
8020
8707
  res.status(401).json({ error: "Unauthorized: Token validation failed" });
8021
8708
  }
8022
8709
  }
8023
8710
 
8024
8711
  // src/lib/config.ts
8712
+ function parseStrictInteger(value) {
8713
+ if (!/^\d+$/.test(value)) {
8714
+ return Number.NaN;
8715
+ }
8716
+ const parsed = Number(value);
8717
+ return Number.isSafeInteger(parsed) ? parsed : Number.NaN;
8718
+ }
8025
8719
  function getServerConfig() {
8026
8720
  const transport = process.env.MCP_TRANSPORT?.trim() || "stdio";
8027
8721
  const sessionMode = process.env.MCP_SESSION_MODE?.trim().toLowerCase() || "stateful";
8028
- const eventStoreMode = process.env.MCP_EVENT_STORE?.trim().toLowerCase() || (sessionMode === "stateless" ? "memory" : "off");
8722
+ const eventStoreMode = process.env.MCP_EVENT_STORE?.trim().toLowerCase() || "off";
8029
8723
  if (transport !== "stdio" && transport !== "http") {
8030
8724
  throw new Error(
8031
8725
  `Invalid MCP_TRANSPORT value: ${transport}. Must be 'stdio' or 'http'`
@@ -8041,13 +8735,12 @@ function getServerConfig() {
8041
8735
  `Invalid MCP_EVENT_STORE value: ${eventStoreMode}. Must be 'off', 'memory', or 'redis'`
8042
8736
  );
8043
8737
  }
8044
- const port = Number.parseInt(
8045
- process.env.PORT?.trim() || process.env.MCP_PORT?.trim() || "3000",
8046
- 10
8047
- );
8738
+ const portVariable = process.env.PORT?.trim() ? "PORT" : "MCP_PORT";
8739
+ const portValue = process.env.PORT?.trim() || process.env.MCP_PORT?.trim() || "3000";
8740
+ const port = parseStrictInteger(portValue);
8048
8741
  if (Number.isNaN(port) || port < 1 || port > 65535) {
8049
8742
  throw new Error(
8050
- `Invalid MCP_PORT value: ${process.env.MCP_PORT}. Must be a valid port number (1-65535)`
8743
+ `Invalid ${portVariable} value: ${portValue}. Must be a valid port number (1-65535)`
8051
8744
  );
8052
8745
  }
8053
8746
  let host;
@@ -8061,21 +8754,28 @@ function getServerConfig() {
8061
8754
  host = "127.0.0.1";
8062
8755
  }
8063
8756
  const sessionTimeoutStr = (process.env.MCP_SESSION_TIMEOUT || "3600000").trim();
8064
- const sessionTimeout = Number.parseInt(sessionTimeoutStr, 10);
8757
+ const sessionTimeout = parseStrictInteger(sessionTimeoutStr);
8065
8758
  if (Number.isNaN(sessionTimeout) || sessionTimeout < 0) {
8066
8759
  throw new Error(
8067
8760
  `Invalid MCP_SESSION_TIMEOUT value: ${sessionTimeoutStr}. Must be a non-negative number`
8068
8761
  );
8069
8762
  }
8763
+ const shutdownTimeoutStr = (process.env.MCP_SHUTDOWN_TIMEOUT_MS || "10000").trim();
8764
+ const shutdownTimeout = parseStrictInteger(shutdownTimeoutStr);
8765
+ if (Number.isNaN(shutdownTimeout) || shutdownTimeout <= 0) {
8766
+ throw new Error(
8767
+ `Invalid MCP_SHUTDOWN_TIMEOUT_MS value: ${shutdownTimeoutStr}. Must be a positive integer`
8768
+ );
8769
+ }
8070
8770
  const maxSessionsStr = (process.env.MCP_MAX_SESSIONS || "100").trim();
8071
- const maxSessions = Number.parseInt(maxSessionsStr, 10);
8771
+ const maxSessions = parseStrictInteger(maxSessionsStr);
8072
8772
  if (!Number.isInteger(maxSessions) || maxSessions <= 0) {
8073
8773
  throw new Error(
8074
8774
  `Invalid MCP_MAX_SESSIONS value: ${maxSessionsStr}. Must be a positive integer`
8075
8775
  );
8076
8776
  }
8077
- const eventStoreTtlStr = (process.env.MCP_EVENT_TTL_SECONDS || "3600").trim();
8078
- const eventStoreTtlSeconds = Number.parseInt(eventStoreTtlStr, 10);
8777
+ const eventStoreTtlStr = (process.env.MCP_EVENT_TTL_SECONDS || "300").trim();
8778
+ const eventStoreTtlSeconds = parseStrictInteger(eventStoreTtlStr);
8079
8779
  if (Number.isNaN(eventStoreTtlSeconds) || eventStoreTtlSeconds <= 0) {
8080
8780
  throw new Error(
8081
8781
  `Invalid MCP_EVENT_TTL_SECONDS value: ${eventStoreTtlStr}. Must be a positive integer`
@@ -8087,6 +8787,38 @@ function getServerConfig() {
8087
8787
  "MCP_EVENT_STORE=redis requires MCP_REDIS_URL (or REDIS_URL) to be set"
8088
8788
  );
8089
8789
  }
8790
+ if (eventStoreMode === "redis" && redisUrl) {
8791
+ let parsedRedisUrl;
8792
+ try {
8793
+ parsedRedisUrl = new URL(redisUrl);
8794
+ } catch {
8795
+ throw new Error(
8796
+ "MCP_REDIS_URL must be a valid redis:// or rediss:// URL"
8797
+ );
8798
+ }
8799
+ if (!["redis:", "rediss:"].includes(parsedRedisUrl.protocol)) {
8800
+ throw new Error("MCP_REDIS_URL must use redis:// or rediss://");
8801
+ }
8802
+ if (process.env.NODE_ENV?.trim().toLowerCase() === "production" && parsedRedisUrl.protocol !== "rediss:") {
8803
+ throw new Error("MCP_REDIS_URL must use rediss:// in production");
8804
+ }
8805
+ }
8806
+ const rawEventEncryptionKey = process.env.MCP_EVENT_ENCRYPTION_KEY?.trim();
8807
+ let eventEncryptionKey;
8808
+ if (eventStoreMode === "redis") {
8809
+ if (!rawEventEncryptionKey) {
8810
+ throw new Error(
8811
+ "MCP_EVENT_ENCRYPTION_KEY must be base64-encoded 32-byte key when MCP_EVENT_STORE=redis"
8812
+ );
8813
+ }
8814
+ const decodedKey = Buffer.from(rawEventEncryptionKey, "base64");
8815
+ if (decodedKey.length !== 32 || decodedKey.toString("base64") !== rawEventEncryptionKey) {
8816
+ throw new Error(
8817
+ "MCP_EVENT_ENCRYPTION_KEY must be base64-encoded 32-byte key when MCP_EVENT_STORE=redis"
8818
+ );
8819
+ }
8820
+ eventEncryptionKey = decodedKey;
8821
+ }
8090
8822
  const redisKeyPrefix = process.env.MCP_REDIS_KEY_PREFIX?.trim() || "mcp:event-store";
8091
8823
  const keyPath = process.env.MCP_TLS_KEY_PATH?.trim() || void 0;
8092
8824
  const certPath = process.env.MCP_TLS_CERT_PATH?.trim() || void 0;
@@ -8107,13 +8839,15 @@ function getServerConfig() {
8107
8839
  mode: eventStoreMode,
8108
8840
  ttlSeconds: eventStoreTtlSeconds,
8109
8841
  redisUrl,
8110
- redisKeyPrefix
8842
+ redisKeyPrefix,
8843
+ encryptionKey: eventEncryptionKey
8111
8844
  },
8112
8845
  protocol,
8113
8846
  port,
8114
8847
  host,
8115
8848
  maxSessions,
8116
8849
  sessionTimeout,
8850
+ shutdownTimeout,
8117
8851
  tls: {
8118
8852
  enabled: tlsEnabled,
8119
8853
  keyPath,
@@ -8124,7 +8858,69 @@ function getServerConfig() {
8124
8858
  }
8125
8859
 
8126
8860
  // src/lib/event-store.ts
8861
+ import {
8862
+ createCipheriv,
8863
+ createDecipheriv,
8864
+ randomBytes as randomBytes2,
8865
+ randomUUID
8866
+ } from "node:crypto";
8127
8867
  var EVENT_STORE_CLEANUP_INTERVAL_MS = 3e4;
8868
+ var EVENT_PAYLOAD_ENCRYPTION_VERSION = "v1";
8869
+ var EVENT_PAYLOAD_AAD = Buffer.from(
8870
+ "portkey-admin-mcp:event-payload:v1",
8871
+ "utf8"
8872
+ );
8873
+ function assertEncryptionKey(key) {
8874
+ if (key.length !== 32) {
8875
+ throw new Error("Event payload encryption requires a 32-byte key");
8876
+ }
8877
+ }
8878
+ function encryptEventPayload(message, key) {
8879
+ assertEncryptionKey(key);
8880
+ const iv = randomBytes2(12);
8881
+ const cipher = createCipheriv("aes-256-gcm", key, iv, {
8882
+ authTagLength: 16
8883
+ });
8884
+ cipher.setAAD(EVENT_PAYLOAD_AAD);
8885
+ const ciphertext = Buffer.concat([
8886
+ cipher.update(JSON.stringify(message), "utf8"),
8887
+ cipher.final()
8888
+ ]);
8889
+ const authenticationTag = cipher.getAuthTag();
8890
+ return [
8891
+ EVENT_PAYLOAD_ENCRYPTION_VERSION,
8892
+ iv.toString("base64url"),
8893
+ authenticationTag.toString("base64url"),
8894
+ ciphertext.toString("base64url")
8895
+ ].join(".");
8896
+ }
8897
+ function decryptEventPayload(encoded, key) {
8898
+ assertEncryptionKey(key);
8899
+ const [version, encodedIv, encodedTag, encodedCiphertext, ...rest] = encoded.split(".");
8900
+ if (version !== EVENT_PAYLOAD_ENCRYPTION_VERSION || !encodedIv || !encodedTag || !encodedCiphertext || rest.length > 0) {
8901
+ throw new Error("Invalid encrypted event payload envelope");
8902
+ }
8903
+ const iv = Buffer.from(encodedIv, "base64url");
8904
+ const authenticationTag = Buffer.from(encodedTag, "base64url");
8905
+ if (iv.length !== 12) {
8906
+ throw new Error("Encrypted event payload must use a 12-byte nonce");
8907
+ }
8908
+ if (authenticationTag.length !== 16) {
8909
+ throw new Error(
8910
+ "Encrypted event payload must use a 16-byte authentication tag"
8911
+ );
8912
+ }
8913
+ const decipher = createDecipheriv("aes-256-gcm", key, iv, {
8914
+ authTagLength: 16
8915
+ });
8916
+ decipher.setAAD(EVENT_PAYLOAD_AAD);
8917
+ decipher.setAuthTag(authenticationTag);
8918
+ const plaintext = Buffer.concat([
8919
+ decipher.update(Buffer.from(encodedCiphertext, "base64url")),
8920
+ decipher.final()
8921
+ ]);
8922
+ return JSON.parse(plaintext.toString("utf8"));
8923
+ }
8128
8924
  var SAFE_EVENT_ID_PATTERN = /^[\w-]{1,128}$/;
8129
8925
  function assertSafeRedisId(id, kind) {
8130
8926
  if (!SAFE_EVENT_ID_PATTERN.test(id)) {
@@ -8134,36 +8930,47 @@ function assertSafeRedisId(id, kind) {
8134
8930
  }
8135
8931
  return id;
8136
8932
  }
8137
- var InMemoryEventStore = class {
8138
- sequence = 0;
8933
+ var InMemoryEventStore = class _InMemoryEventStore {
8139
8934
  ttlMs;
8140
- events = /* @__PURE__ */ new Map();
8141
- streamEvents = /* @__PURE__ */ new Map();
8142
- lastCleanupAt = 0;
8143
- constructor(ttlSeconds) {
8935
+ ownerKey;
8936
+ state;
8937
+ constructor(ttlSeconds, ownerKey, state = {
8938
+ events: /* @__PURE__ */ new Map(),
8939
+ streamEvents: /* @__PURE__ */ new Map(),
8940
+ activeReplayStreams: /* @__PURE__ */ new Set(),
8941
+ lastCleanupAt: 0
8942
+ }) {
8144
8943
  this.ttlMs = ttlSeconds * 1e3;
8944
+ this.ownerKey = ownerKey;
8945
+ this.state = state;
8946
+ }
8947
+ forOwner(ownerKey) {
8948
+ return new _InMemoryEventStore(this.ttlMs / 1e3, ownerKey, this.state);
8145
8949
  }
8146
- removeEvent(eventId, event = this.events.get(eventId)) {
8950
+ removeEvent(eventId, event = this.state.events.get(eventId)) {
8147
8951
  if (!event) {
8148
8952
  return;
8149
8953
  }
8150
- this.events.delete(eventId);
8151
- const eventIds = this.streamEvents.get(event.streamId);
8954
+ this.state.events.delete(eventId);
8955
+ const eventIds = this.state.streamEvents.get(event.streamId);
8152
8956
  if (!eventIds) {
8153
8957
  return;
8154
8958
  }
8155
8959
  const filtered = eventIds.filter((candidate) => candidate !== eventId);
8156
8960
  if (filtered.length === 0) {
8157
- this.streamEvents.delete(event.streamId);
8961
+ this.state.streamEvents.delete(event.streamId);
8158
8962
  } else if (filtered.length !== eventIds.length) {
8159
- this.streamEvents.set(event.streamId, filtered);
8963
+ this.state.streamEvents.set(event.streamId, filtered);
8160
8964
  }
8161
8965
  }
8162
8966
  getEventIfUnexpired(eventId, now = Date.now()) {
8163
- const event = this.events.get(eventId);
8967
+ const event = this.state.events.get(eventId);
8164
8968
  if (!event) {
8165
8969
  return void 0;
8166
8970
  }
8971
+ if (event.ownerKey !== this.ownerKey) {
8972
+ return void 0;
8973
+ }
8167
8974
  if (event.expiresAt > now) {
8168
8975
  return event;
8169
8976
  }
@@ -8171,39 +8978,41 @@ var InMemoryEventStore = class {
8171
8978
  return void 0;
8172
8979
  }
8173
8980
  cleanupExpired(now = Date.now()) {
8174
- for (const [eventId, event] of this.events.entries()) {
8981
+ for (const [eventId, event] of this.state.events.entries()) {
8175
8982
  if (event.expiresAt <= now) {
8176
- this.events.delete(eventId);
8983
+ this.state.events.delete(eventId);
8177
8984
  }
8178
8985
  }
8179
- for (const [streamId, eventIds] of this.streamEvents.entries()) {
8180
- const filtered = eventIds.filter((eventId) => this.events.has(eventId));
8986
+ for (const [streamId, eventIds] of this.state.streamEvents.entries()) {
8987
+ const filtered = eventIds.filter(
8988
+ (eventId) => this.state.events.has(eventId)
8989
+ );
8181
8990
  if (filtered.length === 0) {
8182
- this.streamEvents.delete(streamId);
8991
+ this.state.streamEvents.delete(streamId);
8183
8992
  } else if (filtered.length !== eventIds.length) {
8184
- this.streamEvents.set(streamId, filtered);
8993
+ this.state.streamEvents.set(streamId, filtered);
8185
8994
  }
8186
8995
  }
8187
8996
  }
8188
8997
  maybeCleanupExpired(now = Date.now()) {
8189
- if (now - this.lastCleanupAt < EVENT_STORE_CLEANUP_INTERVAL_MS) {
8998
+ if (now - this.state.lastCleanupAt < EVENT_STORE_CLEANUP_INTERVAL_MS) {
8190
8999
  return;
8191
9000
  }
8192
9001
  this.cleanupExpired(now);
8193
- this.lastCleanupAt = now;
9002
+ this.state.lastCleanupAt = now;
8194
9003
  }
8195
9004
  async storeEvent(streamId, message) {
8196
9005
  this.maybeCleanupExpired();
8197
- this.sequence += 1;
8198
- const eventId = String(this.sequence);
8199
- this.events.set(eventId, {
9006
+ const eventId = randomUUID();
9007
+ this.state.events.set(eventId, {
8200
9008
  streamId,
9009
+ ownerKey: this.ownerKey,
8201
9010
  message,
8202
9011
  expiresAt: Date.now() + this.ttlMs
8203
9012
  });
8204
- const streamIds = this.streamEvents.get(streamId) || [];
9013
+ const streamIds = this.state.streamEvents.get(streamId) || [];
8205
9014
  streamIds.push(eventId);
8206
- this.streamEvents.set(streamId, streamIds);
9015
+ this.state.streamEvents.set(streamId, streamIds);
8207
9016
  return eventId;
8208
9017
  }
8209
9018
  async getStreamIdForEventId(eventId) {
@@ -8211,6 +9020,26 @@ var InMemoryEventStore = class {
8211
9020
  this.maybeCleanupExpired(now);
8212
9021
  return this.getEventIfUnexpired(eventId, now)?.streamId;
8213
9022
  }
9023
+ async acquireReplayLease(eventId) {
9024
+ const streamId = await this.getStreamIdForEventId(eventId);
9025
+ if (!streamId) {
9026
+ return { status: "missing" };
9027
+ }
9028
+ if (this.state.activeReplayStreams.has(streamId)) {
9029
+ return { status: "conflict" };
9030
+ }
9031
+ this.state.activeReplayStreams.add(streamId);
9032
+ let released = false;
9033
+ return {
9034
+ status: "acquired",
9035
+ release: async () => {
9036
+ if (!released) {
9037
+ released = true;
9038
+ this.state.activeReplayStreams.delete(streamId);
9039
+ }
9040
+ }
9041
+ };
9042
+ }
8214
9043
  async replayEventsAfter(lastEventId, {
8215
9044
  send
8216
9045
  }) {
@@ -8220,7 +9049,7 @@ var InMemoryEventStore = class {
8220
9049
  if (!lastEvent) {
8221
9050
  throw new Error(`Event not found for replay: ${lastEventId}`);
8222
9051
  }
8223
- const eventIds = this.streamEvents.get(lastEvent.streamId) || [];
9052
+ const eventIds = this.state.streamEvents.get(lastEvent.streamId) || [];
8224
9053
  const index = eventIds.indexOf(lastEventId);
8225
9054
  if (index < 0) {
8226
9055
  throw new Error(
@@ -8237,16 +9066,36 @@ var InMemoryEventStore = class {
8237
9066
  return lastEvent.streamId;
8238
9067
  }
8239
9068
  };
8240
- var RedisEventStore = class {
8241
- client;
9069
+ var RedisEventStore = class _RedisEventStore {
8242
9070
  redisUrl;
8243
9071
  ttlSeconds;
8244
9072
  keyPrefix;
8245
- connectPromise;
8246
- constructor(redisUrl, keyPrefix, ttlSeconds) {
9073
+ ownerKey;
9074
+ encryptionKey;
9075
+ connection;
9076
+ constructor(redisUrl, keyPrefix, ttlSeconds, ownerKey, encryptionKey, connection = {}) {
8247
9077
  this.redisUrl = redisUrl;
8248
9078
  this.ttlSeconds = ttlSeconds;
8249
9079
  this.keyPrefix = keyPrefix;
9080
+ this.ownerKey = ownerKey;
9081
+ this.encryptionKey = encryptionKey;
9082
+ this.connection = connection;
9083
+ }
9084
+ get client() {
9085
+ return this.connection.client;
9086
+ }
9087
+ set client(client) {
9088
+ this.connection.client = client;
9089
+ }
9090
+ forOwner(ownerKey) {
9091
+ return new _RedisEventStore(
9092
+ this.redisUrl,
9093
+ this.keyPrefix,
9094
+ this.ttlSeconds,
9095
+ ownerKey,
9096
+ this.encryptionKey,
9097
+ this.connection
9098
+ );
8250
9099
  }
8251
9100
  counterKey() {
8252
9101
  return `${this.keyPrefix}:counter`;
@@ -8257,11 +9106,22 @@ var RedisEventStore = class {
8257
9106
  streamEventsKey(streamId) {
8258
9107
  return `${this.keyPrefix}:stream:${assertSafeRedisId(streamId, "streamId")}:events`;
8259
9108
  }
9109
+ replayLeaseKey(streamId) {
9110
+ return `${this.keyPrefix}:stream:${assertSafeRedisId(streamId, "streamId")}:replay-lease`;
9111
+ }
8260
9112
  async ensureConnected() {
8261
- if (!this.client) {
9113
+ if (!this.connection.client) {
8262
9114
  const { createClient } = await import("redis");
8263
- this.client = createClient({ url: this.redisUrl });
8264
- this.client.on("error", (error) => {
9115
+ this.connection.client = createClient({
9116
+ url: this.redisUrl,
9117
+ // node-redis v6 defaults to RESP3, a 5-second command timeout, and a
9118
+ // 30-second keepalive delay. Keep the event store's v5 behavior stable
9119
+ // while taking the v6 fixes and supported runtime baseline.
9120
+ RESP: 2,
9121
+ socket: { keepAliveInitialDelay: 5e3 },
9122
+ commandOptions: { timeout: void 0 }
9123
+ });
9124
+ this.connection.client.on("error", (error) => {
8265
9125
  Logger.error("Redis event store error", {
8266
9126
  metadata: {
8267
9127
  error: error instanceof Error ? error.message : String(error)
@@ -8269,37 +9129,40 @@ var RedisEventStore = class {
8269
9129
  });
8270
9130
  });
8271
9131
  }
8272
- if (this.client.isOpen) {
9132
+ if (this.connection.client.isOpen) {
8273
9133
  return;
8274
9134
  }
8275
- if (!this.connectPromise) {
8276
- this.connectPromise = this.client.connect().catch((error) => {
8277
- this.connectPromise = void 0;
9135
+ if (!this.connection.connectPromise) {
9136
+ this.connection.connectPromise = this.connection.client.connect().catch((error) => {
9137
+ this.connection.connectPromise = void 0;
8278
9138
  throw error;
8279
9139
  });
8280
9140
  }
8281
- await this.connectPromise;
9141
+ await this.connection.connectPromise;
8282
9142
  }
8283
9143
  getConnectedClient() {
8284
- if (!this.client) {
9144
+ if (!this.connection.client) {
8285
9145
  throw new Error("Redis client not initialized");
8286
9146
  }
8287
- return this.client;
9147
+ return this.connection.client;
8288
9148
  }
8289
9149
  async storeEvent(streamId, message) {
8290
9150
  await this.ensureConnected();
8291
9151
  const client = this.getConnectedClient();
8292
- const eventId = String(await client.incr(this.counterKey()));
9152
+ const sequence = await client.incr(this.counterKey());
9153
+ const eventId = randomUUID();
8293
9154
  const eventKey = this.eventKey(eventId);
8294
9155
  const streamKey = this.streamEventsKey(streamId);
8295
9156
  const tx = client.multi();
8296
9157
  tx.hSet(eventKey, {
8297
9158
  streamId,
8298
- message: JSON.stringify(message)
9159
+ ownerKey: this.ownerKey,
9160
+ sequence: String(sequence),
9161
+ message: encryptEventPayload(message, this.encryptionKey)
8299
9162
  });
8300
9163
  tx.expire(eventKey, this.ttlSeconds);
8301
9164
  tx.zAdd(streamKey, {
8302
- score: Number(eventId),
9165
+ score: sequence,
8303
9166
  value: eventId
8304
9167
  });
8305
9168
  tx.expire(streamKey, this.ttlSeconds);
@@ -8311,11 +9174,40 @@ var RedisEventStore = class {
8311
9174
  return void 0;
8312
9175
  }
8313
9176
  await this.ensureConnected();
8314
- const streamId = await this.getConnectedClient().hGet(
8315
- this.eventKey(eventId),
8316
- "streamId"
9177
+ const event = await this.getConnectedClient().hGetAll(
9178
+ this.eventKey(eventId)
8317
9179
  );
8318
- return streamId || void 0;
9180
+ return event.ownerKey === this.ownerKey ? event.streamId || void 0 : void 0;
9181
+ }
9182
+ async acquireReplayLease(eventId) {
9183
+ const streamId = await this.getStreamIdForEventId(eventId);
9184
+ if (!streamId) {
9185
+ return { status: "missing" };
9186
+ }
9187
+ const client = this.getConnectedClient();
9188
+ const leaseKey = this.replayLeaseKey(streamId);
9189
+ const ownerToken = randomUUID();
9190
+ const acquired = await client.set(leaseKey, ownerToken, {
9191
+ NX: true,
9192
+ PX: 6e4
9193
+ });
9194
+ if (acquired !== "OK") {
9195
+ return { status: "conflict" };
9196
+ }
9197
+ let released = false;
9198
+ return {
9199
+ status: "acquired",
9200
+ release: async () => {
9201
+ if (released) {
9202
+ return;
9203
+ }
9204
+ released = true;
9205
+ await client.eval(
9206
+ "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end",
9207
+ { keys: [leaseKey], arguments: [ownerToken] }
9208
+ );
9209
+ }
9210
+ };
8319
9211
  }
8320
9212
  async replayEventsAfter(lastEventId, {
8321
9213
  send
@@ -8324,12 +9216,12 @@ var RedisEventStore = class {
8324
9216
  const client = this.getConnectedClient();
8325
9217
  const baseEvent = await client.hGetAll(this.eventKey(lastEventId));
8326
9218
  const streamId = baseEvent.streamId;
8327
- if (!streamId) {
9219
+ if (!streamId || baseEvent.ownerKey !== this.ownerKey) {
8328
9220
  throw new Error(`Event not found for replay: ${lastEventId}`);
8329
9221
  }
8330
- const baseScore = Number(lastEventId);
9222
+ const baseScore = Number(baseEvent.sequence);
8331
9223
  if (!Number.isFinite(baseScore)) {
8332
- throw new Error(`Invalid replay event ID: ${lastEventId}`);
9224
+ throw new Error(`Invalid replay sequence for event: ${lastEventId}`);
8333
9225
  }
8334
9226
  const eventIds = await client.zRangeByScore(
8335
9227
  this.streamEventsKey(streamId),
@@ -8341,17 +9233,17 @@ var RedisEventStore = class {
8341
9233
  }
8342
9234
  const tx = client.multi();
8343
9235
  for (const eventId of eventIds) {
8344
- tx.hGet(this.eventKey(eventId), "message");
9236
+ tx.hGetAll(this.eventKey(eventId));
8345
9237
  }
8346
- const encodedMessages = await tx.exec();
9238
+ const encodedEvents = await tx.exec();
8347
9239
  for (const [index, eventId] of eventIds.entries()) {
8348
- const encoded = encodedMessages[index];
8349
- if (typeof encoded !== "string") {
9240
+ const event = encodedEvents[index];
9241
+ if (typeof event !== "object" || event === null || !("message" in event) || !("ownerKey" in event) || event.ownerKey !== this.ownerKey || typeof event.message !== "string") {
8350
9242
  continue;
8351
9243
  }
8352
9244
  let message;
8353
9245
  try {
8354
- message = JSON.parse(encoded);
9246
+ message = decryptEventPayload(event.message, this.encryptionKey);
8355
9247
  } catch {
8356
9248
  continue;
8357
9249
  }
@@ -8360,17 +9252,17 @@ var RedisEventStore = class {
8360
9252
  return streamId;
8361
9253
  }
8362
9254
  async close() {
8363
- if (!this.client) {
9255
+ if (!this.connection.client) {
8364
9256
  return;
8365
9257
  }
8366
- if (!this.client.isOpen && this.connectPromise) {
9258
+ if (!this.connection.client.isOpen && this.connection.connectPromise) {
8367
9259
  try {
8368
- await this.connectPromise;
9260
+ await this.connection.connectPromise;
8369
9261
  } catch {
8370
9262
  }
8371
9263
  }
8372
- if (this.client.isOpen) {
8373
- await this.client.quit();
9264
+ if (this.connection.client.isOpen) {
9265
+ await this.connection.client.close();
8374
9266
  }
8375
9267
  }
8376
9268
  };
@@ -8378,26 +9270,41 @@ function createManagedEventStore(config) {
8378
9270
  if (config.eventStore.mode === "off") {
8379
9271
  return {
8380
9272
  mode: "off",
9273
+ eventStoreForOwner: () => void 0,
8381
9274
  close: async () => {
8382
9275
  }
8383
9276
  };
8384
9277
  }
8385
9278
  if (config.eventStore.mode === "memory") {
9279
+ const memoryStore = new InMemoryEventStore(
9280
+ config.eventStore.ttlSeconds,
9281
+ "internal"
9282
+ );
8386
9283
  return {
8387
9284
  mode: "memory",
8388
- eventStore: new InMemoryEventStore(config.eventStore.ttlSeconds),
9285
+ eventStoreForOwner: (ownerKey) => memoryStore.forOwner(ownerKey),
9286
+ acquireReplayLease: (eventId, ownerKey) => memoryStore.forOwner(ownerKey).acquireReplayLease(eventId),
8389
9287
  close: async () => {
8390
9288
  }
8391
9289
  };
8392
9290
  }
9291
+ const encryptionKey = config.eventStore.encryptionKey;
9292
+ if (!encryptionKey) {
9293
+ throw new Error(
9294
+ "Redis event store requires MCP_EVENT_ENCRYPTION_KEY configuration"
9295
+ );
9296
+ }
8393
9297
  const redisStore = new RedisEventStore(
8394
9298
  config.eventStore.redisUrl,
8395
9299
  config.eventStore.redisKeyPrefix,
8396
- config.eventStore.ttlSeconds
9300
+ config.eventStore.ttlSeconds,
9301
+ "internal",
9302
+ encryptionKey
8397
9303
  );
8398
9304
  return {
8399
9305
  mode: "redis",
8400
- eventStore: redisStore,
9306
+ eventStoreForOwner: (ownerKey) => redisStore.forOwner(ownerKey),
9307
+ acquireReplayLease: (eventId, ownerKey) => redisStore.forOwner(ownerKey).acquireReplayLease(eventId),
8401
9308
  close: async () => {
8402
9309
  await redisStore.close();
8403
9310
  }
@@ -8407,7 +9314,7 @@ function createManagedEventStore(config) {
8407
9314
  // src/lib/mcp-server.ts
8408
9315
  import { readFileSync } from "node:fs";
8409
9316
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8410
- import { z as z21 } from "zod";
9317
+ import { z as z23 } from "zod";
8411
9318
  var PACKAGE_JSON_URL_CANDIDATES = [
8412
9319
  new URL("../../package.json", import.meta.url),
8413
9320
  new URL("../package.json", import.meta.url)
@@ -8480,8 +9387,8 @@ function registerServerPromptsAndResources(server) {
8480
9387
  title: "Plan Portkey Admin Workflow",
8481
9388
  description: "Create a concise, safe plan for a Portkey Admin API task using this MCP server.",
8482
9389
  argsSchema: {
8483
- task: z21.string().min(1).max(500).describe("Portkey admin task to plan, such as promoting a prompt"),
8484
- area: z21.string().max(80).optional().describe(
9390
+ task: z23.string().min(1).max(500).describe("Portkey admin task to plan, such as promoting a prompt"),
9391
+ area: z23.string().max(80).optional().describe(
8485
9392
  "Optional Portkey area, such as prompts, analytics, configs, or users"
8486
9393
  )
8487
9394
  }
@@ -8533,6 +9440,25 @@ function parseConfiguredToolDomains(rawValue = process.env.PORTKEY_TOOL_DOMAINS?
8533
9440
  }
8534
9441
  return normalizeToolDomains(requestedDomains);
8535
9442
  }
9443
+ function resolveToolDomains(requestedDomains) {
9444
+ const configuredDomains = parseConfiguredToolDomains();
9445
+ if (!configuredDomains) {
9446
+ return requestedDomains ? normalizeToolDomains(requestedDomains) : void 0;
9447
+ }
9448
+ if (!requestedDomains) {
9449
+ return configuredDomains;
9450
+ }
9451
+ const configuredDomainSet = new Set(configuredDomains);
9452
+ const disallowedDomains = normalizeToolDomains(requestedDomains).filter(
9453
+ (domain) => !configuredDomainSet.has(domain)
9454
+ );
9455
+ if (disallowedDomains.length > 0) {
9456
+ throw new Error(
9457
+ `Requested tool domains are not allowed by PORTKEY_TOOL_DOMAINS: ${disallowedDomains.join(", ")}`
9458
+ );
9459
+ }
9460
+ return normalizeToolDomains(requestedDomains);
9461
+ }
8536
9462
  function createMcpServer(options = {}) {
8537
9463
  const service = getSharedPortkeyService();
8538
9464
  const server = new McpServer(
@@ -8551,7 +9477,7 @@ function createMcpServer(options = {}) {
8551
9477
  );
8552
9478
  registerServerPromptsAndResources(server);
8553
9479
  registerAllTools(server, service, {
8554
- domains: options.toolDomains ?? parseConfiguredToolDomains()
9480
+ domains: resolveToolDomains(options.toolDomains)
8555
9481
  });
8556
9482
  return {
8557
9483
  server,
@@ -8560,6 +9486,8 @@ function createMcpServer(options = {}) {
8560
9486
  }
8561
9487
 
8562
9488
  // src/lib/security.ts
9489
+ import { createHash } from "node:crypto";
9490
+ import { ipKeyGenerator } from "express-rate-limit";
8563
9491
  function parseOrigins(raw) {
8564
9492
  return raw.split(",").map((origin) => origin.trim()).filter(Boolean);
8565
9493
  }
@@ -8576,7 +9504,16 @@ function parseOriginParts(value) {
8576
9504
  }
8577
9505
  }
8578
9506
  function normalizeHostWithoutPort(value) {
8579
- return value.trim().toLowerCase().split(":")[0];
9507
+ const normalized = value.trim().toLowerCase();
9508
+ if (normalized.startsWith("[")) {
9509
+ const bracketEnd = normalized.indexOf("]");
9510
+ if (bracketEnd < 0) {
9511
+ return normalized;
9512
+ }
9513
+ const remainder = normalized.slice(bracketEnd + 1);
9514
+ return remainder === "" || /^:\d+$/.test(remainder) ? normalized.slice(0, bracketEnd + 1) : normalized;
9515
+ }
9516
+ return normalized.split(":", 1)[0];
8580
9517
  }
8581
9518
  function isOriginMatch(origin, allowedOrigin) {
8582
9519
  const originParts = parseOriginParts(origin);
@@ -8600,7 +9537,14 @@ function resolveAllowedOrigins() {
8600
9537
  return parsed;
8601
9538
  }
8602
9539
  }
8603
- return ["http://localhost", "https://localhost"];
9540
+ return [
9541
+ "http://localhost",
9542
+ "https://localhost",
9543
+ "http://127.0.0.1",
9544
+ "https://127.0.0.1",
9545
+ "http://[::1]",
9546
+ "https://[::1]"
9547
+ ];
8604
9548
  }
8605
9549
  var ALLOWED_ORIGINS = resolveAllowedOrigins();
8606
9550
  function getAllowedOrigins() {
@@ -8664,12 +9608,72 @@ function hostValidationMiddleware(req, res, next) {
8664
9608
  }
8665
9609
  next();
8666
9610
  }
9611
+ var RATE_LIMIT_REDIS_COMMAND_TIMEOUT_MS = 5e3;
9612
+ var REDIS_TOKEN_BUCKET_SCRIPT = `
9613
+ local values = redis.call('HMGET', KEYS[1], 'tokens', 'lastRefill')
9614
+ local tokens = tonumber(values[1])
9615
+ local lastRefill = tonumber(values[2])
9616
+ local maxTokens = tonumber(ARGV[1])
9617
+ local windowMs = tonumber(ARGV[2])
9618
+ local refillRate = tonumber(ARGV[3])
9619
+ local now = tonumber(ARGV[4])
9620
+
9621
+ if not tokens or not lastRefill then
9622
+ tokens = maxTokens
9623
+ lastRefill = now
9624
+ end
9625
+
9626
+ local elapsedMs = math.max(0, now - lastRefill)
9627
+ local tokensToAdd = math.floor((elapsedMs / windowMs) * refillRate)
9628
+ if tokensToAdd > 0 then
9629
+ tokens = math.min(maxTokens, tokens + tokensToAdd)
9630
+ lastRefill = now
9631
+ end
9632
+
9633
+ local allowed = 0
9634
+ if tokens > 0 then
9635
+ tokens = tokens - 1
9636
+ allowed = 1
9637
+ end
9638
+
9639
+ redis.call('HSET', KEYS[1], 'tokens', tokens, 'lastRefill', lastRefill)
9640
+ redis.call('PEXPIRE', KEYS[1], math.max(windowMs * 2, 1000))
9641
+ return { allowed, tokens }
9642
+ `;
9643
+ async function consumeRedisRateLimitToken(client, options) {
9644
+ let timeout;
9645
+ const result = await Promise.race([
9646
+ client.eval(REDIS_TOKEN_BUCKET_SCRIPT, {
9647
+ keys: [options.key],
9648
+ arguments: [
9649
+ String(options.maxTokens),
9650
+ String(options.windowMs),
9651
+ String(options.refillRate),
9652
+ String(options.now)
9653
+ ]
9654
+ }),
9655
+ new Promise((_resolve, reject) => {
9656
+ timeout = setTimeout(() => {
9657
+ reject(new Error("Redis rate-limit command timed out"));
9658
+ }, options.timeoutMs ?? RATE_LIMIT_REDIS_COMMAND_TIMEOUT_MS);
9659
+ })
9660
+ ]).finally(() => {
9661
+ if (timeout) {
9662
+ clearTimeout(timeout);
9663
+ }
9664
+ });
9665
+ if (!Array.isArray(result) || result.length < 1) {
9666
+ throw new Error("Invalid response from Redis rate-limit script");
9667
+ }
9668
+ return { allowed: Number(result[0]) === 1 };
9669
+ }
8667
9670
  function parsePositiveIntegerEnv(name, fallback) {
8668
9671
  const raw = process.env[name];
8669
9672
  if (raw === void 0) {
8670
9673
  return fallback;
8671
9674
  }
8672
- const parsed = Number.parseInt(raw.trim(), 10);
9675
+ const normalized = raw.trim();
9676
+ const parsed = /^\d+$/.test(normalized) ? Number(normalized) : Number.NaN;
8673
9677
  if (Number.isFinite(parsed) && Number.isInteger(parsed) && parsed > 0) {
8674
9678
  return parsed;
8675
9679
  }
@@ -8682,20 +9686,71 @@ function parsePositiveIntegerEnv(name, fallback) {
8682
9686
  });
8683
9687
  return fallback;
8684
9688
  }
9689
+ function isExplicitlyEnabled2(value) {
9690
+ return /^(1|true|yes)$/i.test(value?.trim() ?? "");
9691
+ }
9692
+ function resolveRateLimitStore() {
9693
+ const store = process.env.RATE_LIMIT_STORE?.trim().toLowerCase() || "memory";
9694
+ if (store !== "memory" && store !== "redis") {
9695
+ throw new Error(
9696
+ `Invalid RATE_LIMIT_STORE value: ${store}. Must be 'memory' or 'redis'`
9697
+ );
9698
+ }
9699
+ return store;
9700
+ }
9701
+ function resolveRateLimitRedisUrl(store) {
9702
+ if (store !== "redis") {
9703
+ return void 0;
9704
+ }
9705
+ const redisUrl = process.env.RATE_LIMIT_REDIS_URL?.trim() || process.env.MCP_REDIS_URL?.trim() || process.env.REDIS_URL?.trim();
9706
+ if (!redisUrl) {
9707
+ throw new Error(
9708
+ "RATE_LIMIT_STORE=redis requires RATE_LIMIT_REDIS_URL, MCP_REDIS_URL, or REDIS_URL"
9709
+ );
9710
+ }
9711
+ let parsedUrl;
9712
+ try {
9713
+ parsedUrl = new URL(redisUrl);
9714
+ } catch {
9715
+ throw new Error(
9716
+ "Rate-limit Redis URL must be a valid redis:// or rediss:// URL"
9717
+ );
9718
+ }
9719
+ if (!["redis:", "rediss:"].includes(parsedUrl.protocol)) {
9720
+ throw new Error("Rate-limit Redis URL must use redis:// or rediss://");
9721
+ }
9722
+ if (process.env.NODE_ENV?.trim().toLowerCase() === "production" && parsedUrl.protocol !== "rediss:") {
9723
+ throw new Error("Rate-limit Redis URL must use rediss:// in production");
9724
+ }
9725
+ return redisUrl;
9726
+ }
9727
+ var rateLimitEnabled = process.env.RATE_LIMIT_ENABLED?.trim().toLowerCase() !== "false";
9728
+ var rateLimitStore = resolveRateLimitStore();
9729
+ if (rateLimitEnabled && rateLimitStore === "memory" && process.env.NODE_ENV?.trim().toLowerCase() === "production" && !isExplicitlyEnabled2(process.env.RATE_LIMIT_SINGLE_PROCESS)) {
9730
+ throw new Error(
9731
+ "Production in-memory rate limiting requires RATE_LIMIT_SINGLE_PROCESS=true. Use RATE_LIMIT_STORE=redis for multi-instance deployments."
9732
+ );
9733
+ }
8685
9734
  var RATE_LIMIT_CONFIG = {
8686
- enabled: process.env.RATE_LIMIT_ENABLED?.trim().toLowerCase() !== "false",
9735
+ enabled: rateLimitEnabled,
9736
+ store: rateLimitStore,
8687
9737
  maxTokens: parsePositiveIntegerEnv("RATE_LIMIT_MAX", 60),
8688
9738
  windowMs: parsePositiveIntegerEnv("RATE_LIMIT_WINDOW_MS", 6e4),
8689
9739
  refillRate: parsePositiveIntegerEnv("RATE_LIMIT_REFILL", 60),
8690
- maxBuckets: parsePositiveIntegerEnv("RATE_LIMIT_MAX_BUCKETS", 1e4)
9740
+ maxBuckets: parsePositiveIntegerEnv("RATE_LIMIT_MAX_BUCKETS", 1e4),
9741
+ redisUrl: resolveRateLimitRedisUrl(rateLimitStore),
9742
+ redisKeyPrefix: process.env.RATE_LIMIT_REDIS_KEY_PREFIX?.trim() || "mcp:rate-limit"
8691
9743
  };
8692
9744
  function getRateLimitConfig() {
8693
9745
  return RATE_LIMIT_CONFIG;
8694
9746
  }
8695
9747
  var buckets = /* @__PURE__ */ new Map();
8696
9748
  var overflowBucket;
8697
- function getClientIdentifier(req) {
8698
- return req.ip || "unknown";
9749
+ function getClientIdentifier(req, res, scope) {
9750
+ const principal = res.locals?.authPrincipal;
9751
+ const principalKey = scope === "principal" && principal ? getPrincipalOwnerKey(principal) : scope;
9752
+ const trustedIp = ipKeyGenerator(req.ip || "unknown");
9753
+ return createHash("sha256").update(scope, "utf8").update("\0", "utf8").update(principalKey, "utf8").update("\0", "utf8").update(trustedIp, "utf8").digest("hex");
8699
9754
  }
8700
9755
  function refillBucket(bucket, config) {
8701
9756
  const now = Date.now();
@@ -8756,7 +9811,63 @@ function consumeToken(clientId, config) {
8756
9811
  }
8757
9812
  return { allowed: false };
8758
9813
  }
8759
- function rateLimitMiddleware(req, res, next) {
9814
+ var rateLimitRedisClient;
9815
+ var rateLimitRedisCreatePromise;
9816
+ var rateLimitRedisConnectPromise;
9817
+ async function createRateLimitRedisClient(config) {
9818
+ const { createClient } = await import("redis");
9819
+ const client = createClient({
9820
+ url: config.redisUrl,
9821
+ RESP: 2,
9822
+ socket: { keepAliveInitialDelay: 5e3 },
9823
+ commandOptions: { timeout: RATE_LIMIT_REDIS_COMMAND_TIMEOUT_MS }
9824
+ });
9825
+ client.on("error", (error) => {
9826
+ Logger.error("Redis rate-limit store error", {
9827
+ metadata: {
9828
+ error: error instanceof Error ? error.message : String(error)
9829
+ }
9830
+ });
9831
+ });
9832
+ return client;
9833
+ }
9834
+ async function getRateLimitRedisClient(config) {
9835
+ if (!config.redisUrl) {
9836
+ throw new Error("Rate-limit Redis URL is not configured");
9837
+ }
9838
+ if (!rateLimitRedisClient) {
9839
+ rateLimitRedisCreatePromise ??= createRateLimitRedisClient(config).catch(
9840
+ (error) => {
9841
+ rateLimitRedisCreatePromise = void 0;
9842
+ throw error;
9843
+ }
9844
+ );
9845
+ rateLimitRedisClient = await rateLimitRedisCreatePromise;
9846
+ }
9847
+ if (!rateLimitRedisClient.isOpen) {
9848
+ rateLimitRedisConnectPromise ??= rateLimitRedisClient.connect().catch((error) => {
9849
+ rateLimitRedisConnectPromise = void 0;
9850
+ throw error;
9851
+ });
9852
+ await rateLimitRedisConnectPromise;
9853
+ }
9854
+ return rateLimitRedisClient;
9855
+ }
9856
+ function applyRateLimitDecision(allowed, config, req, res, next, clientId) {
9857
+ if (allowed) {
9858
+ next();
9859
+ return;
9860
+ }
9861
+ const retryAfterMs = Math.ceil(config.windowMs / config.refillRate);
9862
+ Logger.warn("Rate limit exceeded", {
9863
+ path: req.path,
9864
+ method: req.method,
9865
+ metadata: { clientFingerprint: clientId.slice(0, 16), retryAfterMs }
9866
+ });
9867
+ res.setHeader("Retry-After", Math.ceil(retryAfterMs / 1e3).toString());
9868
+ res.status(429).json({ error: "Too Many Requests" });
9869
+ }
9870
+ function applyRateLimit(scope, req, res, next) {
8760
9871
  const config = getRateLimitConfig();
8761
9872
  if (!config.enabled) {
8762
9873
  next();
@@ -8766,20 +9877,69 @@ function rateLimitMiddleware(req, res, next) {
8766
9877
  next();
8767
9878
  return;
8768
9879
  }
8769
- const clientId = getClientIdentifier(req);
8770
- const { allowed } = consumeToken(clientId, config);
8771
- if (!allowed) {
8772
- const retryAfterMs = Math.ceil(config.windowMs / config.refillRate);
8773
- Logger.warn("Rate limit exceeded", {
8774
- path: req.path,
8775
- method: req.method,
8776
- metadata: { clientId, retryAfterMs }
8777
- });
8778
- res.setHeader("Retry-After", Math.ceil(retryAfterMs / 1e3).toString());
8779
- res.status(429).json({ error: "Too Many Requests" });
9880
+ if (scope === "authentication" && req.path !== "/mcp") {
9881
+ next();
8780
9882
  return;
8781
9883
  }
8782
- next();
9884
+ const clientId = getClientIdentifier(req, res, scope);
9885
+ if (config.store === "memory") {
9886
+ const { allowed } = consumeToken(clientId, config);
9887
+ applyRateLimitDecision(allowed, config, req, res, next, clientId);
9888
+ return;
9889
+ }
9890
+ return (async () => {
9891
+ try {
9892
+ const client = await getRateLimitRedisClient(config);
9893
+ const { allowed } = await consumeRedisRateLimitToken(client, {
9894
+ key: `${config.redisKeyPrefix}:${clientId}`,
9895
+ maxTokens: config.maxTokens,
9896
+ windowMs: config.windowMs,
9897
+ refillRate: config.refillRate,
9898
+ now: Date.now()
9899
+ });
9900
+ applyRateLimitDecision(allowed, config, req, res, next, clientId);
9901
+ } catch (error) {
9902
+ Logger.error("Rate-limit store unavailable", {
9903
+ path: req.path,
9904
+ method: req.method,
9905
+ metadata: {
9906
+ error: error instanceof Error ? error.message : String(error)
9907
+ }
9908
+ });
9909
+ res.status(503).json({ error: "Rate limit service unavailable" });
9910
+ }
9911
+ })();
9912
+ }
9913
+ function rateLimitMiddleware(req, res, next) {
9914
+ return applyRateLimit("authentication", req, res, next);
9915
+ }
9916
+ function principalRateLimitMiddleware(req, res, next) {
9917
+ return applyRateLimit("principal", req, res, next);
9918
+ }
9919
+ async function closeRateLimitStore() {
9920
+ if (!rateLimitRedisClient && rateLimitRedisCreatePromise) {
9921
+ try {
9922
+ rateLimitRedisClient = await rateLimitRedisCreatePromise;
9923
+ } catch {
9924
+ rateLimitRedisCreatePromise = void 0;
9925
+ return;
9926
+ }
9927
+ }
9928
+ if (!rateLimitRedisClient) {
9929
+ return;
9930
+ }
9931
+ if (!rateLimitRedisClient.isOpen && rateLimitRedisConnectPromise) {
9932
+ try {
9933
+ await rateLimitRedisConnectPromise;
9934
+ } catch {
9935
+ }
9936
+ }
9937
+ if (rateLimitRedisClient.isOpen) {
9938
+ await rateLimitRedisClient.close();
9939
+ }
9940
+ rateLimitRedisClient = void 0;
9941
+ rateLimitRedisCreatePromise = void 0;
9942
+ rateLimitRedisConnectPromise = void 0;
8783
9943
  }
8784
9944
  var cleanupTimer = setInterval(
8785
9945
  () => {
@@ -8816,6 +9976,11 @@ var SessionStore = class {
8816
9976
  get(sessionId) {
8817
9977
  return this.sessions.get(sessionId);
8818
9978
  }
9979
+ /** Return a session only when it belongs to the supplied principal. */
9980
+ getOwned(sessionId, ownerKey) {
9981
+ const entry = this.sessions.get(sessionId);
9982
+ return entry?.ownerKey === ownerKey ? entry : void 0;
9983
+ }
8819
9984
  /**
8820
9985
  * Get a transport by session ID
8821
9986
  * @param sessionId - The session identifier
@@ -8951,6 +10116,17 @@ var SessionStore = class {
8951
10116
  };
8952
10117
 
8953
10118
  // src/lib/http-app.ts
10119
+ function waitForResponseWritable(response) {
10120
+ return new Promise((resolve) => {
10121
+ const done = () => {
10122
+ response.off("drain", done);
10123
+ response.off("close", done);
10124
+ resolve();
10125
+ };
10126
+ response.once("drain", done);
10127
+ response.once("close", done);
10128
+ });
10129
+ }
8954
10130
  function getReadyCheckMode() {
8955
10131
  const readyCheckMode = process.env.MCP_READY_CHECK_MODE?.trim().toLowerCase() || "local";
8956
10132
  if (readyCheckMode !== "local" && readyCheckMode !== "portkey") {
@@ -9056,6 +10232,16 @@ function respondJsonRpcClientError(res, statusCode, message, code = -32602) {
9056
10232
  id: null
9057
10233
  });
9058
10234
  }
10235
+ function getRequestOwnerKey(res) {
10236
+ const principal = res.locals.authPrincipal;
10237
+ if (!principal) {
10238
+ throw new Error("Authenticated MCP request is missing its principal");
10239
+ }
10240
+ return getPrincipalOwnerKey(principal);
10241
+ }
10242
+ function fingerprintCapabilityId(id) {
10243
+ return createHash2("sha256").update(id, "utf8").digest("hex").slice(0, 16);
10244
+ }
9059
10245
  function parseRequestedToolDomains(rawQuery) {
9060
10246
  if (rawQuery === void 0) {
9061
10247
  return void 0;
@@ -9083,7 +10269,7 @@ function parseRequestedToolDomains(rawQuery) {
9083
10269
  `Unknown tool domains: ${invalidDomains.join(", ")}. Valid domains: ${TOOL_DOMAIN_NAMES.join(", ")}`
9084
10270
  );
9085
10271
  }
9086
- return normalizeToolDomains(normalizedDomains);
10272
+ return resolveToolDomains(normalizedDomains);
9087
10273
  }
9088
10274
  function areToolDomainsEqual(left, right) {
9089
10275
  const normalizedLeft = left ? normalizeToolDomains(left) : [];
@@ -9114,7 +10300,7 @@ function getMcpProtocolVersion(req) {
9114
10300
  }
9115
10301
  function validateRequiredProtocolVersion(protocolVersion, expectedProtocolVersion) {
9116
10302
  if (!protocolVersion) {
9117
- return "Bad Request: MCP-Protocol-Version header is required for requests after initialization";
10303
+ return void 0;
9118
10304
  }
9119
10305
  if (!SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
9120
10306
  const safe = sanitizeProtocolVersion(protocolVersion);
@@ -9146,6 +10332,8 @@ function createHttpAppRuntime() {
9146
10332
  const isStatefulSessionMode = config.sessionMode === "stateful";
9147
10333
  const publicBaseUrl = buildConfiguredPublicBaseUrl(config);
9148
10334
  const sessionStore = new SessionStore(config.maxSessions);
10335
+ const activeStatelessConnections = /* @__PURE__ */ new Set();
10336
+ const activeReplayOperations = /* @__PURE__ */ new Set();
9149
10337
  const app = express();
9150
10338
  let isReady = false;
9151
10339
  let server;
@@ -9153,12 +10341,32 @@ function createHttpAppRuntime() {
9153
10341
  let closeRuntimeResourcesPromise;
9154
10342
  let sigintHandler;
9155
10343
  let sigtermHandler;
9156
- async function handleStatelessRequest(req, res, context, parsedBody, toolDomains) {
10344
+ async function handleStatelessRequest(req, res, context, ownerKey, parsedBody, toolDomains) {
10345
+ let replayStreamId;
10346
+ let responseDisconnected = false;
10347
+ const storedMessages = /* @__PURE__ */ new WeakSet();
10348
+ const sharedEventStore = managedEventStore.eventStoreForOwner(ownerKey);
10349
+ const requestEventStore = sharedEventStore ? {
10350
+ storeEvent: async (streamId, message) => {
10351
+ replayStreamId ??= streamId;
10352
+ storedMessages.add(message);
10353
+ return sharedEventStore.storeEvent(streamId, message);
10354
+ },
10355
+ getStreamIdForEventId: async (eventId) => sharedEventStore.getStreamIdForEventId?.(eventId),
10356
+ replayEventsAfter: (lastEventId, options) => sharedEventStore.replayEventsAfter(lastEventId, options)
10357
+ } : void 0;
9157
10358
  const transport = new StreamableHTTPServerTransport({
9158
10359
  sessionIdGenerator: void 0,
9159
- eventStore: managedEventStore.eventStore
10360
+ eventStore: requestEventStore
9160
10361
  });
9161
10362
  const { server: mcpServer } = createMcpServer({ toolDomains });
10363
+ const requestMessages = Array.isArray(parsedBody) ? parsedBody : [parsedBody];
10364
+ const pendingRequestIds = /* @__PURE__ */ new Set();
10365
+ for (const message of requestMessages) {
10366
+ if (isRecord(message) && typeof message.method === "string" && (typeof message.id === "string" || typeof message.id === "number")) {
10367
+ pendingRequestIds.add(message.id);
10368
+ }
10369
+ }
9162
10370
  let closePromise;
9163
10371
  const closeConnection = () => {
9164
10372
  if (!closePromise) {
@@ -9169,16 +10377,60 @@ function createHttpAppRuntime() {
9169
10377
  error: error instanceof Error ? error.message : String(error)
9170
10378
  }
9171
10379
  });
10380
+ }).finally(() => {
10381
+ activeStatelessConnections.delete(closeConnection);
9172
10382
  });
9173
10383
  }
9174
10384
  return closePromise;
9175
10385
  };
10386
+ activeStatelessConnections.add(closeConnection);
10387
+ const originalSend = transport.send.bind(transport);
10388
+ transport.send = async (message, options) => {
10389
+ if ((responseDisconnected || res.destroyed) && replayStreamId && sharedEventStore) {
10390
+ await sharedEventStore.storeEvent(replayStreamId, message);
10391
+ storedMessages.add(message);
10392
+ } else {
10393
+ try {
10394
+ await originalSend(message, options);
10395
+ } catch (error) {
10396
+ const disconnected = error instanceof Error && error.message.startsWith(
10397
+ "No connection established for request ID:"
10398
+ );
10399
+ if (!disconnected || !replayStreamId || !sharedEventStore) {
10400
+ throw error;
10401
+ }
10402
+ if (!storedMessages.has(message)) {
10403
+ await sharedEventStore.storeEvent(replayStreamId, message);
10404
+ storedMessages.add(message);
10405
+ }
10406
+ }
10407
+ if ((responseDisconnected || res.destroyed) && replayStreamId && sharedEventStore && !storedMessages.has(message)) {
10408
+ await sharedEventStore.storeEvent(replayStreamId, message);
10409
+ storedMessages.add(message);
10410
+ }
10411
+ }
10412
+ if ("id" in message && ("result" in message || "error" in message)) {
10413
+ pendingRequestIds.delete(message.id);
10414
+ if (pendingRequestIds.size === 0) {
10415
+ queueMicrotask(() => {
10416
+ void closeConnection();
10417
+ });
10418
+ }
10419
+ }
10420
+ };
9176
10421
  res.once("close", () => {
9177
- void closeConnection();
10422
+ if (pendingRequestIds.size > 0 && replayStreamId) {
10423
+ responseDisconnected = true;
10424
+ } else {
10425
+ void closeConnection();
10426
+ }
9178
10427
  });
9179
10428
  try {
9180
10429
  await mcpServer.connect(transport);
9181
10430
  await transport.handleRequest(req, res, parsedBody);
10431
+ if (pendingRequestIds.size === 0) {
10432
+ await closeConnection();
10433
+ }
9182
10434
  } catch (error) {
9183
10435
  await closeConnection();
9184
10436
  throw error;
@@ -9204,14 +10456,32 @@ function createHttpAppRuntime() {
9204
10456
  app.use(hostValidationMiddleware);
9205
10457
  }
9206
10458
  app.use(originValidationMiddleware);
10459
+ const rateLimitConfig = getRateLimitConfig();
10460
+ app.use(
10461
+ expressRateLimit({
10462
+ windowMs: rateLimitConfig.windowMs,
10463
+ limit: rateLimitConfig.maxTokens,
10464
+ standardHeaders: false,
10465
+ legacyHeaders: false,
10466
+ validate: false,
10467
+ skip: (req) => !rateLimitConfig.enabled || req.path !== "/mcp",
10468
+ message: { error: "Too Many Requests" }
10469
+ })
10470
+ );
9207
10471
  app.use(rateLimitMiddleware);
9208
10472
  app.use(mcpAuthMiddleware);
10473
+ app.use(principalRateLimitMiddleware);
9209
10474
  app.use(
9210
10475
  (err, _req, res, next) => {
9211
- if (err && typeof err === "object" && "type" in err && err.type === "entity.too.large") {
10476
+ const errorType = err && typeof err === "object" && "type" in err ? err.type : void 0;
10477
+ if (errorType === "entity.too.large") {
9212
10478
  res.status(413).json({ error: "Payload too large" });
9213
10479
  return;
9214
10480
  }
10481
+ if (errorType === "entity.parse.failed") {
10482
+ res.status(400).json({ error: "Malformed JSON request body" });
10483
+ return;
10484
+ }
9215
10485
  next(err);
9216
10486
  }
9217
10487
  );
@@ -9357,6 +10627,7 @@ function createHttpAppRuntime() {
9357
10627
  });
9358
10628
  });
9359
10629
  app.post("/mcp", async (req, res) => {
10630
+ const ownerKey = getRequestOwnerKey(res);
9360
10631
  let requestedToolDomains;
9361
10632
  const protocolVersionHeader = getMcpProtocolVersion(req);
9362
10633
  try {
@@ -9370,6 +10641,15 @@ function createHttpAppRuntime() {
9370
10641
  return;
9371
10642
  }
9372
10643
  if (!isStatefulSessionMode) {
10644
+ if (activeStatelessConnections.size >= config.maxSessions) {
10645
+ respondJsonRpcClientError(
10646
+ res,
10647
+ 503,
10648
+ `Maximum active stateless request limit reached (${config.maxSessions})`,
10649
+ -32e3
10650
+ );
10651
+ return;
10652
+ }
9373
10653
  if (!isInitializeRequest(req.body)) {
9374
10654
  const protocolError = validateRequiredProtocolVersion(
9375
10655
  protocolVersionHeader
@@ -9384,6 +10664,7 @@ function createHttpAppRuntime() {
9384
10664
  req,
9385
10665
  res,
9386
10666
  "POST /mcp stateless",
10667
+ ownerKey,
9387
10668
  req.body,
9388
10669
  requestedToolDomains
9389
10670
  );
@@ -9395,7 +10676,7 @@ function createHttpAppRuntime() {
9395
10676
  const sessionId = req.headers["mcp-session-id"];
9396
10677
  let transport;
9397
10678
  let hasReservedSessionSlot = false;
9398
- const sessionEntry = sessionId ? sessionStore.get(sessionId) : void 0;
10679
+ const sessionEntry = sessionId ? sessionStore.getOwned(sessionId, ownerKey) : void 0;
9399
10680
  if (sessionId && sessionEntry) {
9400
10681
  transport = sessionEntry.transport;
9401
10682
  if (transport) {
@@ -9438,11 +10719,12 @@ function createHttpAppRuntime() {
9438
10719
  }
9439
10720
  hasReservedSessionSlot = true;
9440
10721
  const newTransport = new StreamableHTTPServerTransport({
9441
- sessionIdGenerator: () => randomUUID(),
9442
- eventStore: managedEventStore.eventStore,
10722
+ sessionIdGenerator: () => randomUUID2(),
10723
+ eventStore: managedEventStore.eventStoreForOwner(ownerKey),
9443
10724
  onsessioninitialized: (id) => {
9444
10725
  sessionStore.set(id, {
9445
10726
  transport: newTransport,
10727
+ ownerKey,
9446
10728
  toolDomains: requestedToolDomains,
9447
10729
  protocolVersion: getNegotiatedProtocolVersion(req.body),
9448
10730
  createdAt: Date.now(),
@@ -9450,13 +10732,13 @@ function createHttpAppRuntime() {
9450
10732
  });
9451
10733
  hasReservedSessionSlot = false;
9452
10734
  Logger.info("MCP session initialized", {
9453
- metadata: { sessionId: id }
10735
+ metadata: { sessionFingerprint: fingerprintCapabilityId(id) }
9454
10736
  });
9455
10737
  },
9456
10738
  onsessionclosed: (id) => {
9457
10739
  sessionStore.delete(id);
9458
10740
  Logger.info("MCP session closed", {
9459
- metadata: { sessionId: id }
10741
+ metadata: { sessionFingerprint: fingerprintCapabilityId(id) }
9460
10742
  });
9461
10743
  }
9462
10744
  });
@@ -9508,19 +10790,7 @@ function createHttpAppRuntime() {
9508
10790
  }
9509
10791
  });
9510
10792
  app.get("/mcp", async (req, res) => {
9511
- if (!isStatefulSessionMode) {
9512
- res.status(405).json({
9513
- jsonrpc: "2.0",
9514
- error: {
9515
- code: -32e3,
9516
- message: "GET /mcp is not used in stateless session mode"
9517
- },
9518
- id: null
9519
- });
9520
- return;
9521
- }
9522
- const sessionId = req.headers["mcp-session-id"];
9523
- const protocolVersionHeader = getMcpProtocolVersion(req);
10793
+ const ownerKey = getRequestOwnerKey(res);
9524
10794
  let requestedToolDomains;
9525
10795
  try {
9526
10796
  requestedToolDomains = parseRequestedToolDomains(req.query.tools);
@@ -9532,6 +10802,158 @@ function createHttpAppRuntime() {
9532
10802
  );
9533
10803
  return;
9534
10804
  }
10805
+ if (!isStatefulSessionMode) {
10806
+ if (activeReplayOperations.size >= config.maxSessions) {
10807
+ respondJsonRpcClientError(
10808
+ res,
10809
+ 503,
10810
+ `Maximum active stateless replay limit reached (${config.maxSessions})`,
10811
+ -32e3
10812
+ );
10813
+ return;
10814
+ }
10815
+ const replayEventStore = managedEventStore.eventStoreForOwner(ownerKey);
10816
+ if (!replayEventStore || !managedEventStore.acquireReplayLease) {
10817
+ res.setHeader("Allow", "POST");
10818
+ respondJsonRpcClientError(
10819
+ res,
10820
+ 405,
10821
+ "Stateless GET /mcp replay requires MCP_EVENT_STORE=memory or redis",
10822
+ -32e3
10823
+ );
10824
+ return;
10825
+ }
10826
+ const lastEventId = req.headers["last-event-id"];
10827
+ if (typeof lastEventId !== "string" || lastEventId.length === 0) {
10828
+ res.setHeader("Allow", "POST");
10829
+ respondJsonRpcClientError(
10830
+ res,
10831
+ 405,
10832
+ "Stateless GET /mcp only supports replay with Last-Event-ID",
10833
+ -32e3
10834
+ );
10835
+ return;
10836
+ }
10837
+ if (!req.headers.accept?.includes("text/event-stream")) {
10838
+ respondJsonRpcClientError(
10839
+ res,
10840
+ 406,
10841
+ "Not Acceptable: Client must accept text/event-stream",
10842
+ -32e3
10843
+ );
10844
+ return;
10845
+ }
10846
+ const protocolError = validateRequiredProtocolVersion(
10847
+ getMcpProtocolVersion(req)
10848
+ );
10849
+ if (protocolError) {
10850
+ respondJsonRpcClientError(res, 400, protocolError, -32e3);
10851
+ return;
10852
+ }
10853
+ try {
10854
+ const lease = await managedEventStore.acquireReplayLease(
10855
+ lastEventId,
10856
+ ownerKey
10857
+ );
10858
+ if (lease.status !== "acquired") {
10859
+ if (lease.status === "missing") {
10860
+ respondJsonRpcClientError(
10861
+ res,
10862
+ 400,
10863
+ "Invalid event ID format",
10864
+ -32e3
10865
+ );
10866
+ return;
10867
+ }
10868
+ respondJsonRpcClientError(
10869
+ res,
10870
+ 409,
10871
+ "Conflict: Stream already has an active replay connection",
10872
+ -32e3
10873
+ );
10874
+ return;
10875
+ }
10876
+ const replayOperation = (async () => {
10877
+ try {
10878
+ res.status(200);
10879
+ res.setHeader("Content-Type", "text/event-stream");
10880
+ res.setHeader("Cache-Control", "no-cache, no-transform");
10881
+ res.flushHeaders();
10882
+ let cursor = lastEventId;
10883
+ let finalResponseSeen = false;
10884
+ const deadline = Date.now() + 25e3;
10885
+ while (isReady && !res.destroyed && !finalResponseSeen) {
10886
+ await replayEventStore.replayEventsAfter(cursor, {
10887
+ send: async (eventId, message) => {
10888
+ cursor = eventId;
10889
+ finalResponseSeen = "id" in message && ("result" in message || "error" in message);
10890
+ if (!res.write(
10891
+ `event: message
10892
+ id: ${eventId}
10893
+ data: ${JSON.stringify(message)}
10894
+
10895
+ `
10896
+ )) {
10897
+ await waitForResponseWritable(res);
10898
+ }
10899
+ }
10900
+ });
10901
+ if (finalResponseSeen || res.destroyed) {
10902
+ break;
10903
+ }
10904
+ if (Date.now() >= deadline) {
10905
+ res.write(`id: ${cursor}
10906
+ data:
10907
+
10908
+ `);
10909
+ break;
10910
+ }
10911
+ await new Promise((resolvePoll) => setTimeout(resolvePoll, 100));
10912
+ }
10913
+ if (!res.writableEnded && !res.destroyed) {
10914
+ await new Promise((resolveResponse) => {
10915
+ const done = () => resolveResponse();
10916
+ res.once("finish", done);
10917
+ res.once("close", done);
10918
+ res.end();
10919
+ });
10920
+ }
10921
+ } finally {
10922
+ try {
10923
+ await lease.release();
10924
+ } catch (error) {
10925
+ Logger.error("Failed to release stateless replay lease", {
10926
+ metadata: {
10927
+ error: error instanceof Error ? error.message : String(error)
10928
+ }
10929
+ });
10930
+ }
10931
+ }
10932
+ })();
10933
+ activeReplayOperations.add(replayOperation);
10934
+ try {
10935
+ await replayOperation;
10936
+ } finally {
10937
+ activeReplayOperations.delete(replayOperation);
10938
+ }
10939
+ } catch (error) {
10940
+ if (res.headersSent) {
10941
+ Logger.error("GET /mcp stateless replay failed", {
10942
+ metadata: {
10943
+ error: error instanceof Error ? error.message : String(error)
10944
+ }
10945
+ });
10946
+ if (!res.writableEnded) {
10947
+ res.end();
10948
+ }
10949
+ } else {
10950
+ respondJsonRpcInternalError(res, "GET /mcp stateless", error);
10951
+ }
10952
+ }
10953
+ return;
10954
+ }
10955
+ const sessionId = req.headers["mcp-session-id"];
10956
+ const protocolVersionHeader = getMcpProtocolVersion(req);
9535
10957
  if (!sessionId) {
9536
10958
  res.status(400).json({
9537
10959
  jsonrpc: "2.0",
@@ -9543,7 +10965,7 @@ function createHttpAppRuntime() {
9543
10965
  });
9544
10966
  return;
9545
10967
  }
9546
- const sessionEntry = sessionStore.get(sessionId);
10968
+ const sessionEntry = sessionStore.getOwned(sessionId, ownerKey);
9547
10969
  const transport = sessionEntry?.transport;
9548
10970
  if (transport) {
9549
10971
  const protocolError = validateRequiredProtocolVersion(
@@ -9576,6 +10998,7 @@ function createHttpAppRuntime() {
9576
10998
  }
9577
10999
  });
9578
11000
  app.delete("/mcp", async (req, res) => {
11001
+ const ownerKey = getRequestOwnerKey(res);
9579
11002
  if (!isStatefulSessionMode) {
9580
11003
  res.status(405).json({
9581
11004
  jsonrpc: "2.0",
@@ -9600,7 +11023,7 @@ function createHttpAppRuntime() {
9600
11023
  });
9601
11024
  return;
9602
11025
  }
9603
- const sessionEntry = sessionStore.get(sessionId);
11026
+ const sessionEntry = sessionStore.getOwned(sessionId, ownerKey);
9604
11027
  const transport = sessionEntry?.transport;
9605
11028
  if (transport) {
9606
11029
  const protocolError = validateRequiredProtocolVersion(
@@ -9628,7 +11051,9 @@ function createHttpAppRuntime() {
9628
11051
  const expiredIds = await sessionStore.cleanup(config.sessionTimeout);
9629
11052
  for (const id of expiredIds) {
9630
11053
  Logger.info("MCP session expired and cleaned up", {
9631
- metadata: { sessionId: id }
11054
+ metadata: {
11055
+ sessionFingerprint: fingerprintCapabilityId(id)
11056
+ }
9632
11057
  });
9633
11058
  }
9634
11059
  } catch (error) {
@@ -9667,8 +11092,15 @@ function createHttpAppRuntime() {
9667
11092
  if (isStatefulSessionMode) {
9668
11093
  await sessionStore.closeAll();
9669
11094
  }
11095
+ await Promise.allSettled(
11096
+ Array.from(
11097
+ activeStatelessConnections,
11098
+ (closeConnection) => closeConnection()
11099
+ )
11100
+ );
11101
+ await Promise.allSettled(activeReplayOperations);
9670
11102
  } finally {
9671
- await managedEventStore.close();
11103
+ await Promise.all([managedEventStore.close(), closeRateLimitStore()]);
9672
11104
  }
9673
11105
  })();
9674
11106
  }
@@ -9715,7 +11147,7 @@ function createHttpAppRuntime() {
9715
11147
  console.log(" GET /ready - Readiness check");
9716
11148
  console.log(" POST /mcp - MCP requests");
9717
11149
  console.log(
9718
- ` GET /mcp - ${isStatefulSessionMode ? "SSE notifications" : "Not used in stateless mode"}`
11150
+ ` GET /mcp - ${isStatefulSessionMode ? "SSE notifications and replay" : managedEventStore.mode !== "off" ? "SSE replay (requires Last-Event-ID)" : "Unavailable (event store off)"}`
9719
11151
  );
9720
11152
  console.log(
9721
11153
  ` DELETE /mcp - ${isStatefulSessionMode ? "Close session" : "Not used in stateless mode"}`
@@ -9730,9 +11162,7 @@ function createHttpAppRuntime() {
9730
11162
  async function shutdown(signal) {
9731
11163
  console.log(`
9732
11164
  [MCP] Received ${signal}, shutting down gracefully...`);
9733
- const rawShutdownTimeout = process.env.MCP_SHUTDOWN_TIMEOUT_MS?.trim();
9734
- const parsedShutdownTimeout = rawShutdownTimeout ? Number.parseInt(rawShutdownTimeout, 10) : 1e4;
9735
- const shutdownTimeoutMs = Number.isFinite(parsedShutdownTimeout) && parsedShutdownTimeout > 0 ? parsedShutdownTimeout : 1e4;
11165
+ const shutdownTimeoutMs = config.shutdownTimeout;
9736
11166
  const forceExitTimer = setTimeout(() => {
9737
11167
  console.error(
9738
11168
  `[MCP] Forced shutdown after ${shutdownTimeoutMs}ms timeout`