@promptlayer/mcp-server 1.0.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.
@@ -0,0 +1,150 @@
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
+ });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Pretty-prints all MCP tool names, descriptions, and annotations.
3
+ *
4
+ * Usage: npx tsx scripts/show-descriptions.ts
5
+ */
6
+
7
+ import { TOOL_DEFINITIONS } from "../src/types.js";
8
+
9
+ const CATEGORY_ORDER = [
10
+ "Prompt Templates",
11
+ "Tracking",
12
+ "Datasets",
13
+ "Evaluations",
14
+ "Agents / Workflows",
15
+ "Folders",
16
+ ];
17
+
18
+ // Derive categories from the comment markers in TOOL_DEFINITIONS
19
+ const TOOL_CATEGORIES: Record<string, string> = {
20
+ "get-prompt-template": "Prompt Templates",
21
+ "get-prompt-template-raw": "Prompt Templates",
22
+ "list-prompt-templates": "Prompt Templates",
23
+ "publish-prompt-template": "Prompt Templates",
24
+ "list-prompt-template-labels": "Prompt Templates",
25
+ "create-prompt-label": "Prompt Templates",
26
+ "move-prompt-label": "Prompt Templates",
27
+ "delete-prompt-label": "Prompt Templates",
28
+ "get-snippet-usage": "Prompt Templates",
29
+ "log-request": "Tracking",
30
+ "create-spans-bulk": "Tracking",
31
+ "list-datasets": "Datasets",
32
+ "create-dataset-group": "Datasets",
33
+ "create-dataset-version-from-file": "Datasets",
34
+ "create-dataset-version-from-filter-params": "Datasets",
35
+ "list-evaluations": "Evaluations",
36
+ "create-report": "Evaluations",
37
+ "run-report": "Evaluations",
38
+ "get-report": "Evaluations",
39
+ "get-report-score": "Evaluations",
40
+ "update-report-score-card": "Evaluations",
41
+ "delete-reports-by-name": "Evaluations",
42
+ "list-workflows": "Agents / Workflows",
43
+ "get-workflow": "Agents / Workflows",
44
+ "create-workflow": "Agents / Workflows",
45
+ "patch-workflow": "Agents / Workflows",
46
+ "run-workflow": "Agents / Workflows",
47
+ "get-workflow-version-execution-results": "Agents / Workflows",
48
+ "create-folder": "Folders",
49
+ "edit-folder": "Folders",
50
+ "get-folder-entities": "Folders",
51
+ "move-folder-entities": "Folders",
52
+ "delete-folder-entities": "Folders",
53
+ "resolve-folder-id": "Folders",
54
+ };
55
+
56
+ // Group tools by category
57
+ const grouped = new Map<string, { name: string; description: string; readOnly: boolean }[]>();
58
+ for (const cat of CATEGORY_ORDER) grouped.set(cat, []);
59
+
60
+ for (const [name, def] of Object.entries(TOOL_DEFINITIONS)) {
61
+ const cat = TOOL_CATEGORIES[name] ?? "Other";
62
+ if (!grouped.has(cat)) grouped.set(cat, []);
63
+ grouped.get(cat)!.push({
64
+ name,
65
+ description: def.description,
66
+ readOnly: (def.annotations as { readOnlyHint?: boolean })?.readOnlyHint ?? false,
67
+ });
68
+ }
69
+
70
+ // Print
71
+ const SEP = "─".repeat(80);
72
+
73
+ for (const [category, tools] of grouped) {
74
+ if (tools.length === 0) continue;
75
+ console.log(`\n${"═".repeat(80)}`);
76
+ console.log(` ${category.toUpperCase()} (${tools.length} tools)`);
77
+ console.log(`${"═".repeat(80)}`);
78
+
79
+ for (const tool of tools) {
80
+ const badge = tool.readOnly ? "📖 read" : "✏️ write";
81
+ console.log(`\n${SEP}`);
82
+ console.log(` ${tool.name} [${badge}]`);
83
+ console.log(SEP);
84
+ // Word-wrap description at ~76 chars
85
+ const words = tool.description.split(" ");
86
+ let line = " ";
87
+ for (const word of words) {
88
+ if (line.length + word.length + 1 > 78) {
89
+ console.log(line);
90
+ line = " " + word;
91
+ } else {
92
+ line += (line.length > 2 ? " " : "") + word;
93
+ }
94
+ }
95
+ if (line.trim()) console.log(line);
96
+ }
97
+ }
98
+
99
+ console.log(`\n${"═".repeat(80)}`);
100
+ console.log(` TOTAL: ${Object.keys(TOOL_DEFINITIONS).length} tools`);
101
+ console.log(`${"═".repeat(80)}\n`);
package/src/client.ts ADDED
@@ -0,0 +1,92 @@
1
+ import type { GetPromptTemplateParams, ListPromptTemplatesParams } from "./types.js";
2
+ import { buildQueryParams, handleApiError } from "./utils.js";
3
+
4
+ type Body = Record<string, unknown>;
5
+
6
+ export class PromptLayerClient {
7
+ constructor(
8
+ private apiKey: string,
9
+ private baseUrl: string = "https://api.promptlayer.com"
10
+ ) {}
11
+
12
+ private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
13
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
14
+ ...options,
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ "X-API-KEY": this.apiKey,
18
+ ...(options.headers as Record<string, string> | undefined),
19
+ },
20
+ });
21
+ if (!response.ok) throw await handleApiError(response);
22
+ return (await response.json()) as T;
23
+ }
24
+
25
+ private get<T = unknown>(path: string, params?: Body): Promise<T> {
26
+ return this.request<T>(`${path}${buildQueryParams(params)}`, { method: "GET" });
27
+ }
28
+
29
+ private post<T = unknown>(path: string, body?: Body): Promise<T> {
30
+ return this.request<T>(path, { method: "POST", body: JSON.stringify(body) });
31
+ }
32
+
33
+ private patch<T = unknown>(path: string, body?: Body): Promise<T> {
34
+ return this.request<T>(path, { method: "PATCH", body: JSON.stringify(body) });
35
+ }
36
+
37
+ private del<T = unknown>(path: string): Promise<T> {
38
+ return this.request<T>(path, { method: "DELETE" });
39
+ }
40
+
41
+ private enc(s: string): string {
42
+ return encodeURIComponent(s);
43
+ }
44
+
45
+ // Prompt Templates
46
+ getPromptTemplate(name: string, params?: GetPromptTemplateParams) {
47
+ return this.post(`/prompt-templates/${this.enc(name)}`, { api_key: this.apiKey, ...params });
48
+ }
49
+ getPromptTemplateRaw(id: string, params?: Body) { return this.get(`/prompt-templates/${this.enc(id)}`, params); }
50
+ listPromptTemplates(params?: ListPromptTemplatesParams) { return this.get("/prompt-templates", params); }
51
+ publishPromptTemplate(body: Body) { return this.post("/rest/prompt-templates", body); }
52
+ listPromptTemplateLabels(id: string) { return this.get(`/prompt-templates/${this.enc(id)}/labels`); }
53
+ createPromptLabel(promptId: number, body: Body) { return this.post(`/prompts/${promptId}/label`, body); }
54
+ movePromptLabel(labelId: number, body: Body) { return this.patch(`/prompt-labels/${labelId}`, body); }
55
+ deletePromptLabel(labelId: number) { return this.del(`/prompt-labels/${labelId}`); }
56
+ getSnippetUsage(id: string, params?: Body) { return this.get(`/prompt-templates/${this.enc(id)}/snippet-usage`, params); }
57
+
58
+ // Tracking
59
+ logRequest(body: Body) { return this.post("/log-request", body); }
60
+ createSpansBulk(body: Body) { return this.post("/spans-bulk", body); }
61
+
62
+ // Datasets
63
+ listDatasets(params?: Body) { return this.get("/api/public/v2/datasets", params); }
64
+ createDatasetGroup(body: Body) { return this.post("/api/public/v2/dataset-groups", body); }
65
+ createDatasetVersionFromFile(body: Body) { return this.post("/api/public/v2/dataset-versions/from-file", body); }
66
+ createDatasetVersionFromFilterParams(body: Body) { return this.post("/api/public/v2/dataset-versions/from-filter-params", body); }
67
+
68
+ // Evaluations
69
+ listEvaluations(params?: Body) { return this.get("/api/public/v2/evaluations", params); }
70
+ createReport(body: Body) { return this.post("/reports", body); }
71
+ runReport(id: number, body: Body) { return this.post(`/reports/${id}/run`, body); }
72
+ getReport(id: number) { return this.get(`/reports/${id}`); }
73
+ getReportScore(id: number) { return this.get(`/reports/${id}/score`); }
74
+ updateReportScoreCard(id: number, body: Body) { return this.patch(`/reports/${id}/score-card`, body); }
75
+ deleteReportsByName(name: string) { return this.del(`/reports/name/${this.enc(name)}`); }
76
+
77
+ // Agents
78
+ listWorkflows(params?: Body) { return this.get("/workflows", params); }
79
+ getWorkflow(idOrName: string) { return this.get(`/workflows/${this.enc(idOrName)}`); }
80
+ createWorkflow(body: Body) { return this.post("/rest/workflows", body); }
81
+ patchWorkflow(idOrName: string, body: Body) { return this.patch(`/rest/workflows/${this.enc(idOrName)}`, body); }
82
+ runWorkflow(name: string, body: Body) { return this.post(`/workflows/${this.enc(name)}/run`, body); }
83
+ getWorkflowVersionExecutionResults(params: Body) { return this.get("/workflow-version-execution-results", params); }
84
+
85
+ // Folders
86
+ createFolder(body: Body) { return this.post("/api/public/v2/folders", body); }
87
+ editFolder(folderId: number, body: Body) { return this.patch(`/api/public/v2/folders/${folderId}`, body); }
88
+ getFolderEntities(params: Body) { return this.get("/api/public/v2/folders/entities", params); }
89
+ moveFolderEntities(body: Body) { return this.post("/api/public/v2/folders/entities", body); }
90
+ deleteFolderEntities(body: Body) { return this.request("/api/public/v2/folders/entities", { method: "DELETE", body: JSON.stringify(body) }); }
91
+ resolveFolderId(params: Body) { return this.get("/api/public/v2/folders/resolve-id", params); }
92
+ }
@@ -0,0 +1,116 @@
1
+ import type { GetPromptTemplateParams, ListPromptTemplatesParams } from "./types.js";
2
+ import { TOOL_DEFINITIONS } from "./types.js";
3
+ import { createToolHandler } from "./utils.js";
4
+
5
+ type Args = Record<string, unknown> & { api_key?: string };
6
+ function body(args: Args) { const { api_key: _, ...rest } = args; return rest; }
7
+
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ export function registerAllTools(server: any) {
10
+ const t = TOOL_DEFINITIONS;
11
+
12
+ function reg(def: (typeof t)[keyof typeof t], call: Parameters<typeof createToolHandler>[0], msg: Parameters<typeof createToolHandler>[1]) {
13
+ server.tool(def.name, def.description, def.inputSchema.shape, createToolHandler(call, msg));
14
+ }
15
+
16
+ // Prompt Templates
17
+ reg(t["get-prompt-template"],
18
+ (c, a) => { const { api_key: _, prompt_name, ...p } = a as { prompt_name: string; api_key?: string } & GetPromptTemplateParams; return c.getPromptTemplate(prompt_name, p); },
19
+ (r) => { const { prompt_name, version } = r as { prompt_name: string; version: number }; return `Retrieved "${prompt_name}" v${version}`; });
20
+
21
+ reg(t["get-prompt-template-raw"],
22
+ (c, a) => { const { api_key: _, identifier, ...p } = a as { identifier: string; api_key?: string } & Args; return c.getPromptTemplateRaw(identifier, p); },
23
+ (r) => `Retrieved raw template "${(r as { prompt_name?: string }).prompt_name ?? ""}"`);
24
+
25
+ reg(t["list-prompt-templates"],
26
+ (c, a) => c.listPromptTemplates(body(a) as ListPromptTemplatesParams),
27
+ (r) => { const { items, page, pages, total } = r as { items: unknown[]; page: number; pages: number; total: number }; return `${items.length} template(s) (page ${page}/${pages}, total ${total})`; });
28
+
29
+ reg(t["publish-prompt-template"],
30
+ (c, a) => c.publishPromptTemplate(body(a)),
31
+ () => "Published");
32
+
33
+ reg(t["list-prompt-template-labels"],
34
+ (c, a) => c.listPromptTemplateLabels((a as { identifier: string }).identifier),
35
+ (r) => `${Array.isArray(r) ? r.length : 0} label(s)`);
36
+
37
+ reg(t["create-prompt-label"],
38
+ (c, a) => { const { api_key: _, prompt_id, prompt_version_number, name } = a as { prompt_id: number; prompt_version_number: number; name: string; api_key?: string }; return c.createPromptLabel(prompt_id, { prompt_version_number, name }); },
39
+ () => "Label created");
40
+
41
+ reg(t["move-prompt-label"],
42
+ (c, a) => { const { api_key: _, prompt_label_id, prompt_version_number } = a as { prompt_label_id: number; prompt_version_number: number; api_key?: string }; return c.movePromptLabel(prompt_label_id, { prompt_version_number }); },
43
+ () => "Label moved");
44
+
45
+ reg(t["delete-prompt-label"],
46
+ (c, a) => c.deletePromptLabel((a as { prompt_label_id: number }).prompt_label_id),
47
+ () => "Label deleted");
48
+
49
+ reg(t["get-snippet-usage"],
50
+ (c, a) => { const { api_key: _, identifier, ...p } = a as { identifier: string; api_key?: string } & Args; return c.getSnippetUsage(identifier, p); },
51
+ (r) => `${Array.isArray(r) ? r.length : 0} prompt(s) using snippet`);
52
+
53
+ // Tracking
54
+ reg(t["log-request"], (c, a) => c.logRequest(body(a)),
55
+ (r) => { const id = (r as { request_id?: unknown }).request_id; return id ? `Logged (ID: ${id})` : "Logged"; });
56
+ reg(t["create-spans-bulk"], (c, a) => c.createSpansBulk(body(a)),
57
+ (r) => `Created ${(r as { spans?: unknown[] }).spans?.length ?? 0} span(s)`);
58
+
59
+ // Datasets
60
+ reg(t["list-datasets"], (c, a) => c.listDatasets(body(a)),
61
+ (r) => { const { datasets, total } = r as { datasets?: unknown[]; total?: number }; return `${datasets?.length ?? 0} dataset(s) (total: ${total ?? "?"})`; });
62
+ reg(t["create-dataset-group"], (c, a) => c.createDatasetGroup(body(a)), () => "Dataset group created");
63
+ reg(t["create-dataset-version-from-file"], (c, a) => c.createDatasetVersionFromFile(body(a)), () => "Dataset version from file initiated");
64
+ reg(t["create-dataset-version-from-filter-params"], (c, a) => c.createDatasetVersionFromFilterParams(body(a)), () => "Dataset version from history initiated");
65
+
66
+ // Evaluations
67
+ reg(t["list-evaluations"], (c, a) => c.listEvaluations(body(a)),
68
+ (r) => { const { items, total } = r as { items?: unknown[]; total?: number }; return `${items?.length ?? 0} evaluation(s) (total: ${total ?? "?"})`; });
69
+ reg(t["create-report"], (c, a) => c.createReport(body(a)),
70
+ (r) => { const id = (r as { report_id?: number }).report_id; return id ? `Pipeline created (ID: ${id})` : "Pipeline created"; });
71
+ reg(t["run-report"],
72
+ (c, a) => { const { api_key: _, report_id, ...b } = a as { report_id: number; api_key?: string } & Args; return c.runReport(report_id, b); },
73
+ () => "Evaluation run started");
74
+ reg(t["get-report"],
75
+ (c, a) => c.getReport((a as { report_id: number }).report_id),
76
+ () => "Evaluation retrieved");
77
+ reg(t["get-report-score"],
78
+ (c, a) => c.getReportScore((a as { report_id: number }).report_id),
79
+ (r) => { const s = (r as { score?: number }).score; return s !== undefined ? `Score: ${s}` : "Score retrieved"; });
80
+ reg(t["update-report-score-card"],
81
+ (c, a) => { const { api_key: _, report_id, ...b } = a as { report_id: number; api_key?: string } & Args; return c.updateReportScoreCard(report_id, b); },
82
+ () => "Score card updated");
83
+ reg(t["delete-reports-by-name"],
84
+ (c, a) => c.deleteReportsByName((a as { report_name: string }).report_name),
85
+ () => "Reports archived");
86
+
87
+ // Agents
88
+ reg(t["list-workflows"], (c, a) => c.listWorkflows(body(a)), () => "Agents listed");
89
+ reg(t["create-workflow"], (c, a) => c.createWorkflow(body(a)), () => "Agent created");
90
+ reg(t["patch-workflow"],
91
+ (c, a) => { const { api_key: _, workflow_id_or_name, ...b } = a as { workflow_id_or_name: string; api_key?: string } & Args; return c.patchWorkflow(workflow_id_or_name, b); },
92
+ () => "Agent updated");
93
+ reg(t["run-workflow"],
94
+ (c, a) => { const { api_key: _, workflow_name, ...b } = a as { workflow_name: string; api_key?: string } & Args; return c.runWorkflow(workflow_name, b); },
95
+ (r) => { const id = (r as { workflow_version_execution_id?: number }).workflow_version_execution_id; return id ? `Agent run started (ID: ${id})` : "Agent run started"; });
96
+ reg(t["get-workflow-version-execution-results"],
97
+ (c, a) => c.getWorkflowVersionExecutionResults(body(a)),
98
+ () => "Execution results retrieved");
99
+ reg(t["get-workflow"],
100
+ (c, a) => c.getWorkflow((a as { workflow_id_or_name: string }).workflow_id_or_name),
101
+ (r) => { const w = r as { workflow?: { name?: string } }; return `Agent "${w.workflow?.name ?? ""}" retrieved`; });
102
+
103
+ // Folders
104
+ reg(t["create-folder"], (c, a) => c.createFolder(body(a)), () => "Folder created");
105
+ reg(t["edit-folder"],
106
+ (c, a) => { const { api_key: _, folder_id, ...b } = a as { folder_id: number; api_key?: string } & Args; return c.editFolder(folder_id, b); },
107
+ () => "Folder renamed");
108
+ reg(t["get-folder-entities"], (c, a) => c.getFolderEntities(body(a)),
109
+ (r) => { const e = (r as { entities?: unknown[] }).entities; return `${e?.length ?? 0} entity/entities`; });
110
+ reg(t["move-folder-entities"], (c, a) => c.moveFolderEntities(body(a)),
111
+ (r) => { const c_ = (r as { moved_count?: number }).moved_count; return `Moved ${c_ ?? 0} entity/entities`; });
112
+ reg(t["delete-folder-entities"], (c, a) => c.deleteFolderEntities(body(a)),
113
+ (r) => { const c_ = (r as { moved_count?: number }).moved_count; return `Deleted ${c_ ?? 0} entity/entities`; });
114
+ reg(t["resolve-folder-id"], (c, a) => c.resolveFolderId(body(a)),
115
+ (r) => { const id = (r as { id?: number }).id; return id ? `Folder ID: ${id}` : "Folder not found"; });
116
+ }
package/src/index.ts ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { registerAllTools } from "./handlers.js";
6
+
7
+ const INSTRUCTIONS = `
8
+ PromptLayer is a prompt management and observability platform. This MCP server lets you manage PromptLayer resources.
9
+
10
+ ## Key entities and naming
11
+
12
+ - **Prompt template**: A versioned prompt in the registry. Each version has a prompt_template (the content) and metadata. Versions are immutable — publishing always creates a new version.
13
+ - **Snippet**: A reusable prompt fragment referenced inside prompt templates with @@@snippet_name@@@ markers. Snippets are themselves prompt templates (with type "completion"). When a prompt is fetched, snippets are expanded inline by default.
14
+ - **Release label**: A pointer (e.g. "prod", "staging") attached to a specific prompt version. Move labels between versions for deployment.
15
+ - **Agent** (backend name: workflow): A multi-step pipeline of nodes. Each node has a type, configuration, and dependencies. Agents are versioned like prompts.
16
+ - **Evaluation pipeline** (backend name: report): Runs evaluation columns against a dataset and produces scores. Columns can be LLM assertions, code execution, comparisons, etc.
17
+ - **Dataset**: A versioned collection of test rows. Belongs to a dataset group. Versions can be created from CSV/JSON files or by filtering request log history.
18
+ - **Folder**: Organizes prompts, agents, datasets, evaluations, and other entities into a hierarchy.
19
+
20
+ ## Working with prompts and snippets
21
+
22
+ When editing a prompt that may contain snippets, always use get-prompt-template-raw with resolve_snippets=false. This preserves the raw @@@snippet_name@@@ references so they are not lost on re-publish. The response also includes a "snippets" array listing every snippet used.
23
+
24
+ When publishing back, keep @@@snippet_name@@@ markers intact in the prompt_template content. Do not inline snippet text — this breaks the snippet reference and future snippet updates will no longer propagate.
25
+
26
+ Use get-prompt-template (the POST variant) only when you need a fully rendered prompt ready to send to an LLM, with input_variables filled in and provider-specific formatting applied.
27
+
28
+ ## Working with evaluations
29
+
30
+ The recommended way to create an evaluation pipeline is with LLM assertion columns — these use a language model to score each dataset row. For details on all available column types (LLM assertion, code execution, comparison, etc.), search the PromptLayer docs or see https://docs.promptlayer.com/features/evaluations/column-types.
31
+
32
+ ## Additional documentation
33
+
34
+ For deeper questions about PromptLayer features, configuration, or API details, the PromptLayer docs site has an MCP server you can use for search. See https://docs.promptlayer.com/mcp for setup.
35
+ `.trim();
36
+
37
+ const server = new McpServer(
38
+ { name: "promptlayer-server", version: "1.0.0" },
39
+ { instructions: INSTRUCTIONS },
40
+ );
41
+ registerAllTools(server);
42
+
43
+ async function main() {
44
+ const transport = new StdioServerTransport();
45
+ await server.connect(transport);
46
+ console.error("PromptLayer MCP Server running on stdio");
47
+ }
48
+
49
+ main().catch((error) => {
50
+ console.error("Fatal error:", error);
51
+ process.exit(1);
52
+ });