@promptlayer/mcp-server 1.2.0 → 1.3.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/src/types.ts CHANGED
@@ -228,9 +228,6 @@ export const CreateDatasetVersionFromFileArgsSchema = z.object({
228
228
  export const CreateDatasetVersionFromFilterParamsArgsSchema = z.object({
229
229
  dataset_group_id: z.number().int().describe("Dataset group ID"),
230
230
  variables_to_parse: z.array(z.string()).optional().describe("Variables to extract from request logs"),
231
- prompt_id: z.number().int().optional().describe("Filter by prompt template ID"),
232
- prompt_version_id: z.number().int().optional().describe("Filter by prompt version ID"),
233
- prompt_label_id: z.number().int().optional().describe("Filter by prompt label ID"),
234
231
  workspace_id: z.number().int().optional().describe("Workspace ID"),
235
232
  tags: z.array(z.string()).optional().describe("Filter by tags (simple tag filter)"),
236
233
  metadata: z.record(z.string()).optional().describe("Simple metadata key-value filter"),
@@ -251,7 +248,7 @@ export const CreateDatasetVersionFromFilterParamsArgsSchema = z.object({
251
248
  name: z.string().describe("Prompt template name"),
252
249
  version_numbers: z.array(z.number().int()).optional().describe("Filter to specific version numbers"),
253
250
  labels: z.array(z.string()).optional().describe("Filter to specific labels"),
254
- })).optional().describe("Include request logs matching these prompt templates"),
251
+ })).optional().describe("Include request logs matching these prompt templates. This is the primary way to filter by prompt — use the prompt name (not ID)."),
255
252
  prompt_templates_exclude: z.array(z.object({
256
253
  name: z.string().describe("Prompt template name"),
257
254
  version_numbers: z.array(z.number().int()).optional().describe("Filter to specific version numbers"),
@@ -423,6 +420,15 @@ export const GetWorkflowVersionExecutionResultsArgsSchema = z.object({
423
420
  // ── Get Agent (GET /workflows/{workflow_id_or_name}) ─────────────────────
424
421
 
425
422
  export const GetWorkflowArgsSchema = z.object({
423
+ workflow_id_or_name: z.string().describe("Agent ID or name"),
424
+ version: z.number().int().optional().describe("Specific version number (mutually exclusive with label)"),
425
+ label: z.string().optional().describe("Release label name, e.g. 'prod' (mutually exclusive with version)"),
426
+ api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
427
+ });
428
+
429
+ // ── Get Agent Labels (GET /workflows/{workflow_id_or_name}/labels) ────────
430
+
431
+ export const GetWorkflowLabelsArgsSchema = z.object({
426
432
  workflow_id_or_name: z.string().describe("Agent ID or name"),
427
433
  api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
428
434
  });
@@ -807,10 +813,16 @@ export const TOOL_DEFINITIONS = {
807
813
  },
808
814
  "get-workflow": {
809
815
  name: "get-workflow",
810
- description: "Get a single agent (called 'workflow' in the API) by ID or name. Returns the agent details and configuration.",
816
+ description: "Get a single agent (called 'workflow' in the API) by ID or name. Returns the agent details including full node configuration, edges, and version info. Optionally filter by version number or release label.",
811
817
  inputSchema: GetWorkflowArgsSchema,
812
818
  annotations: { readOnlyHint: true },
813
819
  },
820
+ "get-workflow-labels": {
821
+ name: "get-workflow-labels",
822
+ description: "List all release labels for an agent (workflow). Returns each label with its name, ID, and the version it points to.",
823
+ inputSchema: GetWorkflowLabelsArgsSchema,
824
+ annotations: { readOnlyHint: true },
825
+ },
814
826
 
815
827
  // ── Folders ─────────────────────────────────────────────────────────
816
828
  "create-folder": {
@@ -1,277 +0,0 @@
1
- /**
2
- * Compares openapi-endpoints.json (source of truth) against mcp-tools.json
3
- * (what our MCP implements) and reports discrepancies.
4
- *
5
- * Usage: npx tsx scripts/diff-endpoints.ts
6
- *
7
- * Exit code 0 = in sync (or all issues are known exceptions), 1 = new issues found.
8
- */
9
-
10
- import { readFileSync } from "fs";
11
-
12
- const OPENAPI_PATH = new URL("./openapi-endpoints.json", import.meta.url).pathname;
13
- const MCP_PATH = new URL("./mcp-tools.json", import.meta.url).pathname;
14
-
15
- // ─── Known exceptions ────────────────────────────────────────────────────────
16
- // Each string is matched exactly against an issue line. When ALL remaining
17
- // issues are in this list, the script exits 0 (pass). Add a comment for each
18
- // explaining WHY it's expected so a future reader/agent understands.
19
- const KNOWN_EXCEPTIONS: string[] = [
20
- // update-report-score-card is documented in the PromptLayer reference text
21
- // (https://docs.promptlayer.com/reference/update-report-score-card) but the
22
- // endpoint is not present in the OpenAPI spec (openapi.json). We implement
23
- // it because users need it; it will resolve when PromptLayer adds it to the spec.
24
- "EXTRA IN MCP (not in OpenAPI): PATCH /reports/{report_id}/score-card (tool: update-report-score-card)",
25
-
26
- // These endpoints exist in the OpenAPI spec but were intentionally removed from the MCP server.
27
- // track-prompt, track-score, track-metadata, and track-group are redundant with log-request
28
- // which accepts all tracking fields inline. add-report-column is removed as well.
29
- "MISSING IN MCP: POST /rest/track-prompt (Track Prompt)",
30
- "MISSING IN MCP: POST /rest/track-score (Track Score)",
31
- "MISSING IN MCP: POST /rest/track-metadata (Track Metadata)",
32
- "MISSING IN MCP: POST /rest/track-group (Track Group)",
33
- "MISSING IN MCP: POST /report-columns (Add Column to Evaluation Pipeline)",
34
-
35
- // get-workflow exists in the backend (public_api/workflows/get_workflows_id_or_name.py)
36
- // but is not in the OpenAPI spec.
37
- "EXTRA IN MCP (not in OpenAPI): GET /workflows/{workflow_id_or_name} (tool: get-workflow)",
38
-
39
- // Folder management endpoints exist in the backend (public_api/) but are not yet in the OpenAPI spec.
40
- // edit-folder was added in PR #244.
41
- "EXTRA IN MCP (not in OpenAPI): PATCH /api/public/v2/folders/{folder_id} (tool: edit-folder)",
42
- "EXTRA IN MCP (not in OpenAPI): GET /api/public/v2/folders/entities (tool: get-folder-entities)",
43
- "EXTRA IN MCP (not in OpenAPI): POST /api/public/v2/folders/entities (tool: move-folder-entities)",
44
- "EXTRA IN MCP (not in OpenAPI): DELETE /api/public/v2/folders/entities (tool: delete-folder-entities)",
45
- "EXTRA IN MCP (not in OpenAPI): GET /api/public/v2/folders/resolve-id (tool: resolve-folder-id)",
46
-
47
- // callback_url is documented in the run-workflow reference text
48
- // (https://docs.promptlayer.com/reference/run-workflow) under Request Body
49
- // but is not yet in the OpenAPI spec. We include it for async webhook support.
50
- 'EXTRA FIELD: POST /workflows/{workflow_name}/run > "callback_url" not in OpenAPI [tool: run-workflow]',
51
-
52
- // The OpenAPI spec defines input/output as oneOf (ChatPrompt | CompletionPrompt).
53
- // We type them as plain object (z.record) because the MCP tool passes them through
54
- // as-is to the API — the server validates the discriminated union, not us.
55
- 'TYPE MISMATCH: POST /log-request > "input": MCP=object, OpenAPI=oneOf [tool: log-request]',
56
- 'TYPE MISMATCH: POST /log-request > "output": MCP=object, OpenAPI=oneOf [tool: log-request]',
57
-
58
- // workspace_id is accepted by the backend but not yet in the OpenAPI spec for these endpoints.
59
- 'EXTRA FIELD: GET /prompt-templates > "workspace_id" not in OpenAPI [tool: list-prompt-templates]',
60
- 'EXTRA FIELD: POST /api/public/v2/folders > "workspace_id" not in OpenAPI [tool: create-folder]',
61
-
62
- // snippet_overrides is accepted by the backend publish endpoint but not in OpenAPI spec.
63
- 'EXTRA FIELD: POST /rest/prompt-templates > "snippet_overrides" not in OpenAPI [tool: publish-prompt-template]',
64
-
65
- // Backend accepts prompt_version_id as alternative to prompt_version_number for label ops,
66
- // and we make prompt_version_number optional to support both. Not yet in OpenAPI spec.
67
- 'EXTRA FIELD: POST /prompts/{prompt_id}/label > "prompt_version_id" not in OpenAPI [tool: create-prompt-label]',
68
- 'EXTRA FIELD: PATCH /prompt-labels/{prompt_label_id} > "prompt_version_id" not in OpenAPI [tool: move-prompt-label]',
69
- 'REQUIRED MISMATCH: POST /prompts/{prompt_id}/label > "prompt_version_number": MCP=false, OpenAPI=true [tool: create-prompt-label]',
70
- 'REQUIRED MISMATCH: PATCH /prompt-labels/{prompt_label_id} > "prompt_version_number": MCP=false, OpenAPI=true [tool: move-prompt-label]',
71
-
72
- // score_name is accepted by the backend log-request endpoint but not in OpenAPI spec.
73
- 'EXTRA FIELD: POST /log-request > "score_name" not in OpenAPI [tool: log-request]',
74
-
75
- // folder_id is accepted by the backend create-dataset-group endpoint but not in OpenAPI spec.
76
- 'EXTRA FIELD: POST /api/public/v2/dataset-groups > "folder_id" not in OpenAPI [tool: create-dataset-group]',
77
-
78
- // Backend auto-generates name if omitted for dataset groups; OpenAPI marks it required.
79
- 'REQUIRED MISMATCH: POST /api/public/v2/dataset-groups > "name": MCP=false, OpenAPI=true [tool: create-dataset-group]',
80
-
81
- // workflow_node_id and workflow_node_name are accepted by the backend execution results
82
- // endpoint but not in OpenAPI spec.
83
- 'EXTRA FIELD: GET /workflow-version-execution-results > "workflow_node_id" not in OpenAPI [tool: get-workflow-version-execution-results]',
84
- 'EXTRA FIELD: GET /workflow-version-execution-results > "workflow_node_name" not in OpenAPI [tool: get-workflow-version-execution-results]',
85
-
86
- // The create-dataset-version-from-filter-params endpoint passes through to the backend's
87
- // RequestLogFilterParams model which has many more filter fields than the OpenAPI spec lists.
88
- // We include the full set from the backend for completeness. The OpenAPI spec only documents
89
- // a simplified subset (dataset_group_id, variables_to_parse, start_time, end_time, plus the
90
- // fields we added: prompt_id, prompt_version_id, prompt_label_id, workspace_id, tags, metadata).
91
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "id" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
92
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "limit" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
93
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "tags_and" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
94
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "tags_or" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
95
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "metadata_and" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
96
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "metadata_or" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
97
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "prompt_templates_include" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
98
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "prompt_templates_exclude" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
99
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "starred" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
100
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "status" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
101
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "sort_by" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
102
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "sort_order" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
103
- 'EXTRA FIELD: POST /api/public/v2/dataset-versions/from-filter-params > "order_by_random" not in OpenAPI [tool: create-dataset-version-from-filter-params]',
104
-
105
- // The OpenAPI spec defines scores as object but backend uses a list of {name, operator, value}.
106
- // Our array type matches what the backend actually accepts.
107
- 'TYPE MISMATCH: POST /api/public/v2/dataset-versions/from-filter-params > "scores": MCP=array, OpenAPI=object [tool: create-dataset-version-from-filter-params]',
108
-
109
- // patch-prompt-template-version exists in the backend and OpenAPI but is not yet implemented
110
- // in MCP. Low priority — publish-prompt-template covers most use cases.
111
- "MISSING IN MCP: PATCH /rest/prompt-templates/{identifier} (Patch Prompt Template Version)",
112
-
113
- // OTLP trace ingestion endpoint exists in OpenAPI but is not implemented in MCP.
114
- // This is a specialized protocol endpoint, not a typical REST API tool.
115
- "MISSING IN MCP: POST /v1/traces (Ingest Traces (OTLP))",
116
-
117
- // Semantic search and tags parameters for folder entities are documented in the reference docs
118
- // (https://docs.promptlayer.com/reference/list-folder-entities) but not yet in the OpenAPI spec.
119
- 'EXTRA FIELD: GET /api/public/v2/folders/entities > "semantic_search" not in OpenAPI [tool: get-folder-entities]',
120
- 'EXTRA FIELD: GET /api/public/v2/folders/entities > "semantic_search_top_k" not in OpenAPI [tool: get-folder-entities]',
121
- 'EXTRA FIELD: GET /api/public/v2/folders/entities > "semantic_search_threshold" not in OpenAPI [tool: get-folder-entities]',
122
- 'EXTRA FIELD: GET /api/public/v2/folders/entities > "tags" not in OpenAPI [tool: get-folder-entities]',
123
-
124
- // The OpenAPI spec represents tags as anyOf(string, array) but our Zod union serializes
125
- // as "union" vs "union(string,array)". Functionally equivalent.
126
- 'TYPE MISMATCH: GET /prompt-templates > "tags": MCP=union, OpenAPI=union(string,array) [tool: list-prompt-templates]',
127
-
128
- // filter_type OpenAPI uses oneOf vs our Zod union. Functionally equivalent.
129
- 'TYPE MISMATCH: GET /api/public/v2/folders/entities > "filter_type": MCP=union, OpenAPI=oneOf [tool: get-folder-entities]',
130
-
131
- // workspace_id is optional in MCP because the API key implicitly provides it.
132
- // OpenAPI marks it required but the backend falls back to the API key's workspace.
133
- 'REQUIRED MISMATCH: GET /api/public/v2/folders/entities > "workspace_id": MCP=false, OpenAPI=true [tool: get-folder-entities]',
134
- 'REQUIRED MISMATCH: GET /api/public/v2/folders/resolve-id > "workspace_id": MCP=false, OpenAPI=true [tool: resolve-folder-id]',
135
- ];
136
- // ─────────────────────────────────────────────────────────────────────────────
137
-
138
- type OpenAPIEndpoint = {
139
- method: string;
140
- path: string;
141
- summary: string;
142
- operationId: string;
143
- pathParams: { name: string; type: string; required: boolean }[];
144
- queryParams: { name: string; type: string; required: boolean; enum?: string[] }[];
145
- bodyFields: { name: string; type: string; required: boolean; enum?: string[] }[];
146
- };
147
-
148
- type MCPTool = {
149
- toolName: string;
150
- method: string;
151
- path: string;
152
- description: string;
153
- fields: { name: string; type: string; required: boolean; enum?: string[] }[];
154
- };
155
-
156
- function endpointKey(method: string, path: string): string {
157
- return `${method} ${path}`;
158
- }
159
-
160
- function main() {
161
- const openapi: OpenAPIEndpoint[] = JSON.parse(readFileSync(OPENAPI_PATH, "utf-8"));
162
- const mcp: MCPTool[] = JSON.parse(readFileSync(MCP_PATH, "utf-8"));
163
-
164
- const allIssues: string[] = [];
165
-
166
- const openapiMap = new Map<string, OpenAPIEndpoint>();
167
- for (const ep of openapi) openapiMap.set(endpointKey(ep.method, ep.path), ep);
168
-
169
- const mcpMap = new Map<string, MCPTool>();
170
- for (const tool of mcp) mcpMap.set(endpointKey(tool.method, tool.path), tool);
171
-
172
- // 1. Endpoints in OpenAPI but missing from MCP
173
- for (const [key, ep] of openapiMap) {
174
- if (!mcpMap.has(key)) {
175
- allIssues.push(`MISSING IN MCP: ${key} (${ep.summary})`);
176
- }
177
- }
178
-
179
- // 2. Endpoints in MCP but not in OpenAPI
180
- for (const [key, tool] of mcpMap) {
181
- if (!openapiMap.has(key)) {
182
- allIssues.push(`EXTRA IN MCP (not in OpenAPI): ${key} (tool: ${tool.toolName})`);
183
- }
184
- }
185
-
186
- // 3. Field-level comparison for matching endpoints
187
- for (const [key, tool] of mcpMap) {
188
- const ep = openapiMap.get(key);
189
- if (!ep) continue;
190
-
191
- const openapiFields = new Map<string, { type: string; required: boolean; enum?: string[] }>();
192
- for (const p of ep.pathParams) openapiFields.set(p.name, p);
193
- for (const p of ep.queryParams) openapiFields.set(p.name, { type: p.type, required: p.required, ...(p.enum ? { enum: p.enum } : {}) });
194
- for (const f of ep.bodyFields) openapiFields.set(f.name, { type: f.type, required: f.required, ...(f.enum ? { enum: f.enum } : {}) });
195
-
196
- const mcpFields = new Map<string, { type: string; required: boolean; enum?: string[] }>();
197
- for (const f of tool.fields) mcpFields.set(f.name, f);
198
-
199
- for (const [name, spec] of openapiFields) {
200
- if (!mcpFields.has(name)) {
201
- allIssues.push(`MISSING FIELD: ${key} > "${name}" (${spec.type}, ${spec.required ? "required" : "optional"}) [tool: ${tool.toolName}]`);
202
- }
203
- }
204
-
205
- for (const [name] of mcpFields) {
206
- if (!openapiFields.has(name)) {
207
- allIssues.push(`EXTRA FIELD: ${key} > "${name}" not in OpenAPI [tool: ${tool.toolName}]`);
208
- }
209
- }
210
-
211
- for (const [name, mcpField] of mcpFields) {
212
- const oaField = openapiFields.get(name);
213
- if (!oaField) continue;
214
-
215
- if (mcpField.required !== oaField.required) {
216
- allIssues.push(`REQUIRED MISMATCH: ${key} > "${name}": MCP=${mcpField.required}, OpenAPI=${oaField.required} [tool: ${tool.toolName}]`);
217
- }
218
-
219
- const mcpType = mcpField.type.replace("number", "integer");
220
- const oaType = oaField.type.replace("number", "integer");
221
- if (mcpType !== oaType && oaType !== "unknown") {
222
- allIssues.push(`TYPE MISMATCH: ${key} > "${name}": MCP=${mcpField.type}, OpenAPI=${oaField.type} [tool: ${tool.toolName}]`);
223
- }
224
- }
225
- }
226
-
227
- // Partition into known vs new
228
- const knownSet = new Set(KNOWN_EXCEPTIONS);
229
- const knownIssues = allIssues.filter((i) => knownSet.has(i));
230
- const newIssues = allIssues.filter((i) => !knownSet.has(i));
231
-
232
- // Report
233
- if (allIssues.length === 0) {
234
- console.log("PASS: All MCP tools are in sync with the OpenAPI spec. No issues.");
235
- process.exit(0);
236
- }
237
-
238
- if (knownIssues.length > 0) {
239
- console.log(`Known exceptions (${knownIssues.length}):`);
240
- knownIssues.forEach((i) => console.log(` [OK] ${i}`));
241
- console.log();
242
- }
243
-
244
- if (newIssues.length === 0) {
245
- console.log(`PASS: ${knownIssues.length} known exception(s), 0 new issues.`);
246
- process.exit(0);
247
- }
248
-
249
- console.log(`FAIL: ${newIssues.length} new issue(s) found:\n`);
250
-
251
- const buckets: Record<string, string[]> = {
252
- "Missing Endpoints": [],
253
- "Extra Endpoints": [],
254
- "Missing Fields": [],
255
- "Extra Fields": [],
256
- "Type/Required Mismatches": [],
257
- };
258
-
259
- for (const i of newIssues) {
260
- if (i.startsWith("MISSING IN MCP")) buckets["Missing Endpoints"].push(i);
261
- else if (i.startsWith("EXTRA IN MCP")) buckets["Extra Endpoints"].push(i);
262
- else if (i.startsWith("MISSING FIELD")) buckets["Missing Fields"].push(i);
263
- else if (i.startsWith("EXTRA FIELD")) buckets["Extra Fields"].push(i);
264
- else buckets["Type/Required Mismatches"].push(i);
265
- }
266
-
267
- for (const [label, items] of Object.entries(buckets)) {
268
- if (items.length === 0) continue;
269
- console.log(`--- ${label} ---`);
270
- items.forEach((i) => console.log(` ${i}`));
271
- console.log();
272
- }
273
-
274
- process.exit(1);
275
- }
276
-
277
- main();
@@ -1,122 +0,0 @@
1
- /**
2
- * Extracts the MCP tool definitions from our types.ts and outputs a canonical
3
- * JSON file matching the shape of openapi-endpoints.json for comparison.
4
- *
5
- * Usage: npx tsx scripts/extract-mcp-tools.ts
6
- */
7
-
8
- import { TOOL_DEFINITIONS } from "../src/types.js";
9
- import { zodToJsonSchema } from "zod-to-json-schema";
10
-
11
- const OUTPUT_PATH = new URL("./mcp-tools.json", import.meta.url).pathname;
12
-
13
- // Map from MCP tool name to the REST endpoint it wraps.
14
- // This is the manual mapping — it's the one thing we maintain by hand.
15
- // The diff script checks everything else automatically.
16
- // Fields renamed in MCP vs OpenAPI (MCP name → OpenAPI name)
17
- const FIELD_ALIASES: Record<string, Record<string, string>> = {
18
- "get-prompt-template": { "prompt_name": "identifier" },
19
- };
20
-
21
- const TOOL_TO_ENDPOINT: Record<string, { method: string; path: string }> = {
22
- "get-prompt-template": { method: "POST", path: "/prompt-templates/{identifier}" },
23
- "get-prompt-template-raw": { method: "GET", path: "/prompt-templates/{identifier}" },
24
- "list-prompt-templates": { method: "GET", path: "/prompt-templates" },
25
- "publish-prompt-template": { method: "POST", path: "/rest/prompt-templates" },
26
- "list-prompt-template-labels":{ method: "GET", path: "/prompt-templates/{identifier}/labels" },
27
- "create-prompt-label": { method: "POST", path: "/prompts/{prompt_id}/label" },
28
- "move-prompt-label": { method: "PATCH", path: "/prompt-labels/{prompt_label_id}" },
29
- "delete-prompt-label": { method: "DELETE", path: "/prompt-labels/{prompt_label_id}" },
30
- "get-snippet-usage": { method: "GET", path: "/prompt-templates/{identifier}/snippet-usage" },
31
- "search-request-logs": { method: "POST", path: "/api/public/v2/requests/search" },
32
- "get-request": { method: "GET", path: "/api/public/v2/requests/{request_id}" },
33
- "log-request": { method: "POST", path: "/log-request" },
34
- "create-spans-bulk": { method: "POST", path: "/spans-bulk" },
35
- "list-datasets": { method: "GET", path: "/api/public/v2/datasets" },
36
- "get-dataset-rows": { method: "GET", path: "/api/public/v2/datasets/{dataset_id}/rows" },
37
- "create-dataset-group": { method: "POST", path: "/api/public/v2/dataset-groups" },
38
- "create-dataset-version-from-file": { method: "POST", path: "/api/public/v2/dataset-versions/from-file" },
39
- "create-dataset-version-from-filter-params": { method: "POST", path: "/api/public/v2/dataset-versions/from-filter-params" },
40
- "list-evaluations": { method: "GET", path: "/api/public/v2/evaluations" },
41
- "get-evaluation-rows": { method: "GET", path: "/api/public/v2/evaluations/{evaluation_id}/rows" },
42
- "create-report": { method: "POST", path: "/reports" },
43
- "run-report": { method: "POST", path: "/reports/{report_id}/run" },
44
- "get-report": { method: "GET", path: "/reports/{report_id}" },
45
- "get-report-score": { method: "GET", path: "/reports/{report_id}/score" },
46
- "update-report-score-card": { method: "PATCH", path: "/reports/{report_id}/score-card" },
47
- "delete-reports-by-name": { method: "DELETE", path: "/reports/name/{report_name}" },
48
- "list-workflows": { method: "GET", path: "/workflows" },
49
- "create-workflow": { method: "POST", path: "/rest/workflows" },
50
- "patch-workflow": { method: "PATCH", path: "/rest/workflows/{workflow_id_or_name}" },
51
- "run-workflow": { method: "POST", path: "/workflows/{workflow_name}/run" },
52
- "get-workflow-version-execution-results": { method: "GET", path: "/workflow-version-execution-results" },
53
- "get-workflow": { method: "GET", path: "/workflows/{workflow_id_or_name}" },
54
- "create-folder": { method: "POST", path: "/api/public/v2/folders" },
55
- "edit-folder": { method: "PATCH", path: "/api/public/v2/folders/{folder_id}" },
56
- "get-folder-entities": { method: "GET", path: "/api/public/v2/folders/entities" },
57
- "move-folder-entities": { method: "POST", path: "/api/public/v2/folders/entities" },
58
- "delete-folder-entities": { method: "DELETE", path: "/api/public/v2/folders/entities" },
59
- "resolve-folder-id": { method: "GET", path: "/api/public/v2/folders/resolve-id" },
60
- };
61
-
62
- type ToolEntry = {
63
- toolName: string;
64
- method: string;
65
- path: string;
66
- description: string;
67
- fields: { name: string; type: string; required: boolean; enum?: string[] }[];
68
- };
69
-
70
- function normalizeType(schema: Record<string, unknown>): string {
71
- const t = schema.type;
72
- if (typeof t === "string") return t;
73
- if (Array.isArray(t)) {
74
- const nonNull = t.filter((x: unknown) => x !== "null");
75
- return nonNull.length === 1 ? String(nonNull[0]) : `union(${nonNull.join(",")})`;
76
- }
77
- if (schema.anyOf) return "union";
78
- if (schema.oneOf) return "oneOf";
79
- return "unknown";
80
- }
81
-
82
- async function main() {
83
- const tools: ToolEntry[] = [];
84
-
85
- for (const [name, def] of Object.entries(TOOL_DEFINITIONS)) {
86
- const mapping = TOOL_TO_ENDPOINT[name];
87
- if (!mapping) {
88
- console.error(`WARNING: No endpoint mapping for tool "${name}"`);
89
- continue;
90
- }
91
-
92
- const jsonSchema = zodToJsonSchema(def.inputSchema, { target: "openApi3" }) as Record<string, unknown>;
93
- const properties = (jsonSchema.properties || {}) as Record<string, Record<string, unknown>>;
94
- const required = new Set((jsonSchema.required || []) as string[]);
95
-
96
- const aliases = FIELD_ALIASES[name] || {};
97
- const fields: ToolEntry["fields"] = [];
98
- for (const [fieldName, fieldSchema] of Object.entries(properties)) {
99
- if (fieldName === "api_key") continue; // skip MCP-only field
100
- fields.push({
101
- name: aliases[fieldName] || fieldName,
102
- type: normalizeType(fieldSchema),
103
- required: required.has(fieldName),
104
- ...(fieldSchema.enum ? { enum: fieldSchema.enum as string[] } : {}),
105
- });
106
- }
107
-
108
- tools.push({
109
- toolName: name,
110
- method: mapping.method,
111
- path: mapping.path,
112
- description: def.description,
113
- fields,
114
- });
115
- }
116
-
117
- const fs = await import("fs");
118
- fs.writeFileSync(OUTPUT_PATH, JSON.stringify(tools, null, 2) + "\n");
119
- console.error(`Wrote ${tools.length} tools to ${OUTPUT_PATH}`);
120
- }
121
-
122
- main().catch((e) => { console.error(e); process.exit(1); });
@@ -1,150 +0,0 @@
1
- /**
2
- * Fetches the PromptLayer OpenAPI spec from GitHub and extracts a canonical
3
- * list of endpoints with their parameters. Writes to scripts/openapi-endpoints.json.
4
- *
5
- * Usage: npx tsx scripts/fetch-openapi-endpoints.ts
6
- */
7
-
8
- const OPENAPI_URL = "https://raw.githubusercontent.com/magnivorg/prompt-layer-docs/master/openapi.json";
9
- const OUTPUT_PATH = new URL("./openapi-endpoints.json", import.meta.url).pathname;
10
-
11
- type OpenAPIParam = {
12
- name: string;
13
- in: string;
14
- required?: boolean;
15
- schema?: { type?: string; enum?: string[]; default?: unknown; [k: string]: unknown };
16
- [k: string]: unknown;
17
- };
18
-
19
- type OpenAPISchema = {
20
- type?: string;
21
- properties?: Record<string, { type?: string | unknown[]; enum?: string[]; [k: string]: unknown }>;
22
- required?: string[];
23
- $ref?: string;
24
- [k: string]: unknown;
25
- };
26
-
27
- type CanonicalEndpoint = {
28
- method: string;
29
- path: string;
30
- summary: string;
31
- operationId: string;
32
- pathParams: { name: string; type: string; required: boolean }[];
33
- queryParams: { name: string; type: string; required: boolean; enum?: string[]; default?: unknown }[];
34
- bodyFields: { name: string; type: string; required: boolean; enum?: string[] }[];
35
- };
36
-
37
- function resolveRef(ref: string, spec: Record<string, unknown>): Record<string, unknown> {
38
- const parts = ref.replace("#/", "").split("/");
39
- let obj: unknown = spec;
40
- for (const p of parts) obj = (obj as Record<string, unknown>)[p];
41
- return obj as Record<string, unknown>;
42
- }
43
-
44
- function resolveSchema(schema: OpenAPISchema, spec: Record<string, unknown>): OpenAPISchema {
45
- if (schema.$ref) return resolveRef(schema.$ref, spec) as OpenAPISchema;
46
- return schema;
47
- }
48
-
49
- function normalizeType(schema: { type?: string | unknown[]; anyOf?: unknown[]; oneOf?: unknown[]; [k: string]: unknown }): string {
50
- if (typeof schema.type === "string") return schema.type;
51
- if (Array.isArray(schema.type)) {
52
- const nonNull = schema.type.filter((t) => t !== "null");
53
- return nonNull.length === 1 ? String(nonNull[0]) : String(schema.type);
54
- }
55
- if (schema.anyOf) {
56
- const types = (schema.anyOf as Array<{ type?: string }>).map((s) => s.type).filter((t) => t && t !== "null");
57
- return types.length === 1 ? types[0]! : `union(${types.join(",")})`;
58
- }
59
- if (schema.oneOf) return "oneOf";
60
- return "unknown";
61
- }
62
-
63
- async function main() {
64
- console.error(`Fetching OpenAPI spec from ${OPENAPI_URL}...`);
65
- const resp = await fetch(OPENAPI_URL);
66
- if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
67
- const spec = (await resp.json()) as Record<string, unknown>;
68
- const paths = spec.paths as Record<string, Record<string, Record<string, unknown>>>;
69
-
70
- const endpoints: CanonicalEndpoint[] = [];
71
-
72
- for (const [path, methods] of Object.entries(paths).sort(([a], [b]) => a.localeCompare(b))) {
73
- for (const method of ["get", "post", "put", "patch", "delete"]) {
74
- const op = methods[method];
75
- if (!op) continue;
76
-
77
- const endpoint: CanonicalEndpoint = {
78
- method: method.toUpperCase(),
79
- path,
80
- summary: (op.summary as string) || "",
81
- operationId: (op.operationId as string) || "",
82
- pathParams: [],
83
- queryParams: [],
84
- bodyFields: [],
85
- };
86
-
87
- // Parameters (path + query)
88
- const params = (op.parameters || []) as OpenAPIParam[];
89
- for (const p of params) {
90
- const resolved = p.$ref ? (resolveRef(p.$ref as string, spec) as OpenAPIParam) : p;
91
- if (resolved.in === "header") continue; // skip X-API-KEY
92
- const schema = resolved.schema || {};
93
- const entry = {
94
- name: resolved.name,
95
- type: normalizeType(schema),
96
- required: resolved.required ?? false,
97
- ...(schema.enum ? { enum: schema.enum } : {}),
98
- ...(schema.default !== undefined ? { default: schema.default } : {}),
99
- };
100
- if (resolved.in === "path") endpoint.pathParams.push(entry);
101
- else if (resolved.in === "query") endpoint.queryParams.push(entry);
102
- }
103
-
104
- // Request body
105
- const reqBody = op.requestBody as { content?: Record<string, { schema?: OpenAPISchema }> } | undefined;
106
- if (reqBody?.content) {
107
- for (const ct of Object.values(reqBody.content)) {
108
- let schema = ct.schema;
109
- if (!schema) continue;
110
- schema = resolveSchema(schema, spec);
111
-
112
- // Handle anyOf/oneOf wrappers (e.g. anyOf: [$ref, null])
113
- if (!schema.properties && (schema.anyOf || schema.oneOf)) {
114
- const variants = (schema.anyOf || schema.oneOf) as OpenAPISchema[];
115
- const nonNull = variants.filter((v) => v.type !== "null");
116
- if (nonNull.length === 1) {
117
- schema = resolveSchema(nonNull[0], spec);
118
- }
119
- }
120
-
121
- if (schema.type === "object" && schema.properties) {
122
- const required = new Set(schema.required || []);
123
- for (const [name, propSchema] of Object.entries(schema.properties)) {
124
- const resolved = propSchema.$ref
125
- ? (resolveRef(propSchema.$ref as string, spec) as typeof propSchema)
126
- : propSchema;
127
- endpoint.bodyFields.push({
128
- name,
129
- type: normalizeType(resolved),
130
- required: required.has(name),
131
- ...(resolved.enum ? { enum: resolved.enum as string[] } : {}),
132
- });
133
- }
134
- }
135
- }
136
- }
137
-
138
- endpoints.push(endpoint);
139
- }
140
- }
141
-
142
- const { writeFileSync } = await import("fs");
143
- writeFileSync(OUTPUT_PATH, JSON.stringify(endpoints, null, 2) + "\n");
144
- console.error(`Wrote ${endpoints.length} endpoints to ${OUTPUT_PATH}`);
145
- }
146
-
147
- main().catch((e) => {
148
- console.error(e);
149
- process.exit(1);
150
- });