portkey-admin-mcp 0.8.0 โ 0.10.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 +20 -1
- package/build/index.js +178 -157
- package/build/server.js +358 -254
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
16
16
|
<br>
|
|
17
17
|
<a href="https://github.com/CodesWhat/portkey-admin-mcp/actions/workflows/ci.yml"><img src="https://github.com/CodesWhat/portkey-admin-mcp/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
18
|
+
<a href="https://securityscorecards.dev/viewer/?uri=github.com/CodesWhat/portkey-admin-mcp"><img src="https://img.shields.io/ossf-scorecard/github.com/CodesWhat/portkey-admin-mcp?label=openssf+scorecard&style=flat" alt="OpenSSF Scorecard"></a>
|
|
19
|
+
<a href="https://www.bestpractices.dev/projects/14031"><img src="https://www.bestpractices.dev/projects/14031/badge" alt="OpenSSF Best Practices"></a>
|
|
18
20
|
<a href="https://github.com/punkpeye/awesome-mcp-servers"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome MCP Servers"></a>
|
|
19
21
|
<a href="https://lobehub.com/mcp/codeswhat-portkey-admin-mcp"><img src="https://lobehub.com/badge/mcp/codeswhat-portkey-admin-mcp" alt="LobeHub MCP"></a>
|
|
20
22
|
</p>
|
|
@@ -34,8 +36,13 @@
|
|
|
34
36
|
- [๐งฐ What You Can Do](#what-you-can-do)
|
|
35
37
|
- [๐ API Key Scopes](#api-key-scopes)
|
|
36
38
|
- [๐ HTTP Server (Experimental)](#http-server)
|
|
39
|
+
- [๐๏ธ Architecture](./docs/ARCHITECTURE.md)
|
|
37
40
|
- [๐ Prisma AIRS interoperability](./docs/PRISMA_AIRS_INTEROPERABILITY.md)
|
|
41
|
+
- [๐ Verify a release](./docs/VERIFY_RELEASE.md)
|
|
38
42
|
- [๐ ๏ธ Development](#development)
|
|
43
|
+
- [๐ค Contributing](./CONTRIBUTING.md)
|
|
44
|
+
- [๐๏ธ Governance](./GOVERNANCE.md)
|
|
45
|
+
- [๐ก๏ธ Security assurance](./SECURITY-ASSURANCE.md)
|
|
39
46
|
- [๐งพ Full tool list โ ENDPOINTS.md](./ENDPOINTS.md)
|
|
40
47
|
|
|
41
48
|
<hr>
|
|
@@ -219,6 +226,7 @@ For local-only HTTP use, leave `MCP_HOST` at its default `127.0.0.1`. Set `MCP_H
|
|
|
219
226
|
| `MCP_MAX_SESSIONS` | `100` | Maximum concurrent stateful sessions or active stateless request handlers |
|
|
220
227
|
| `MCP_EVENT_STORE` | `off` | `off`, `memory`, or `redis`; stateless `GET /mcp` replay requires `memory` or `redis` |
|
|
221
228
|
| `MCP_EVENT_TTL_SECONDS` | `300` | Replay retention in seconds |
|
|
229
|
+
| `MCP_EVENT_STORE_COMMAND_TIMEOUT_MS` | `5000` | Redis command timeout for the event store, in milliseconds; `0` disables the timeout (restores unbounded pre-v6 behavior) |
|
|
222
230
|
| `MCP_REDIS_URL` | โ | Redis URL for shared event store; production requires `rediss://` and ACL-scoped credentials |
|
|
223
231
|
| `MCP_EVENT_ENCRYPTION_KEY` | โ | Required 32-byte base64 AES key for Redis replay payloads; generate with `openssl rand -base64 32` |
|
|
224
232
|
| `MCP_REDIS_KEY_PREFIX` | `mcp:event-store` | Dedicated Redis namespace for replay data |
|
|
@@ -283,13 +291,24 @@ docker run \
|
|
|
283
291
|
npm run dev # stdio with hot reload
|
|
284
292
|
npm run dev:http # HTTP with hot reload
|
|
285
293
|
npm test # unit + contract tests
|
|
294
|
+
npm run test:coverage # unit + contract tests with the enforced 80% line floor
|
|
286
295
|
npm run test:e2e # MCP protocol tests
|
|
287
296
|
npm run test:http # HTTP endpoint smoke test
|
|
288
|
-
npm run ci # full pipeline (lint + typecheck +
|
|
297
|
+
npm run ci # full pipeline (lint + typecheck + coverage + build + e2e + verify)
|
|
289
298
|
```
|
|
290
299
|
|
|
300
|
+
The required CI and release gates measure every TypeScript source file and fail
|
|
301
|
+
below 80% line coverage. The current full report is 99.36% lines, 92.61%
|
|
302
|
+
branches, and 99.01% functions.
|
|
303
|
+
|
|
291
304
|
`npm run dev:http` now requires `MCP_AUTH_MODE=bearer` or `MCP_AUTH_MODE=clerk` by default. For deliberate local-only unauthenticated testing, set `MCP_ALLOW_UNAUTHENTICATED_HTTP=true`.
|
|
292
305
|
|
|
306
|
+
Contributions use pull requests and the checks documented in
|
|
307
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md). Project decisions and maintainer roles are
|
|
308
|
+
documented in [`GOVERNANCE.md`](GOVERNANCE.md). Report vulnerabilities through
|
|
309
|
+
[`SECURITY.md`](SECURITY.md), and see [`SECURITY-ASSURANCE.md`](SECURITY-ASSURANCE.md)
|
|
310
|
+
for the public threat model and assurance case.
|
|
311
|
+
|
|
293
312
|
---
|
|
294
313
|
|
|
295
314
|
<div align="center">
|
package/build/index.js
CHANGED
|
@@ -3,6 +3,59 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
|
+
// src/lib/logger.ts
|
|
7
|
+
var LOG_LEVELS = {
|
|
8
|
+
debug: 0,
|
|
9
|
+
info: 1,
|
|
10
|
+
warn: 2,
|
|
11
|
+
error: 3
|
|
12
|
+
};
|
|
13
|
+
function getConfiguredLogLevel() {
|
|
14
|
+
const envLevel = process.env.LOG_LEVEL?.toLowerCase();
|
|
15
|
+
if (envLevel && envLevel in LOG_LEVELS) {
|
|
16
|
+
return envLevel;
|
|
17
|
+
}
|
|
18
|
+
return "info";
|
|
19
|
+
}
|
|
20
|
+
var configuredLogLevel = getConfiguredLogLevel();
|
|
21
|
+
function shouldLog(level) {
|
|
22
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[configuredLogLevel];
|
|
23
|
+
}
|
|
24
|
+
function formatEntry(level, message, extra) {
|
|
25
|
+
return {
|
|
26
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27
|
+
level,
|
|
28
|
+
message,
|
|
29
|
+
...extra
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function writeLog(entry) {
|
|
33
|
+
process.stderr.write(`${JSON.stringify(entry)}
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
var Logger = {
|
|
37
|
+
debug(message, extra) {
|
|
38
|
+
if (shouldLog("debug")) {
|
|
39
|
+
writeLog(formatEntry("debug", message, extra));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
info(message, extra) {
|
|
43
|
+
if (shouldLog("info")) {
|
|
44
|
+
writeLog(formatEntry("info", message, extra));
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
warn(message, extra) {
|
|
48
|
+
if (shouldLog("warn")) {
|
|
49
|
+
writeLog(formatEntry("warn", message, extra));
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
error(message, extra) {
|
|
53
|
+
if (shouldLog("error")) {
|
|
54
|
+
writeLog(formatEntry("error", message, extra));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
6
59
|
// src/lib/mcp-server.ts
|
|
7
60
|
import { readFileSync } from "node:fs";
|
|
8
61
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -64,62 +117,10 @@ async function parseErrorResponse(response) {
|
|
|
64
117
|
}
|
|
65
118
|
}
|
|
66
119
|
|
|
67
|
-
// src/lib/logger.ts
|
|
68
|
-
var LOG_LEVELS = {
|
|
69
|
-
debug: 0,
|
|
70
|
-
info: 1,
|
|
71
|
-
warn: 2,
|
|
72
|
-
error: 3
|
|
73
|
-
};
|
|
74
|
-
function getConfiguredLogLevel() {
|
|
75
|
-
const envLevel = process.env.LOG_LEVEL?.toLowerCase();
|
|
76
|
-
if (envLevel && envLevel in LOG_LEVELS) {
|
|
77
|
-
return envLevel;
|
|
78
|
-
}
|
|
79
|
-
return "info";
|
|
80
|
-
}
|
|
81
|
-
var configuredLogLevel = getConfiguredLogLevel();
|
|
82
|
-
function shouldLog(level) {
|
|
83
|
-
return LOG_LEVELS[level] >= LOG_LEVELS[configuredLogLevel];
|
|
84
|
-
}
|
|
85
|
-
function formatEntry(level, message, extra) {
|
|
86
|
-
return {
|
|
87
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
88
|
-
level,
|
|
89
|
-
message,
|
|
90
|
-
...extra
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
function writeLog(entry) {
|
|
94
|
-
process.stderr.write(`${JSON.stringify(entry)}
|
|
95
|
-
`);
|
|
96
|
-
}
|
|
97
|
-
var Logger = {
|
|
98
|
-
debug(message, extra) {
|
|
99
|
-
if (shouldLog("debug")) {
|
|
100
|
-
writeLog(formatEntry("debug", message, extra));
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
info(message, extra) {
|
|
104
|
-
if (shouldLog("info")) {
|
|
105
|
-
writeLog(formatEntry("info", message, extra));
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
warn(message, extra) {
|
|
109
|
-
if (shouldLog("warn")) {
|
|
110
|
-
writeLog(formatEntry("warn", message, extra));
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
error(message, extra) {
|
|
114
|
-
if (shouldLog("error")) {
|
|
115
|
-
writeLog(formatEntry("error", message, extra));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
120
|
// src/services/base.service.ts
|
|
121
121
|
var DEFAULT_BASE_URL = "https://api.portkey.ai/v1";
|
|
122
122
|
var DEFAULT_PUBLIC_BASE_URL = "https://api.portkey.ai";
|
|
123
|
+
var MISSING_API_KEY_PLACEHOLDER = "__PORTKEY_API_KEY_NOT_CONFIGURED__";
|
|
123
124
|
var PRIVATE_BASE_URL_OVERRIDE_HINT = "Set PORTKEY_ALLOW_PRIVATE_BASE_URL=true to allow self-hosted gateways on loopback or private networks.";
|
|
124
125
|
var INSECURE_HTTP_OVERRIDE_HINT = "Set PORTKEY_ALLOW_INSECURE_HTTP=true only for a trusted self-hosted gateway when TLS is unavailable.";
|
|
125
126
|
function isPrivateOrLocalHost(hostname) {
|
|
@@ -233,6 +234,11 @@ var BaseService = class {
|
|
|
233
234
|
return body ? JSON.stringify(body) : void 0;
|
|
234
235
|
}
|
|
235
236
|
async executeRequest(method, path, options = {}) {
|
|
237
|
+
if ((options.authenticate ?? true) && this.apiKey === MISSING_API_KEY_PLACEHOLDER) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
"PORTKEY_API_KEY is not configured. Set the PORTKEY_API_KEY environment variable to a valid Portkey Admin API key."
|
|
240
|
+
);
|
|
241
|
+
}
|
|
236
242
|
const requestId = crypto.randomUUID();
|
|
237
243
|
const url = this.buildUrl(path, options.params, options.baseUrl);
|
|
238
244
|
const startTime = Date.now();
|
|
@@ -1795,7 +1801,6 @@ var WorkspacesService = class extends BaseService {
|
|
|
1795
1801
|
|
|
1796
1802
|
// src/services/index.ts
|
|
1797
1803
|
import { createHmac, randomBytes } from "node:crypto";
|
|
1798
|
-
var MISSING_API_KEY_PLACEHOLDER = "__PORTKEY_API_KEY_NOT_CONFIGURED__";
|
|
1799
1804
|
var SERVICE_CACHE_HMAC_KEY = randomBytes(32);
|
|
1800
1805
|
function resolvePortkeyApiKey(apiKey) {
|
|
1801
1806
|
const resolvedApiKey = apiKey ?? process.env.PORTKEY_API_KEY;
|
|
@@ -2684,6 +2689,14 @@ function buildConfigPayload(params) {
|
|
|
2684
2689
|
function hasConfigSettings(config) {
|
|
2685
2690
|
return config.cache !== void 0 || config.retry !== void 0 || config.strategy !== void 0 || config.targets !== void 0;
|
|
2686
2691
|
}
|
|
2692
|
+
var createConfigSchema = z5.object(CONFIGS_TOOL_SCHEMAS.createConfig).superRefine((value, ctx) => {
|
|
2693
|
+
if (!hasConfigSettings(buildConfigPayload(value))) {
|
|
2694
|
+
ctx.addIssue({
|
|
2695
|
+
code: z5.ZodIssueCode.custom,
|
|
2696
|
+
message: "At least one config setting (cache_mode, retry_attempts, strategy_mode, or targets) must be provided"
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
});
|
|
2687
2700
|
function registerConfigsTools(server, service) {
|
|
2688
2701
|
server.tool(
|
|
2689
2702
|
"list_configs",
|
|
@@ -2749,25 +2762,15 @@ function registerConfigsTools(server, service) {
|
|
|
2749
2762
|
"Create a config that defines routing, cache, retry, and targets for requests; use update_config to modify an existing one and list_config_versions for history. At least one setting is required, new configs become active immediately once referenced by a key or prompt, and the call returns the new id and version_id.",
|
|
2750
2763
|
CONFIGS_TOOL_SCHEMAS.createConfig,
|
|
2751
2764
|
async (params) => {
|
|
2752
|
-
const
|
|
2753
|
-
|
|
2754
|
-
return {
|
|
2755
|
-
content: [
|
|
2756
|
-
{
|
|
2757
|
-
type: "text",
|
|
2758
|
-
text: "Error creating configuration: At least one config setting (cache_mode, retry_attempts, strategy_mode, or targets) must be provided"
|
|
2759
|
-
}
|
|
2760
|
-
],
|
|
2761
|
-
isError: true
|
|
2762
|
-
};
|
|
2763
|
-
}
|
|
2765
|
+
const validated = createConfigSchema.parse(params);
|
|
2766
|
+
const config = buildConfigPayload(validated);
|
|
2764
2767
|
const result = await service.configs.createConfig({
|
|
2765
|
-
name:
|
|
2768
|
+
name: validated.name,
|
|
2766
2769
|
config,
|
|
2767
|
-
workspace_id:
|
|
2770
|
+
workspace_id: validated.workspace_id
|
|
2768
2771
|
});
|
|
2769
2772
|
return jsonResult({
|
|
2770
|
-
message: `Successfully created configuration "${
|
|
2773
|
+
message: `Successfully created configuration "${validated.name}"`,
|
|
2771
2774
|
id: result.id,
|
|
2772
2775
|
version_id: result.version_id
|
|
2773
2776
|
});
|
|
@@ -3396,6 +3399,16 @@ var INTEGRATIONS_TOOL_SCHEMAS = {
|
|
|
3396
3399
|
default_provider_slug: z8.string().optional().describe("Default slug for providers auto-created in workspaces")
|
|
3397
3400
|
}
|
|
3398
3401
|
};
|
|
3402
|
+
var updateIntegrationWorkspacesSchema = z8.object(INTEGRATIONS_TOOL_SCHEMAS.updateIntegrationWorkspaces).superRefine((value, ctx) => {
|
|
3403
|
+
const hasGlobalLimits = value.global_credit_limit !== void 0 || value.global_alert_threshold !== void 0 || value.global_rate_limit_rpm !== void 0;
|
|
3404
|
+
if (hasGlobalLimits && value.global_workspace_access_enabled === void 0) {
|
|
3405
|
+
ctx.addIssue({
|
|
3406
|
+
code: z8.ZodIssueCode.custom,
|
|
3407
|
+
path: ["global_workspace_access_enabled"],
|
|
3408
|
+
message: "global_workspace_access_enabled is required when setting global limits."
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
});
|
|
3399
3412
|
function buildIntegrationConfigurations(params) {
|
|
3400
3413
|
const configurations = {};
|
|
3401
3414
|
if (params.api_version !== void 0)
|
|
@@ -3673,19 +3686,8 @@ function registerIntegrationsTools(server, service) {
|
|
|
3673
3686
|
idempotentHint: true,
|
|
3674
3687
|
openWorldHint: true
|
|
3675
3688
|
},
|
|
3676
|
-
async (
|
|
3677
|
-
const
|
|
3678
|
-
if (hasGlobalLimits && params.global_workspace_access_enabled === void 0) {
|
|
3679
|
-
return {
|
|
3680
|
-
content: [
|
|
3681
|
-
{
|
|
3682
|
-
type: "text",
|
|
3683
|
-
text: "global_workspace_access_enabled is required when setting global limits."
|
|
3684
|
-
}
|
|
3685
|
-
],
|
|
3686
|
-
isError: true
|
|
3687
|
-
};
|
|
3688
|
-
}
|
|
3689
|
+
async (rawParams) => {
|
|
3690
|
+
const params = updateIntegrationWorkspacesSchema.parse(rawParams);
|
|
3689
3691
|
const globalUsageLimits = params.global_credit_limit !== void 0 || params.global_alert_threshold !== void 0 ? [
|
|
3690
3692
|
{
|
|
3691
3693
|
type: "cost",
|
|
@@ -4163,23 +4165,21 @@ var LABELS_TOOL_SCHEMAS = {
|
|
|
4163
4165
|
label_id: z10.string().describe("Label ID to delete")
|
|
4164
4166
|
}
|
|
4165
4167
|
};
|
|
4168
|
+
var createPromptLabelSchema = z10.object(LABELS_TOOL_SCHEMAS.createPromptLabel).superRefine((value, ctx) => {
|
|
4169
|
+
if (!value.organisation_id && !value.workspace_id) {
|
|
4170
|
+
ctx.addIssue({
|
|
4171
|
+
code: z10.ZodIssueCode.custom,
|
|
4172
|
+
message: "Either organisation_id or workspace_id is required"
|
|
4173
|
+
});
|
|
4174
|
+
}
|
|
4175
|
+
});
|
|
4166
4176
|
function registerLabelsTools(server, service) {
|
|
4167
4177
|
server.tool(
|
|
4168
4178
|
"create_prompt_label",
|
|
4169
4179
|
"Create a prompt label for tagging prompt versions such as production, staging, or experiment. Requires either organisation_id or workspace_id to set scope, returns the new label id, and does not assign it to any versions yet.",
|
|
4170
4180
|
LABELS_TOOL_SCHEMAS.createPromptLabel,
|
|
4171
|
-
async (
|
|
4172
|
-
|
|
4173
|
-
return {
|
|
4174
|
-
content: [
|
|
4175
|
-
{
|
|
4176
|
-
type: "text",
|
|
4177
|
-
text: "Error: Either organisation_id or workspace_id is required"
|
|
4178
|
-
}
|
|
4179
|
-
],
|
|
4180
|
-
isError: true
|
|
4181
|
-
};
|
|
4182
|
-
}
|
|
4181
|
+
async (rawParams) => {
|
|
4182
|
+
const params = createPromptLabelSchema.parse(rawParams);
|
|
4183
4183
|
const result = await service.labels.createLabel({
|
|
4184
4184
|
name: params.name,
|
|
4185
4185
|
organisation_id: params.organisation_id,
|
|
@@ -4589,12 +4589,13 @@ var logExportFieldSchema = z12.enum([
|
|
|
4589
4589
|
"prompt_slug",
|
|
4590
4590
|
"metadata"
|
|
4591
4591
|
]);
|
|
4592
|
-
var
|
|
4592
|
+
var getLogShape = {
|
|
4593
4593
|
log_id: z12.string().describe("Gateway log ID to retrieve"),
|
|
4594
4594
|
path_format: z12.enum(["v1", "v2"]).optional().describe("Log storage path format; defaults to v1"),
|
|
4595
4595
|
created_at: z12.iso.datetime({ offset: true }).optional().describe("Log creation timestamp in ISO 8601 format; required for v2"),
|
|
4596
4596
|
type: z12.enum(["hooks"]).optional().describe("Set to hooks when retrieving a hook execution log")
|
|
4597
|
-
}
|
|
4597
|
+
};
|
|
4598
|
+
var getLogSchema = z12.object(getLogShape).superRefine((input, context) => {
|
|
4598
4599
|
if (input.path_format === "v2" && input.created_at === void 0) {
|
|
4599
4600
|
context.addIssue({
|
|
4600
4601
|
code: "custom",
|
|
@@ -4675,7 +4676,7 @@ function registerLoggingTools(server, service) {
|
|
|
4675
4676
|
"get_log",
|
|
4676
4677
|
{
|
|
4677
4678
|
description: "Get one gateway request log by ID, including its request, response, usage, cost, and metadata payload when available. For path_format v2, also provide the log's created_at timestamp.",
|
|
4678
|
-
inputSchema:
|
|
4679
|
+
inputSchema: getLogShape,
|
|
4679
4680
|
annotations: {
|
|
4680
4681
|
title: "Get Gateway Log",
|
|
4681
4682
|
readOnlyHint: true,
|
|
@@ -4684,7 +4685,8 @@ function registerLoggingTools(server, service) {
|
|
|
4684
4685
|
openWorldHint: true
|
|
4685
4686
|
}
|
|
4686
4687
|
},
|
|
4687
|
-
async (
|
|
4688
|
+
async (rawParams) => {
|
|
4689
|
+
const params = getLogSchema.parse(rawParams);
|
|
4688
4690
|
const result = await service.logging.getLog(params.log_id, {
|
|
4689
4691
|
path_format: params.path_format,
|
|
4690
4692
|
created_at: params.created_at,
|
|
@@ -4976,6 +4978,17 @@ var MCP_INTEGRATIONS_TOOL_SCHEMAS = {
|
|
|
4976
4978
|
).min(1).describe("Array of workspace access updates")
|
|
4977
4979
|
}
|
|
4978
4980
|
};
|
|
4981
|
+
var createMcpIntegrationSchema = z13.object(MCP_INTEGRATIONS_TOOL_SCHEMAS.createMcpIntegration).superRefine((value, ctx) => {
|
|
4982
|
+
if (value.auth_type === "headers" && (!value.custom_headers || Object.keys(value.custom_headers).length === 0) && !value.secret_mappings?.some(
|
|
4983
|
+
(mapping) => mapping.target_field === "configurations.custom_headers"
|
|
4984
|
+
)) {
|
|
4985
|
+
ctx.addIssue({
|
|
4986
|
+
code: z13.ZodIssueCode.custom,
|
|
4987
|
+
path: ["custom_headers"],
|
|
4988
|
+
message: "custom_headers must be provided when auth_type is 'headers'"
|
|
4989
|
+
});
|
|
4990
|
+
}
|
|
4991
|
+
});
|
|
4979
4992
|
function isRecord2(value) {
|
|
4980
4993
|
return typeof value === "object" && value !== null;
|
|
4981
4994
|
}
|
|
@@ -5054,20 +5067,8 @@ function registerMcpIntegrationsTools(server, service) {
|
|
|
5054
5067
|
idempotentHint: false,
|
|
5055
5068
|
openWorldHint: true
|
|
5056
5069
|
},
|
|
5057
|
-
async (
|
|
5058
|
-
|
|
5059
|
-
(mapping) => mapping.target_field === "configurations.custom_headers"
|
|
5060
|
-
)) {
|
|
5061
|
-
return {
|
|
5062
|
-
content: [
|
|
5063
|
-
{
|
|
5064
|
-
type: "text",
|
|
5065
|
-
text: "Error: custom_headers must be provided when auth_type is 'headers'"
|
|
5066
|
-
}
|
|
5067
|
-
],
|
|
5068
|
-
isError: true
|
|
5069
|
-
};
|
|
5070
|
-
}
|
|
5070
|
+
async (rawParams) => {
|
|
5071
|
+
const params = createMcpIntegrationSchema.parse(rawParams);
|
|
5071
5072
|
const { custom_headers, ...rest } = params;
|
|
5072
5073
|
const result = await service.mcpIntegrations.createMcpIntegration({
|
|
5073
5074
|
...rest,
|
|
@@ -5904,6 +5905,28 @@ var PROMPTS_TOOL_SCHEMAS = {
|
|
|
5904
5905
|
)
|
|
5905
5906
|
}
|
|
5906
5907
|
};
|
|
5908
|
+
var createPromptSchema = z17.object(PROMPTS_TOOL_SCHEMAS.createPrompt).superRefine((value, ctx) => {
|
|
5909
|
+
if (!value.model && !value.ai_model_id && !value.finetune_id) {
|
|
5910
|
+
ctx.addIssue({
|
|
5911
|
+
code: z17.ZodIssueCode.custom,
|
|
5912
|
+
message: "At least one of model, ai_model_id, or finetune_id must be provided"
|
|
5913
|
+
});
|
|
5914
|
+
}
|
|
5915
|
+
if (value.string === void 0 && value.messages === void 0) {
|
|
5916
|
+
ctx.addIssue({
|
|
5917
|
+
code: z17.ZodIssueCode.custom,
|
|
5918
|
+
message: "Provide either string or messages"
|
|
5919
|
+
});
|
|
5920
|
+
}
|
|
5921
|
+
});
|
|
5922
|
+
var migratePromptSchema = z17.object(PROMPTS_TOOL_SCHEMAS.migratePrompt).superRefine((value, ctx) => {
|
|
5923
|
+
if (value.string === void 0 && value.messages === void 0) {
|
|
5924
|
+
ctx.addIssue({
|
|
5925
|
+
code: z17.ZodIssueCode.custom,
|
|
5926
|
+
message: "Provide either string or messages"
|
|
5927
|
+
});
|
|
5928
|
+
}
|
|
5929
|
+
});
|
|
5907
5930
|
function normalizePromptTemplateString(params) {
|
|
5908
5931
|
if (params.string !== void 0) {
|
|
5909
5932
|
return params.string;
|
|
@@ -5978,30 +6001,9 @@ function registerPromptsTools(server, service) {
|
|
|
5978
6001
|
"create_prompt",
|
|
5979
6002
|
"Create a new prompt template and initial version. Use this for first-time setup; use migrate_prompt for idempotent CI/CD flows. Accepts plain text or structured chat messages, creates a new version immediately, and returns the prompt id, slug, and version id. For multi-message chat prompts pass messages (preferred) or a JSON-encoded array as string.",
|
|
5980
6003
|
PROMPTS_TOOL_SCHEMAS.createPrompt,
|
|
5981
|
-
async (
|
|
5982
|
-
|
|
5983
|
-
return {
|
|
5984
|
-
content: [
|
|
5985
|
-
{
|
|
5986
|
-
type: "text",
|
|
5987
|
-
text: "Error creating prompt: At least one of model, ai_model_id, or finetune_id must be provided"
|
|
5988
|
-
}
|
|
5989
|
-
],
|
|
5990
|
-
isError: true
|
|
5991
|
-
};
|
|
5992
|
-
}
|
|
6004
|
+
async (rawParams) => {
|
|
6005
|
+
const params = createPromptSchema.parse(rawParams);
|
|
5993
6006
|
const templateString = normalizePromptTemplateString(params);
|
|
5994
|
-
if (templateString === void 0) {
|
|
5995
|
-
return {
|
|
5996
|
-
content: [
|
|
5997
|
-
{
|
|
5998
|
-
type: "text",
|
|
5999
|
-
text: "Error creating prompt: Provide either string or messages"
|
|
6000
|
-
}
|
|
6001
|
-
],
|
|
6002
|
-
isError: true
|
|
6003
|
-
};
|
|
6004
|
-
}
|
|
6005
6007
|
if (params.dry_run) {
|
|
6006
6008
|
return jsonResult({
|
|
6007
6009
|
dry_run: true,
|
|
@@ -6111,14 +6113,18 @@ function registerPromptsTools(server, service) {
|
|
|
6111
6113
|
});
|
|
6112
6114
|
if (dry_run) {
|
|
6113
6115
|
const current = await service.prompts.getPrompt(prompt_id);
|
|
6116
|
+
const changes = Object.keys(updateData).filter(
|
|
6117
|
+
(k) => updateData[k] !== void 0
|
|
6118
|
+
);
|
|
6119
|
+
if (messages !== void 0 && !changes.includes("string")) {
|
|
6120
|
+
changes.push("string");
|
|
6121
|
+
}
|
|
6114
6122
|
return jsonResult({
|
|
6115
6123
|
dry_run: true,
|
|
6116
6124
|
action: "update",
|
|
6117
6125
|
message: `Would update prompt "${current.name}"`,
|
|
6118
6126
|
current_version: current.current_version?.version_number ?? null,
|
|
6119
|
-
changes
|
|
6120
|
-
(k) => updateData[k] !== void 0
|
|
6121
|
-
)
|
|
6127
|
+
changes
|
|
6122
6128
|
});
|
|
6123
6129
|
}
|
|
6124
6130
|
const result = await service.prompts.updatePrompt(prompt_id, {
|
|
@@ -6256,19 +6262,9 @@ function registerPromptsTools(server, service) {
|
|
|
6256
6262
|
idempotentHint: true,
|
|
6257
6263
|
openWorldHint: false
|
|
6258
6264
|
},
|
|
6259
|
-
async (
|
|
6265
|
+
async (rawParams) => {
|
|
6266
|
+
const params = migratePromptSchema.parse(rawParams);
|
|
6260
6267
|
const templateString = normalizePromptTemplateString(params);
|
|
6261
|
-
if (templateString === void 0) {
|
|
6262
|
-
return {
|
|
6263
|
-
content: [
|
|
6264
|
-
{
|
|
6265
|
-
type: "text",
|
|
6266
|
-
text: "Error migrating prompt: Provide either string or messages"
|
|
6267
|
-
}
|
|
6268
|
-
],
|
|
6269
|
-
isError: true
|
|
6270
|
-
};
|
|
6271
|
-
}
|
|
6272
6268
|
const result = await service.prompts.migratePrompt({
|
|
6273
6269
|
name: params.name,
|
|
6274
6270
|
app: params.app,
|
|
@@ -7234,7 +7230,7 @@ var scimWorkspaceMappingBaseShape = {
|
|
|
7234
7230
|
workspace_id: z22.string().describe("Portkey workspace ID that the SCIM group should access"),
|
|
7235
7231
|
role: z22.enum(["admin", "member", "manager"]).describe("Workspace role automatically granted to group members")
|
|
7236
7232
|
};
|
|
7237
|
-
var
|
|
7233
|
+
var createScimWorkspaceMappingShape = {
|
|
7238
7234
|
...scimWorkspaceMappingBaseShape,
|
|
7239
7235
|
scim_group_id: z22.string().min(1).optional().describe(
|
|
7240
7236
|
"Existing identity-provider SCIM group ID; provide this or scim_group_name, but not both"
|
|
@@ -7242,7 +7238,8 @@ var createScimWorkspaceMappingInputSchema = z22.object({
|
|
|
7242
7238
|
scim_group_name: z22.string().min(1).optional().describe(
|
|
7243
7239
|
"Existing SCIM group display name; provide this or scim_group_id, but not both"
|
|
7244
7240
|
)
|
|
7245
|
-
}
|
|
7241
|
+
};
|
|
7242
|
+
var createScimWorkspaceMappingSchema = z22.object(createScimWorkspaceMappingShape).superRefine((input, context) => {
|
|
7246
7243
|
if (Boolean(input.scim_group_id) === Boolean(input.scim_group_name)) {
|
|
7247
7244
|
context.addIssue({
|
|
7248
7245
|
code: "custom",
|
|
@@ -7397,7 +7394,7 @@ function registerWorkspacesTools(server, service) {
|
|
|
7397
7394
|
"create_scim_workspace_mapping",
|
|
7398
7395
|
{
|
|
7399
7396
|
description: "Map one identity-provider SCIM group to a Portkey workspace role so current and future group members receive access automatically. Provide exactly one of scim_group_id or scim_group_name; a name can pre-create the Portkey SCIM group before the IdP provisions it. Use list_scim_groups to discover existing groups and list_workspaces for the workspace ID. This changes access provisioning and is distinct from add_workspace_member, which grants one user directly.",
|
|
7400
|
-
inputSchema:
|
|
7397
|
+
inputSchema: createScimWorkspaceMappingShape,
|
|
7401
7398
|
annotations: {
|
|
7402
7399
|
title: "Create SCIM Workspace Mapping",
|
|
7403
7400
|
readOnlyHint: false,
|
|
@@ -7406,7 +7403,8 @@ function registerWorkspacesTools(server, service) {
|
|
|
7406
7403
|
openWorldHint: true
|
|
7407
7404
|
}
|
|
7408
7405
|
},
|
|
7409
|
-
async (
|
|
7406
|
+
async (rawParams) => {
|
|
7407
|
+
const params = createScimWorkspaceMappingSchema.parse(rawParams);
|
|
7410
7408
|
let request;
|
|
7411
7409
|
if (params.scim_group_id !== void 0) {
|
|
7412
7410
|
request = {
|
|
@@ -7767,7 +7765,25 @@ function augmentToolDescription(toolName, description) {
|
|
|
7767
7765
|
}
|
|
7768
7766
|
return `${description} ${ENTERPRISE_GATED_DESCRIPTION_NOTE}`;
|
|
7769
7767
|
}
|
|
7768
|
+
function formatZodIssues(error) {
|
|
7769
|
+
return error.issues.map((issue) => {
|
|
7770
|
+
const path = issue.path.join(".");
|
|
7771
|
+
if (!path) {
|
|
7772
|
+
return issue.message;
|
|
7773
|
+
}
|
|
7774
|
+
const namesPath = new RegExp(`\\b${escapeRegExp(path)}\\b`).test(
|
|
7775
|
+
issue.message
|
|
7776
|
+
);
|
|
7777
|
+
return namesPath ? issue.message : `${path}: ${issue.message}`;
|
|
7778
|
+
}).join("; ");
|
|
7779
|
+
}
|
|
7780
|
+
function escapeRegExp(value) {
|
|
7781
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7782
|
+
}
|
|
7770
7783
|
function getToolErrorMessage(error) {
|
|
7784
|
+
if (error instanceof z23.ZodError) {
|
|
7785
|
+
return formatZodIssues(error);
|
|
7786
|
+
}
|
|
7771
7787
|
if (error instanceof Error && error.message) {
|
|
7772
7788
|
return error.message;
|
|
7773
7789
|
}
|
|
@@ -7914,14 +7930,14 @@ function wrapToolCallback(toolName, callback) {
|
|
|
7914
7930
|
return normalizeToolResult(await callback(...args));
|
|
7915
7931
|
} catch (error) {
|
|
7916
7932
|
const message = getToolErrorMessage(error);
|
|
7933
|
+
const isValidationError = error instanceof z23.ZodError;
|
|
7934
|
+
const text = isValidationError ? `Invalid arguments for "${toolName}": ${message}` : `Tool "${toolName}" failed: ${message}`;
|
|
7917
7935
|
Logger.error("Tool callback failed", {
|
|
7918
7936
|
error: message,
|
|
7919
7937
|
metadata: { toolName }
|
|
7920
7938
|
});
|
|
7921
7939
|
return normalizeToolResult({
|
|
7922
|
-
content: [
|
|
7923
|
-
{ type: "text", text: `Tool "${toolName}" failed: ${message}` }
|
|
7924
|
-
],
|
|
7940
|
+
content: [{ type: "text", text }],
|
|
7925
7941
|
isError: true
|
|
7926
7942
|
});
|
|
7927
7943
|
}
|
|
@@ -8233,6 +8249,11 @@ function createMcpServer(options = {}) {
|
|
|
8233
8249
|
// src/index.ts
|
|
8234
8250
|
async function main() {
|
|
8235
8251
|
try {
|
|
8252
|
+
if (!process.env.PORTKEY_API_KEY) {
|
|
8253
|
+
Logger.warn(
|
|
8254
|
+
"PORTKEY_API_KEY is not set; tool calls will fail until it is configured."
|
|
8255
|
+
);
|
|
8256
|
+
}
|
|
8236
8257
|
const { server } = createMcpServer();
|
|
8237
8258
|
const transport = new StdioServerTransport();
|
|
8238
8259
|
await server.connect(transport);
|