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.
- package/README.md +89 -34
- package/build/index.js +1243 -638
- package/build/server.js +2275 -845
- 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 {
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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/
|
|
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:
|
|
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:
|
|
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:
|
|
1746
|
-
total_units_max:
|
|
1747
|
-
cost_min:
|
|
1748
|
-
cost_max:
|
|
1749
|
-
prompt_token_min:
|
|
1750
|
-
prompt_token_max:
|
|
1751
|
-
completion_token_min:
|
|
1752
|
-
completion_token_max:
|
|
1753
|
-
status_code:
|
|
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:
|
|
1757
|
-
weighted_feedback_max:
|
|
1758
|
-
virtual_keys:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1774
|
-
api_key_ids:
|
|
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
|
-
},
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1895
|
+
prompt_slug: z2.string().optional().describe("Filter by prompt slug")
|
|
1810
1896
|
};
|
|
1811
1897
|
var paginatedAnalyticsSchema = {
|
|
1812
1898
|
...baseAnalyticsSchema,
|
|
1813
|
-
current_page:
|
|
1814
|
-
page_size:
|
|
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:
|
|
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
|
|
2487
|
+
import { z as z3 } from "zod";
|
|
2402
2488
|
var AUDIT_TOOL_SCHEMAS = {
|
|
2403
2489
|
listAuditLogs: {
|
|
2404
|
-
workspace_id:
|
|
2405
|
-
actor_id:
|
|
2406
|
-
action:
|
|
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:
|
|
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:
|
|
2413
|
-
start_time:
|
|
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:
|
|
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:
|
|
2420
|
-
page_size:
|
|
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
|
|
2559
|
+
import { z as z4 } from "zod";
|
|
2474
2560
|
var COLLECTIONS_TOOL_SCHEMAS = {
|
|
2475
2561
|
listCollections: {
|
|
2476
|
-
workspace_id:
|
|
2477
|
-
search:
|
|
2478
|
-
current_page:
|
|
2479
|
-
page_size:
|
|
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:
|
|
2568
|
+
name: z4.string().describe(
|
|
2483
2569
|
"Collection name (e.g., 'hourlink', 'apizone', 'research-pilot')"
|
|
2484
2570
|
),
|
|
2485
|
-
workspace_id:
|
|
2571
|
+
workspace_id: z4.string().optional().describe("Workspace ID to create collection in")
|
|
2486
2572
|
},
|
|
2487
2573
|
getCollection: {
|
|
2488
|
-
collection_id:
|
|
2574
|
+
collection_id: z4.string().describe("Collection ID or slug to retrieve")
|
|
2489
2575
|
},
|
|
2490
2576
|
updateCollection: {
|
|
2491
|
-
collection_id:
|
|
2492
|
-
name:
|
|
2493
|
-
description:
|
|
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:
|
|
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
|
|
2703
|
+
import { z as z5 } from "zod";
|
|
2618
2704
|
var CONFIGS_TOOL_SCHEMAS = {
|
|
2619
2705
|
listConfigs: {
|
|
2620
|
-
current_page:
|
|
2621
|
-
page_size:
|
|
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:
|
|
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:
|
|
2630
|
-
workspace_id:
|
|
2631
|
-
cache_mode:
|
|
2632
|
-
cache_max_age:
|
|
2633
|
-
retry_attempts:
|
|
2634
|
-
retry_on_status_codes:
|
|
2635
|
-
strategy_mode:
|
|
2636
|
-
targets:
|
|
2637
|
-
|
|
2638
|
-
provider:
|
|
2639
|
-
virtual_key:
|
|
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:
|
|
2647
|
-
name:
|
|
2648
|
-
status:
|
|
2649
|
-
cache_mode:
|
|
2650
|
-
cache_max_age:
|
|
2651
|
-
retry_attempts:
|
|
2652
|
-
retry_on_status_codes:
|
|
2653
|
-
strategy_mode:
|
|
2654
|
-
targets:
|
|
2655
|
-
|
|
2656
|
-
provider:
|
|
2657
|
-
virtual_key:
|
|
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:
|
|
2750
|
+
slug: z5.string().describe("Configuration slug to delete")
|
|
2665
2751
|
},
|
|
2666
2752
|
listConfigVersions: {
|
|
2667
|
-
slug:
|
|
2753
|
+
slug: z5.string().describe("Configuration slug to list versions for")
|
|
2668
2754
|
}
|
|
2669
2755
|
};
|
|
2670
|
-
var configPayloadSchema =
|
|
2671
|
-
cache_mode:
|
|
2672
|
-
cache_max_age:
|
|
2673
|
-
retry_attempts:
|
|
2674
|
-
retry_on_status_codes:
|
|
2675
|
-
strategy_mode:
|
|
2676
|
-
targets:
|
|
2677
|
-
|
|
2678
|
-
provider:
|
|
2679
|
-
virtual_key:
|
|
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
|
|
2897
|
-
var guardrailCheckSchema =
|
|
2898
|
-
id:
|
|
2899
|
-
name:
|
|
2900
|
-
is_enabled:
|
|
2901
|
-
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 =
|
|
2904
|
-
value:
|
|
2905
|
-
weight:
|
|
2906
|
-
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 =
|
|
2909
|
-
deny:
|
|
2910
|
-
async:
|
|
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:
|
|
2914
|
-
message:
|
|
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:
|
|
2919
|
-
organisation_id:
|
|
2920
|
-
page_size:
|
|
2921
|
-
current_page:
|
|
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:
|
|
3010
|
+
guardrail_id: z6.string().describe("The guardrail UUID or slug (with guard_ prefix) to retrieve")
|
|
2925
3011
|
},
|
|
2926
3012
|
createGuardrail: {
|
|
2927
|
-
name:
|
|
2928
|
-
checks:
|
|
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:
|
|
2933
|
-
organisation_id:
|
|
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:
|
|
2937
|
-
name:
|
|
2938
|
-
checks:
|
|
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:
|
|
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
|
|
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:
|
|
3138
|
-
page_size:
|
|
3139
|
-
workspace_id:
|
|
3140
|
-
type:
|
|
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:
|
|
3146
|
-
ai_provider_id:
|
|
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:
|
|
3235
|
+
slug: z7.string().optional().describe(
|
|
3150
3236
|
"URL-friendly identifier (auto-generated from name if not provided)"
|
|
3151
3237
|
),
|
|
3152
|
-
key:
|
|
3153
|
-
description:
|
|
3154
|
-
workspace_id:
|
|
3155
|
-
api_version:
|
|
3156
|
-
resource_name:
|
|
3157
|
-
deployment_name:
|
|
3158
|
-
aws_region:
|
|
3159
|
-
aws_access_key_id:
|
|
3160
|
-
aws_secret_access_key:
|
|
3161
|
-
vertex_project_id:
|
|
3162
|
-
vertex_region:
|
|
3163
|
-
custom_host:
|
|
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:
|
|
3252
|
+
slug: z7.string().describe("The unique slug identifier of the integration to retrieve")
|
|
3167
3253
|
},
|
|
3168
3254
|
updateIntegration: {
|
|
3169
|
-
slug:
|
|
3170
|
-
name:
|
|
3171
|
-
key:
|
|
3172
|
-
description:
|
|
3173
|
-
api_version:
|
|
3174
|
-
resource_name:
|
|
3175
|
-
deployment_name:
|
|
3176
|
-
aws_region:
|
|
3177
|
-
aws_access_key_id:
|
|
3178
|
-
aws_secret_access_key:
|
|
3179
|
-
vertex_project_id:
|
|
3180
|
-
vertex_region:
|
|
3181
|
-
custom_host:
|
|
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:
|
|
3270
|
+
slug: z7.string().describe("The slug of the integration to delete")
|
|
3185
3271
|
},
|
|
3186
3272
|
listIntegrationModels: {
|
|
3187
|
-
slug:
|
|
3188
|
-
current_page:
|
|
3189
|
-
page_size:
|
|
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:
|
|
3193
|
-
models:
|
|
3194
|
-
|
|
3195
|
-
slug:
|
|
3196
|
-
model_name:
|
|
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:
|
|
3200
|
-
is_custom:
|
|
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:
|
|
3206
|
-
model_slug:
|
|
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:
|
|
3210
|
-
current_page:
|
|
3211
|
-
page_size:
|
|
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:
|
|
3215
|
-
workspaces:
|
|
3216
|
-
|
|
3217
|
-
id:
|
|
3218
|
-
enabled:
|
|
3219
|
-
credit_limit:
|
|
3220
|
-
alert_threshold:
|
|
3221
|
-
rate_limit_rpm:
|
|
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
|
|
3640
|
+
import { z as z8 } from "zod";
|
|
3555
3641
|
var KEYS_TOOL_SCHEMAS = {
|
|
3556
3642
|
listVirtualKeys: {
|
|
3557
|
-
current_page:
|
|
3558
|
-
page_size:
|
|
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:
|
|
3562
|
-
provider:
|
|
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:
|
|
3566
|
-
note:
|
|
3567
|
-
workspace_id:
|
|
3568
|
-
api_version:
|
|
3569
|
-
resource_name:
|
|
3570
|
-
deployment_name:
|
|
3571
|
-
credit_limit:
|
|
3572
|
-
alert_threshold:
|
|
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:
|
|
3661
|
+
rate_limit_rpm: z8.coerce.number().positive().optional().describe("Requests per minute limit")
|
|
3576
3662
|
},
|
|
3577
3663
|
getVirtualKey: {
|
|
3578
|
-
slug:
|
|
3664
|
+
slug: z8.string().describe("The unique slug identifier of the virtual key to retrieve")
|
|
3579
3665
|
},
|
|
3580
3666
|
updateVirtualKey: {
|
|
3581
|
-
slug:
|
|
3582
|
-
name:
|
|
3583
|
-
key:
|
|
3584
|
-
note:
|
|
3585
|
-
credit_limit:
|
|
3586
|
-
alert_threshold:
|
|
3587
|
-
rate_limit_rpm:
|
|
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:
|
|
3676
|
+
slug: z8.string().describe("The slug of the virtual key to delete")
|
|
3591
3677
|
},
|
|
3592
3678
|
createApiKey: {
|
|
3593
|
-
type:
|
|
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:
|
|
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:
|
|
3600
|
-
description:
|
|
3601
|
-
workspace_id:
|
|
3602
|
-
user_id:
|
|
3603
|
-
scopes:
|
|
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:
|
|
3607
|
-
alert_threshold:
|
|
3608
|
-
rate_limit_rpm:
|
|
3609
|
-
default_config_id:
|
|
3610
|
-
default_metadata:
|
|
3611
|
-
alert_emails:
|
|
3612
|
-
expires_at:
|
|
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:
|
|
3616
|
-
current_page:
|
|
3617
|
-
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:
|
|
3706
|
+
id: z8.string().uuid().describe("The UUID of the API key to retrieve")
|
|
3621
3707
|
},
|
|
3622
3708
|
updateApiKey: {
|
|
3623
|
-
id:
|
|
3624
|
-
name:
|
|
3625
|
-
description:
|
|
3626
|
-
scopes:
|
|
3627
|
-
credit_limit:
|
|
3628
|
-
alert_threshold:
|
|
3629
|
-
rate_limit_rpm:
|
|
3630
|
-
default_config_id:
|
|
3631
|
-
default_metadata:
|
|
3632
|
-
alert_emails:
|
|
3633
|
-
expires_at:
|
|
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:
|
|
3730
|
+
id: z8.string().uuid().describe("The UUID of the API key to delete")
|
|
3639
3731
|
}
|
|
3640
3732
|
};
|
|
3641
|
-
var createApiKeySchema =
|
|
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:
|
|
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:
|
|
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
|
|
4133
|
+
import { z as z9 } from "zod";
|
|
4011
4134
|
var LABELS_TOOL_SCHEMAS = {
|
|
4012
4135
|
createPromptLabel: {
|
|
4013
|
-
name:
|
|
4014
|
-
organisation_id:
|
|
4015
|
-
workspace_id:
|
|
4016
|
-
description:
|
|
4017
|
-
color_code:
|
|
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:
|
|
4023
|
-
workspace_id:
|
|
4024
|
-
search:
|
|
4025
|
-
current_page:
|
|
4026
|
-
page_size:
|
|
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:
|
|
4030
|
-
organisation_id:
|
|
4031
|
-
workspace_id:
|
|
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:
|
|
4035
|
-
name:
|
|
4036
|
-
description:
|
|
4037
|
-
color_code:
|
|
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:
|
|
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
|
|
4181
|
-
var conditionSchema =
|
|
4182
|
-
field:
|
|
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:
|
|
4186
|
-
value:
|
|
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:
|
|
4313
|
+
workspace_id: z10.string().optional().describe("Filter rate limits by workspace ID")
|
|
4191
4314
|
},
|
|
4192
4315
|
getRateLimit: {
|
|
4193
|
-
id:
|
|
4316
|
+
id: z10.string().describe("The unique identifier of the rate limit")
|
|
4194
4317
|
},
|
|
4195
4318
|
createRateLimit: {
|
|
4196
|
-
conditions:
|
|
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:
|
|
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:
|
|
4203
|
-
unit:
|
|
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:
|
|
4207
|
-
name:
|
|
4208
|
-
workspace_id:
|
|
4209
|
-
organisation_id:
|
|
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:
|
|
4213
|
-
name:
|
|
4214
|
-
unit:
|
|
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:
|
|
4340
|
+
value: z10.coerce.number().positive().optional().describe("New maximum allowed value per unit")
|
|
4218
4341
|
},
|
|
4219
4342
|
deleteRateLimit: {
|
|
4220
|
-
id:
|
|
4343
|
+
id: z10.string().describe("The unique identifier of the rate limit")
|
|
4221
4344
|
},
|
|
4222
4345
|
listUsageLimits: {
|
|
4223
|
-
workspace_id:
|
|
4346
|
+
workspace_id: z10.string().optional().describe("Filter usage limits by workspace ID")
|
|
4224
4347
|
},
|
|
4225
4348
|
getUsageLimit: {
|
|
4226
|
-
id:
|
|
4349
|
+
id: z10.string().describe("The unique identifier of the usage limit")
|
|
4227
4350
|
},
|
|
4228
4351
|
createUsageLimit: {
|
|
4229
|
-
conditions:
|
|
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:
|
|
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:
|
|
4236
|
-
credit_limit:
|
|
4237
|
-
name:
|
|
4238
|
-
alert_threshold:
|
|
4239
|
-
periodic_reset:
|
|
4240
|
-
workspace_id:
|
|
4241
|
-
organisation_id:
|
|
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:
|
|
4245
|
-
name:
|
|
4246
|
-
credit_limit:
|
|
4247
|
-
alert_threshold:
|
|
4248
|
-
periodic_reset:
|
|
4249
|
-
reset_usage_for_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:
|
|
4375
|
+
id: z10.string().describe("The unique identifier of the usage limit")
|
|
4253
4376
|
},
|
|
4254
4377
|
listUsageLimitEntities: {
|
|
4255
|
-
limit_id:
|
|
4378
|
+
limit_id: z10.string().describe("Usage limit policy ID")
|
|
4256
4379
|
},
|
|
4257
4380
|
resetUsageLimitEntity: {
|
|
4258
|
-
limit_id:
|
|
4259
|
-
entity_id:
|
|
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
|
|
4566
|
-
var logExportFieldSchema =
|
|
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:
|
|
4591
|
-
request_provider:
|
|
4592
|
-
request_method:
|
|
4593
|
-
request_headers:
|
|
4594
|
-
request_body:
|
|
4595
|
-
response_status:
|
|
4596
|
-
response_headers:
|
|
4597
|
-
response_body:
|
|
4598
|
-
response_time:
|
|
4599
|
-
streaming_mode:
|
|
4600
|
-
metadata_organization:
|
|
4601
|
-
metadata_user:
|
|
4602
|
-
metadata_trace_id:
|
|
4603
|
-
metadata_span_id:
|
|
4604
|
-
metadata_span_name:
|
|
4605
|
-
metadata_parent_span_id:
|
|
4606
|
-
metadata_custom:
|
|
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:
|
|
4610
|
-
description:
|
|
4611
|
-
time_min:
|
|
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:
|
|
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:
|
|
4618
|
-
cost_max:
|
|
4619
|
-
total_units_min:
|
|
4620
|
-
total_units_max:
|
|
4621
|
-
ai_model:
|
|
4622
|
-
requested_fields:
|
|
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:
|
|
4750
|
+
workspace_id: z11.string().describe("Workspace ID to list exports for (required)")
|
|
4628
4751
|
},
|
|
4629
4752
|
getLogExport: {
|
|
4630
|
-
export_id:
|
|
4753
|
+
export_id: z11.string().describe("The unique ID of the log export")
|
|
4631
4754
|
},
|
|
4632
4755
|
startLogExport: {
|
|
4633
|
-
export_id:
|
|
4756
|
+
export_id: z11.string().describe("The unique ID of the log export to start")
|
|
4634
4757
|
},
|
|
4635
4758
|
cancelLogExport: {
|
|
4636
|
-
export_id:
|
|
4759
|
+
export_id: z11.string().describe("The unique ID of the log export to cancel")
|
|
4637
4760
|
},
|
|
4638
4761
|
downloadLogExport: {
|
|
4639
|
-
export_id:
|
|
4762
|
+
export_id: z11.string().describe("The unique ID of the log export to download")
|
|
4640
4763
|
},
|
|
4641
4764
|
updateLogExport: {
|
|
4642
|
-
export_id:
|
|
4643
|
-
workspace_id:
|
|
4644
|
-
time_of_generation_max:
|
|
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:
|
|
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
|
|
5011
|
+
import { z as z12 } from "zod";
|
|
4889
5012
|
var MCP_INTEGRATIONS_TOOL_SCHEMAS = {
|
|
4890
5013
|
listMcpIntegrations: {
|
|
4891
|
-
current_page:
|
|
4892
|
-
page_size:
|
|
4893
|
-
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:
|
|
4897
|
-
url:
|
|
4898
|
-
auth_type:
|
|
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:
|
|
5024
|
+
transport: z12.enum(["http", "sse"]).describe(
|
|
4902
5025
|
"MCP transport protocol: 'http' (streamable HTTP) or 'sse' (server-sent events)"
|
|
4903
5026
|
),
|
|
4904
|
-
slug:
|
|
4905
|
-
description:
|
|
4906
|
-
custom_headers:
|
|
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:
|
|
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:
|
|
5037
|
+
id: z12.string().describe("The MCP integration ID or slug to retrieve")
|
|
4915
5038
|
},
|
|
4916
5039
|
updateMcpIntegration: {
|
|
4917
|
-
id:
|
|
4918
|
-
name:
|
|
4919
|
-
description:
|
|
4920
|
-
url:
|
|
4921
|
-
auth_type:
|
|
4922
|
-
transport:
|
|
4923
|
-
custom_headers:
|
|
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:
|
|
5051
|
+
id: z12.string().describe("The MCP integration ID or slug to delete")
|
|
4929
5052
|
},
|
|
4930
5053
|
getMcpIntegrationMetadata: {
|
|
4931
|
-
id:
|
|
5054
|
+
id: z12.string().describe("The MCP integration ID or slug")
|
|
4932
5055
|
},
|
|
4933
5056
|
listMcpIntegrationCapabilities: {
|
|
4934
|
-
id:
|
|
5057
|
+
id: z12.string().describe("The MCP integration ID or slug")
|
|
4935
5058
|
},
|
|
4936
5059
|
updateMcpIntegrationCapabilities: {
|
|
4937
|
-
id:
|
|
4938
|
-
capabilities:
|
|
4939
|
-
|
|
4940
|
-
name:
|
|
4941
|
-
type:
|
|
4942
|
-
enabled:
|
|
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:
|
|
5070
|
+
id: z12.string().describe("The MCP integration ID or slug")
|
|
4948
5071
|
},
|
|
4949
5072
|
updateMcpIntegrationWorkspaces: {
|
|
4950
|
-
id:
|
|
4951
|
-
workspaces:
|
|
4952
|
-
|
|
4953
|
-
id:
|
|
4954
|
-
enabled:
|
|
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
|
|
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:
|
|
5257
|
-
page_size:
|
|
5258
|
-
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:
|
|
5262
|
-
mcp_integration_id:
|
|
5263
|
-
slug:
|
|
5264
|
-
description:
|
|
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:
|
|
5390
|
+
id: z13.string().describe("The MCP server ID or slug to retrieve")
|
|
5268
5391
|
},
|
|
5269
5392
|
updateMcpServer: {
|
|
5270
|
-
id:
|
|
5271
|
-
name:
|
|
5272
|
-
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:
|
|
5398
|
+
id: z13.string().describe("The MCP server ID or slug to delete")
|
|
5276
5399
|
},
|
|
5277
5400
|
testMcpServer: {
|
|
5278
|
-
id:
|
|
5401
|
+
id: z13.string().describe("The MCP server ID or slug to test")
|
|
5279
5402
|
},
|
|
5280
5403
|
listMcpServerCapabilities: {
|
|
5281
|
-
id:
|
|
5282
|
-
current_page:
|
|
5283
|
-
page_size:
|
|
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:
|
|
5287
|
-
capabilities:
|
|
5288
|
-
|
|
5289
|
-
name:
|
|
5290
|
-
type:
|
|
5291
|
-
enabled:
|
|
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:
|
|
5297
|
-
current_page:
|
|
5298
|
-
page_size:
|
|
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:
|
|
5302
|
-
users:
|
|
5303
|
-
|
|
5304
|
-
user_id:
|
|
5305
|
-
enabled:
|
|
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
|
|
5672
|
+
import { z as z14 } from "zod";
|
|
5550
5673
|
var PARTIALS_TOOL_SCHEMAS = {
|
|
5551
5674
|
createPromptPartial: {
|
|
5552
|
-
name:
|
|
5553
|
-
string:
|
|
5554
|
-
workspace_id:
|
|
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:
|
|
5680
|
+
version_description: z14.string().optional().describe("Description for this version")
|
|
5558
5681
|
},
|
|
5559
5682
|
listPromptPartials: {
|
|
5560
|
-
collection_id:
|
|
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:
|
|
5688
|
+
prompt_partial_id: z14.string().describe("Prompt partial ID or slug to retrieve")
|
|
5566
5689
|
},
|
|
5567
5690
|
updatePromptPartial: {
|
|
5568
|
-
prompt_partial_id:
|
|
5569
|
-
name:
|
|
5570
|
-
string:
|
|
5571
|
-
description:
|
|
5572
|
-
status:
|
|
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:
|
|
5698
|
+
prompt_partial_id: z14.string().describe("Prompt partial ID or slug to delete")
|
|
5576
5699
|
},
|
|
5577
5700
|
listPartialVersions: {
|
|
5578
|
-
prompt_partial_id:
|
|
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:
|
|
5582
|
-
version:
|
|
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
|
|
5891
|
+
import { z as z16 } from "zod";
|
|
5769
5892
|
|
|
5770
5893
|
// src/lib/schemas.ts
|
|
5771
|
-
import { z as
|
|
5772
|
-
var PromptFunctionSchema =
|
|
5773
|
-
name:
|
|
5774
|
-
description:
|
|
5775
|
-
parameters:
|
|
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 =
|
|
5778
|
-
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 =
|
|
5782
|
-
max_tokens:
|
|
5783
|
-
temperature:
|
|
5784
|
-
top_p:
|
|
5785
|
-
top_k:
|
|
5786
|
-
presence_penalty:
|
|
5787
|
-
frequency_penalty:
|
|
5788
|
-
stop:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5797
|
-
client_id:
|
|
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:
|
|
5801
|
-
feature:
|
|
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 =
|
|
5804
|
-
mode:
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
5841
|
-
var PROMPT_TEMPLATE_CONTENT_BLOCK_SCHEMA =
|
|
5842
|
-
type:
|
|
5843
|
-
text:
|
|
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 =
|
|
5846
|
-
role:
|
|
5847
|
-
content:
|
|
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:
|
|
5852
|
-
collection_id:
|
|
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:
|
|
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:
|
|
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:
|
|
5862
|
-
virtual_key:
|
|
5863
|
-
model:
|
|
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:
|
|
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:
|
|
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:
|
|
5873
|
-
template_metadata:
|
|
5874
|
-
functions:
|
|
5875
|
-
tools:
|
|
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:
|
|
6000
|
+
dry_run: z16.boolean().optional().describe("When true, validate without creating")
|
|
5878
6001
|
},
|
|
5879
6002
|
listPrompts: {
|
|
5880
|
-
collection_id:
|
|
6003
|
+
collection_id: z16.string().optional().describe(
|
|
5881
6004
|
"Filter by collection ID (recommended for app-specific prompts)"
|
|
5882
6005
|
),
|
|
5883
|
-
workspace_id:
|
|
5884
|
-
search:
|
|
5885
|
-
current_page:
|
|
5886
|
-
page_size:
|
|
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:
|
|
6012
|
+
prompt_id: z16.string().describe("Prompt ID or slug to retrieve")
|
|
5890
6013
|
},
|
|
5891
6014
|
updatePrompt: {
|
|
5892
|
-
prompt_id:
|
|
5893
|
-
name:
|
|
5894
|
-
collection_id:
|
|
5895
|
-
string:
|
|
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:
|
|
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:
|
|
5902
|
-
model:
|
|
5903
|
-
virtual_key:
|
|
5904
|
-
version_description:
|
|
5905
|
-
template_metadata:
|
|
5906
|
-
functions:
|
|
5907
|
-
tools:
|
|
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:
|
|
6034
|
+
dry_run: z16.boolean().optional().describe("When true, validate without updating")
|
|
5912
6035
|
},
|
|
5913
6036
|
deletePrompt: {
|
|
5914
|
-
prompt_id:
|
|
6037
|
+
prompt_id: z16.string().describe("Prompt ID or slug to delete")
|
|
5915
6038
|
},
|
|
5916
6039
|
publishPrompt: {
|
|
5917
|
-
prompt_id:
|
|
5918
|
-
version:
|
|
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:
|
|
6044
|
+
prompt_id: z16.string().describe("Prompt ID or slug to list versions for")
|
|
5922
6045
|
},
|
|
5923
6046
|
renderPrompt: {
|
|
5924
|
-
prompt_id:
|
|
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:
|
|
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:
|
|
6064
|
+
name: z16.string().describe("Prompt name to create or find for update"),
|
|
5942
6065
|
app: PromptAppIdentifierSchema,
|
|
5943
6066
|
env: PromptEnvironmentIdentifierSchema,
|
|
5944
|
-
collection_id:
|
|
5945
|
-
string:
|
|
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:
|
|
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:
|
|
5952
|
-
virtual_key:
|
|
5953
|
-
model:
|
|
5954
|
-
version_description:
|
|
5955
|
-
template_metadata:
|
|
5956
|
-
functions:
|
|
5957
|
-
tools:
|
|
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:
|
|
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:
|
|
5965
|
-
target_collection_id:
|
|
5966
|
-
target_name:
|
|
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:
|
|
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:
|
|
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:
|
|
5979
|
-
feature:
|
|
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:
|
|
5983
|
-
version_id:
|
|
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:
|
|
5987
|
-
version_id:
|
|
5988
|
-
label_id:
|
|
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
|
|
6701
|
+
import { z as z17 } from "zod";
|
|
6579
6702
|
var PROVIDERS_TOOL_SCHEMAS = {
|
|
6580
6703
|
listProviders: {
|
|
6581
|
-
current_page:
|
|
6582
|
-
page_size:
|
|
6583
|
-
workspace_id:
|
|
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:
|
|
6589
|
-
integration_id:
|
|
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:
|
|
6715
|
+
workspace_id: z17.string().optional().describe(
|
|
6593
6716
|
"Workspace ID - required when using organization admin API keys"
|
|
6594
6717
|
),
|
|
6595
|
-
slug:
|
|
6718
|
+
slug: z17.string().optional().describe(
|
|
6596
6719
|
"Custom slug for the provider. Auto-generated with random suffix if omitted"
|
|
6597
6720
|
),
|
|
6598
|
-
note:
|
|
6599
|
-
credit_limit:
|
|
6600
|
-
alert_threshold:
|
|
6601
|
-
usage_limit_type:
|
|
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:
|
|
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:
|
|
6608
|
-
rate_limit_unit:
|
|
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:
|
|
6734
|
+
expires_at: z17.string().optional().describe("Expiration date in ISO 8601 format")
|
|
6612
6735
|
},
|
|
6613
6736
|
getProvider: {
|
|
6614
|
-
slug:
|
|
6615
|
-
workspace_id:
|
|
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:
|
|
6619
|
-
workspace_id:
|
|
6620
|
-
name:
|
|
6621
|
-
note:
|
|
6622
|
-
credit_limit:
|
|
6623
|
-
alert_threshold:
|
|
6624
|
-
usage_limit_type:
|
|
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:
|
|
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:
|
|
6631
|
-
rate_limit_unit:
|
|
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:
|
|
6635
|
-
reset_usage:
|
|
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:
|
|
6639
|
-
workspace_id:
|
|
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/
|
|
6821
|
-
import { z as
|
|
6822
|
-
var
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
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
|
-
|
|
6828
|
-
|
|
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
|
-
|
|
6831
|
-
|
|
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
|
-
|
|
6834
|
-
|
|
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
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
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
|
-
|
|
6843
|
-
|
|
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
|
|
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
|
-
"
|
|
6849
|
-
"Create
|
|
6850
|
-
|
|
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
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
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
|
|
6864
|
-
|
|
6865
|
-
|
|
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
|
-
"
|
|
6874
|
-
"
|
|
6875
|
-
|
|
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.
|
|
6878
|
-
|
|
6879
|
-
|
|
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
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
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
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
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
|
|
7715
|
+
import { z as z21 } from "zod";
|
|
7196
7716
|
var WORKSPACES_TOOL_SCHEMAS = {
|
|
7197
7717
|
listWorkspaces: {
|
|
7198
|
-
page_size:
|
|
7199
|
-
current_page:
|
|
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:
|
|
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:
|
|
7208
|
-
slug:
|
|
7209
|
-
description:
|
|
7210
|
-
is_default:
|
|
7211
|
-
metadata:
|
|
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:
|
|
7215
|
-
name:
|
|
7216
|
-
slug:
|
|
7217
|
-
description:
|
|
7218
|
-
is_default:
|
|
7219
|
-
metadata:
|
|
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:
|
|
7742
|
+
workspace_id: z21.string().describe("The workspace ID to delete")
|
|
7223
7743
|
},
|
|
7224
7744
|
addWorkspaceMember: {
|
|
7225
|
-
workspace_id:
|
|
7226
|
-
user_id:
|
|
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:
|
|
7751
|
+
role: z21.enum(["admin", "member", "manager"]).describe("Role in the workspace")
|
|
7232
7752
|
},
|
|
7233
7753
|
listWorkspaceMembers: {
|
|
7234
|
-
workspace_id:
|
|
7754
|
+
workspace_id: z21.string().describe("The workspace ID to list members for")
|
|
7235
7755
|
},
|
|
7236
7756
|
getWorkspaceMember: {
|
|
7237
|
-
workspace_id:
|
|
7238
|
-
user_id:
|
|
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:
|
|
7242
|
-
user_id:
|
|
7243
|
-
role:
|
|
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:
|
|
7247
|
-
user_id:
|
|
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:
|
|
7657
|
-
data:
|
|
7658
|
-
error:
|
|
7659
|
-
message:
|
|
7660
|
-
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:
|
|
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
|
|
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 =
|
|
7960
|
-
const right =
|
|
7961
|
-
return
|
|
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() ||
|
|
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
|
|
8045
|
-
|
|
8046
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 || "
|
|
8078
|
-
const eventStoreTtlSeconds =
|
|
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
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
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(
|
|
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
|
-
|
|
8198
|
-
|
|
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
|
-
|
|
8246
|
-
|
|
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({
|
|
8264
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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(
|
|
9222
|
+
const baseScore = Number(baseEvent.sequence);
|
|
8331
9223
|
if (!Number.isFinite(baseScore)) {
|
|
8332
|
-
throw new Error(`Invalid replay event
|
|
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.
|
|
9236
|
+
tx.hGetAll(this.eventKey(eventId));
|
|
8345
9237
|
}
|
|
8346
|
-
const
|
|
9238
|
+
const encodedEvents = await tx.exec();
|
|
8347
9239
|
for (const [index, eventId] of eventIds.entries()) {
|
|
8348
|
-
const
|
|
8349
|
-
if (typeof
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
8484
|
-
area:
|
|
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
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8770
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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: () =>
|
|
9442
|
-
eventStore: managedEventStore.
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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: {
|
|
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" : "
|
|
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
|
|
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`
|