@promptlayer/mcp-server 1.8.0 → 1.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/gcp/package.json CHANGED
@@ -22,5 +22,18 @@
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=20.0.0"
25
+ },
26
+ "overrides": {
27
+ "hono": "^4.12.14",
28
+ "@hono/node-server": "^1.19.13",
29
+ "express-rate-limit": "^8.2.2",
30
+ "express": {
31
+ "path-to-regexp": "^0.1.13"
32
+ },
33
+ "router": {
34
+ "path-to-regexp": "^8.4.0"
35
+ },
36
+ "qs": "^6.14.2",
37
+ "ajv": "^8.18.0"
25
38
  }
26
39
  }
package/gcp/src/index.ts CHANGED
@@ -90,6 +90,14 @@ const TOOL_HANDLERS: Record<string, ToolHandler> = {
90
90
  "get-workflow-labels": (c, { workflow_id_or_name }) =>
91
91
  c.getWorkflowLabels(workflow_id_or_name as string),
92
92
 
93
+ // Tool Registry
94
+ "list-tool-registries": (c) => c.listToolRegistries(),
95
+ "get-tool-registry": (c, { api_key: _, identifier, ...p }) =>
96
+ c.getToolRegistry(identifier as string, p),
97
+ "create-tool-registry": (c, a) => c.createToolRegistry(body(a)),
98
+ "create-tool-version": (c, { api_key: _, identifier, ...b }) =>
99
+ c.createToolVersion(identifier as string, b),
100
+
93
101
  // Folders
94
102
  "create-folder": (c, a) => c.createFolder(body(a)),
95
103
  "edit-folder": (c, { api_key: _, folder_id, ...b }) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptlayer/mcp-server",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Model Context Protocol server for PromptLayer",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -32,5 +32,13 @@
32
32
  },
33
33
  "engines": {
34
34
  "node": ">=18.0.0"
35
+ },
36
+ "overrides": {
37
+ "hono": "^4.12.14",
38
+ "@hono/node-server": "^1.19.13",
39
+ "express-rate-limit": "^8.2.2",
40
+ "path-to-regexp": "^8.4.0",
41
+ "qs": "^6.14.2",
42
+ "ajv": "^8.18.0"
35
43
  }
36
44
  }
package/src/client.ts CHANGED
@@ -99,6 +99,12 @@ export class PromptLayerClient {
99
99
  runWorkflow(name: string, body: Body) { return this.post(`/workflows/${this.enc(name)}/run`, body); }
100
100
  getWorkflowVersionExecutionResults(params: Body) { return this.get("/workflow-version-execution-results", params); }
101
101
 
102
+ // Tool Registry
103
+ listToolRegistries() { return this.get("/api/public/v2/tool-registry"); }
104
+ getToolRegistry(identifier: string, params?: Body) { return this.get(`/api/public/v2/tool-registry/${this.enc(identifier)}`, params); }
105
+ createToolRegistry(body: Body) { return this.post("/api/public/v2/tool-registry", body); }
106
+ createToolVersion(identifier: string, body: Body) { return this.post(`/api/public/v2/tool-registry/${this.enc(identifier)}/versions`, body); }
107
+
102
108
  // Folders
103
109
  createFolder(body: Body) { return this.post("/api/public/v2/folders", body); }
104
110
  editFolder(folderId: number, body: Body) { return this.patch(`/api/public/v2/folders/${folderId}`, body); }
package/src/handlers.ts CHANGED
@@ -139,6 +139,18 @@ export function registerAllTools(server: any) {
139
139
  (c, a) => c.getWorkflowLabels((a as { workflow_id_or_name: string }).workflow_id_or_name),
140
140
  (r) => { const labels = (r as { release_labels?: unknown[] }).release_labels; return `${labels?.length ?? 0} label(s) found`; });
141
141
 
142
+ // Tool Registry
143
+ reg(t["list-tool-registries"], (c) => c.listToolRegistries(),
144
+ (r) => { const tools = (r as { tool_registries?: unknown[] }).tool_registries; return `${tools?.length ?? 0} tool(s)`; });
145
+ reg(t["get-tool-registry"],
146
+ (c, a) => { const { api_key: _, identifier, ...p } = a as { identifier: string; api_key?: string } & Args; return c.getToolRegistry(identifier, p); },
147
+ (r) => { const t_ = (r as { tool_registry?: { name?: string } }).tool_registry; return `Tool "${t_?.name ?? ""}" retrieved`; });
148
+ reg(t["create-tool-registry"], (c, a) => c.createToolRegistry(body(a)),
149
+ (r) => { const t_ = (r as { tool_registry?: { name?: string; id?: number } }).tool_registry; return t_ ? `Tool "${t_.name}" created (ID: ${t_.id})` : "Tool created"; });
150
+ reg(t["create-tool-version"],
151
+ (c, a) => { const { api_key: _, identifier, ...b } = a as { identifier: string; api_key?: string } & Args; return c.createToolVersion(identifier, b); },
152
+ (r) => { const v = (r as { version?: { number?: number } }).version; return v ? `Version ${v.number} created` : "Version created"; });
153
+
142
154
  // Folders
143
155
  reg(t["create-folder"], (c, a) => c.createFolder(body(a)), () => "Folder created");
144
156
  reg(t["edit-folder"],
package/src/types.ts CHANGED
@@ -83,7 +83,12 @@ export const PublishPromptTemplateArgsSchema = z.object({
83
83
  prompt_name: z.string().describe("Name of the prompt template"),
84
84
  tags: z.array(z.string()).optional().describe("Tags to associate"),
85
85
  folder_id: z.number().int().optional().describe("Folder ID to publish into"),
86
- }).describe("Template metadata: prompt_name (required), tags, folder_id"),
86
+ is_snippet: z.boolean().optional().describe(
87
+ "Mark this template as a reusable snippet (referenced via @@@name@@@ in other prompts). " +
88
+ "Only takes effect when the prompt is first created — publishing a new version of an " +
89
+ "existing template will not flip this flag."
90
+ ),
91
+ }).describe("Template metadata: prompt_name (required), tags, folder_id, is_snippet"),
87
92
  prompt_version: z.object({
88
93
  prompt_template: z.record(z.unknown()).describe("The template content in chat ({type:'chat', messages:[...]}) or completion format"),
89
94
  commit_message: z.string().optional().describe("Commit message (max 72 chars)"),
@@ -218,44 +223,59 @@ export const CreateDatasetVersionFromFileArgsSchema = z.object({
218
223
  api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
219
224
  });
220
225
 
226
+ // ── Structured filter primitives (shared by request search + dataset-from-history) ──
227
+ // NOTE: value/filters use loose types (z.unknown()) because the backend validates
228
+ // operator-field compatibility at runtime. This is tracked as a known exception in
229
+ // scripts/diff-endpoints.ts.
230
+
231
+ const StructuredFilterSchema = z.object({
232
+ field: z.enum([
233
+ "pl_id", "prompt_id", "engine", "provider_type", "input_text", "output_text",
234
+ "prompt_version_number", "input_tokens", "output_tokens", "cost", "latency_ms",
235
+ "request_start_time", "request_end_time", "status",
236
+ "is_json", "is_tool_call", "is_plain_text", "has_trace",
237
+ "tags", "metadata_keys", "metadata", "tool_names",
238
+ "output", "output_keys", "input_variables", "input_variable_keys",
239
+ "turn_count", "tool_call_count",
240
+ ]).describe("Request log field to filter on"),
241
+ operator: z.enum([
242
+ "is", "is_not", "in", "not_in",
243
+ "contains", "not_contains", "starts_with", "ends_with",
244
+ "eq", "neq", "gt", "gte", "lt", "lte", "between",
245
+ "before", "after",
246
+ "is_true", "is_false", "is_empty", "is_not_empty",
247
+ "is_null", "is_not_null",
248
+ "key_equals", "key_not_equals", "key_contains",
249
+ ]).describe("Filter operator (availability depends on field type)"),
250
+ value: z.unknown().optional().describe("Filter value (type depends on operator)"),
251
+ nested_key: z.string().optional().describe("Key name for nested field operators (metadata, output, input_variables)"),
252
+ });
253
+
254
+ const StructuredFilterGroupSchema: z.ZodType = z.object({
255
+ logic: z.enum(["AND", "OR"]).optional().describe("Logical operator (default: AND)"),
256
+ filters: z.array(z.union([StructuredFilterSchema, z.lazy(() => StructuredFilterGroupSchema)])).describe("Filters or nested filter groups"),
257
+ });
258
+
221
259
  // ── Create Dataset Version from Filter Params (POST /api/public/v2/dataset-versions/from-filter-params)
222
260
 
223
261
  export const CreateDatasetVersionFromFilterParamsArgsSchema = z.object({
224
- dataset_group_id: z.number().int().describe("Dataset group ID"),
225
- variables_to_parse: z.array(z.string()).optional().describe("Variables to extract from request logs"),
226
- tags: z.array(z.string()).optional().describe("Filter by tags (simple tag filter)"),
227
- metadata: z.record(z.string()).optional().describe("Simple metadata key-value filter"),
228
- start_time: z.string().optional().describe("Start time filter (ISO 8601)"),
229
- end_time: z.string().optional().describe("End time filter (ISO 8601)"),
230
- id: z.number().int().optional().describe("Filter by specific request log ID"),
231
- limit: z.number().int().optional().describe("Limit number of request logs to pull"),
232
- tags_and: z.array(z.string()).optional().describe("Filter by tags (AND logic all must match)"),
233
- tags_or: z.array(z.string()).optional().describe("Filter by tags (OR logic — any can match)"),
234
- metadata_and: z.array(z.object({ key: z.string(), value: z.string() })).optional().describe("Metadata filters with AND logic (all must match). Each item: {key, value}"),
235
- metadata_or: z.array(z.object({ key: z.string(), value: z.string() })).optional().describe("Metadata filters with OR logic (any can match). Each item: {key, value}"),
236
- scores: z.array(z.object({
237
- name: z.string().describe("Score name"),
238
- operator: z.enum([">", "<", ">=", "<=", "="]).describe("Comparison operator"),
239
- value: z.number().int().describe("Score value to compare against"),
240
- })).optional().describe("Filter by score criteria. Each item: {name, operator, value}"),
241
- prompt_templates_include: z.array(z.object({
242
- name: z.string().describe("Prompt template name"),
243
- version_numbers: z.array(z.number().int()).optional().describe("Filter to specific version numbers"),
244
- labels: z.array(z.string()).optional().describe("Filter to specific labels"),
245
- })).optional().describe("Include request logs matching these prompt templates. This is the primary way to filter by prompt — use the prompt name (not ID)."),
246
- prompt_templates_exclude: z.array(z.object({
247
- name: z.string().describe("Prompt template name"),
248
- version_numbers: z.array(z.number().int()).optional().describe("Filter to specific version numbers"),
249
- labels: z.array(z.string()).optional().describe("Filter to specific labels"),
250
- })).optional().describe("Exclude request logs matching these prompt templates"),
251
- starred: z.boolean().optional().describe("Filter by starred status"),
252
- status: z.array(z.enum(["SUCCESS", "WARNING", "ERROR"])).optional().describe("Filter by request log status"),
262
+ dataset_group_id: z.number().int().describe("Dataset group ID to create the new version under"),
263
+ request_log_ids: z.array(z.number().int().positive()).optional().describe(
264
+ "Static snapshot mode: pin the dataset to an explicit list of request log IDs (capped at 50,000). " +
265
+ "≤50 IDs run synchronously; >50 are processed asynchronously. " +
266
+ "Datasets created this way are not refreshable."
267
+ ),
268
+ filter_group: StructuredFilterGroupSchema.optional().describe(
269
+ "Structured filter mode: same shape as search-request-logs (AND/OR groups of field/operator/value filters). " +
270
+ "Always processed asynchronously. Persisted on the dataset so refresh_dataset can replay it."
271
+ ),
272
+ q: z.string().optional().describe("Free-text search query applied alongside filter_group"),
253
273
  sort_by: z.enum([
254
- "request_start_time", "input_tokens", "output_tokens", "price",
255
- "score", "latency", "prompt_name", "status",
274
+ "request_start_time", "input_tokens", "output_tokens", "cost",
275
+ "latency_ms", "status", "turn_count", "tool_call_count",
256
276
  ]).optional().describe("Sort field"),
257
- sort_order: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
258
- order_by_random: z.boolean().optional().describe("Random ordering (requires limit)"),
277
+ sort_order: z.enum(["asc", "desc"]).optional().describe("Sort direction (defaults to desc when sort_by is set)"),
278
+ variables_to_parse: z.array(z.string()).optional().describe("Input variable names to extract as dataset columns"),
259
279
  api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
260
280
  });
261
281
 
@@ -496,6 +516,34 @@ export const GetWorkflowLabelsArgsSchema = z.object({
496
516
  });
497
517
 
498
518
 
519
+ // ── Tool Registry ────────────────────────────────────────────────────
520
+
521
+ export const ListToolRegistriesArgsSchema = z.object({
522
+ api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
523
+ });
524
+
525
+ export const GetToolRegistryArgsSchema = z.object({
526
+ identifier: z.string().describe("Tool ID (numeric) or name"),
527
+ label: z.string().optional().describe("Resolve version by label name (e.g. 'production')"),
528
+ version: z.number().int().optional().describe("Resolve by specific version number"),
529
+ api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
530
+ });
531
+
532
+ export const CreateToolRegistryArgsSchema = z.object({
533
+ name: z.string().describe("Tool name (unique per workspace)"),
534
+ tool_definition: z.record(z.unknown()).describe("Tool definition in OpenAI function-calling format: {type: 'function', function: {name, description, parameters}}"),
535
+ folder_id: z.number().int().optional().describe("Folder ID to place tool in"),
536
+ commit_message: z.string().optional().describe("Commit message for the initial version"),
537
+ api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
538
+ });
539
+
540
+ export const CreateToolVersionArgsSchema = z.object({
541
+ identifier: z.string().describe("Tool ID (numeric) or name"),
542
+ tool_definition: z.record(z.unknown()).describe("Updated tool definition in OpenAI function-calling format"),
543
+ commit_message: z.string().optional().describe("Commit message describing what changed"),
544
+ api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
545
+ });
546
+
499
547
  export const CreateFolderArgsSchema = z.object({
500
548
  name: z.string().describe("Folder name (unique within parent)"),
501
549
  parent_id: z.number().int().optional().describe("Parent folder ID (root if omitted)"),
@@ -570,43 +618,18 @@ export const ResolveFolderIdArgsSchema = z.object({
570
618
 
571
619
 
572
620
  // ── Search Request Logs (POST /api/public/v2/requests/search) ────────────
573
- // NOTE: The StructuredFilter and StructuredFilterGroup schemas use loose types
574
- // (z.unknown()) for value/filters because the backend validates operator-field
575
- // compatibility at runtime. This is tracked as a known exception in scripts/diff-endpoints.ts.
576
-
577
- const StructuredFilterSchema = z.object({
578
- field: z.enum([
579
- "pl_id", "prompt_id", "engine", "provider_type", "input_text", "output_text",
580
- "prompt_version_number", "input_tokens", "output_tokens", "cost", "latency_ms",
581
- "request_start_time", "request_end_time", "status",
582
- "is_json", "is_tool_call", "is_plain_text",
583
- "tags", "metadata_keys", "metadata", "tool_names",
584
- "output", "output_keys", "input_variables", "input_variable_keys",
585
- ]).describe("Request log field to filter on"),
586
- operator: z.enum([
587
- "is", "is_not", "in", "not_in",
588
- "contains", "not_contains", "starts_with", "ends_with",
589
- "eq", "neq", "gt", "gte", "lt", "lte", "between",
590
- "before", "after",
591
- "is_true", "is_false", "is_empty", "is_not_empty",
592
- "is_null", "is_not_null",
593
- "key_equals", "key_not_equals", "key_contains",
594
- ]).describe("Filter operator (availability depends on field type)"),
595
- value: z.unknown().optional().describe("Filter value (type depends on operator)"),
596
- nested_key: z.string().optional().describe("Key name for nested field operators (metadata, output, input_variables)"),
597
- });
598
-
599
- const StructuredFilterGroupSchema: z.ZodType = z.object({
600
- logic: z.enum(["AND", "OR"]).optional().describe("Logical operator (default: AND)"),
601
- filters: z.array(z.union([StructuredFilterSchema, z.lazy(() => StructuredFilterGroupSchema)])).describe("Filters or nested filter groups"),
602
- });
621
+ // (StructuredFilter / StructuredFilterGroup are defined higher up so the
622
+ // dataset-from-filter-params endpoint can reuse them.)
603
623
 
604
624
  export const SearchRequestLogsArgsSchema = z.object({
605
625
  filter_group: StructuredFilterGroupSchema.optional().describe("Filter group with AND/OR logic, supports nesting. Wrap multiple filters in an AND group."),
606
626
  q: z.string().optional().describe("Free-text search across prompt input and LLM output"),
607
627
  page: z.number().int().optional().describe("Page number (default: 1)"),
608
628
  per_page: z.number().int().optional().describe("Items per page (max: 25)"),
609
- sort_by: z.enum(["request_start_time", "input_tokens", "output_tokens", "cost", "latency_ms", "status"]).optional().describe("Sort field"),
629
+ sort_by: z.enum([
630
+ "request_start_time", "input_tokens", "output_tokens", "cost", "latency_ms", "status",
631
+ "turn_count", "tool_call_count",
632
+ ]).optional().describe("Sort field"),
610
633
  sort_order: z.enum(["asc", "desc"]).optional().describe("Sort direction (must be provided with sort_by)"),
611
634
  include_prompt_name: z.boolean().optional().describe("Include prompt template name in results"),
612
635
  api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
@@ -687,11 +710,14 @@ export const TOOL_DEFINITIONS = {
687
710
  name: "publish-prompt-template",
688
711
  description:
689
712
  "Create a new version of a prompt template. " +
690
- "Body has two required objects: prompt_template (with prompt_name, tags, folder_id) and " +
713
+ "Body has two required objects: prompt_template (with prompt_name, tags, folder_id, is_snippet) and " +
691
714
  "prompt_version (with prompt_template content in chat/completion format, commit_message, metadata). " +
692
715
  "Optionally assign release_labels. " +
693
716
  "IMPORTANT: If the prompt uses snippets, preserve @@@snippet_name@@@ markers in the content. " +
694
- "Do not inline snippet text — this breaks snippet references.",
717
+ "Do not inline snippet text — this breaks snippet references. " +
718
+ "To create a snippet (a reusable fragment referenced by other prompts), set " +
719
+ "prompt_template.is_snippet=true on the first publish. The flag is only honored on initial " +
720
+ "creation; later versions cannot flip an existing template into a snippet or vice versa.",
695
721
  inputSchema: PublishPromptTemplateArgsSchema,
696
722
  annotations: { readOnlyHint: false },
697
723
  },
@@ -736,9 +762,9 @@ export const TOOL_DEFINITIONS = {
736
762
  "Operators by field type:\n" +
737
763
  " - String fields (engine, provider_type): is, is_not, in, not_in\n" +
738
764
  " - Text fields (input_text, output_text): contains, not_contains, starts_with, ends_with\n" +
739
- " - Numeric fields (cost, latency_ms, input_tokens, output_tokens): eq, neq, gt, gte, lt, lte, between (value=[min,max]), is_null, is_not_null\n" +
765
+ " - Numeric fields (cost, latency_ms, input_tokens, output_tokens, turn_count, tool_call_count): eq, neq, gt, gte, lt, lte, between (value=[min,max]), is_null, is_not_null\n" +
740
766
  " - Datetime fields (request_start_time, request_end_time): is, before, after, between (value=[start,end] as ISO 8601)\n" +
741
- " - Boolean fields (is_json, is_tool_call, is_plain_text): is_true, is_false\n" +
767
+ " - Boolean fields (is_json, is_tool_call, is_plain_text, has_trace): is_true, is_false\n" +
742
768
  " - Array fields (tags, metadata_keys, tool_names, output_keys, input_variable_keys): contains, not_contains, in, not_in, is_empty, is_not_empty\n" +
743
769
  " - Nested fields (metadata, output, input_variables): key_equals, key_not_equals, key_contains, in, not_in, is_empty, is_not_empty — requires nested_key\n\n" +
744
770
  "EXAMPLES:\n" +
@@ -821,7 +847,12 @@ export const TOOL_DEFINITIONS = {
821
847
  },
822
848
  "create-dataset-version-from-filter-params": {
823
849
  name: "create-dataset-version-from-filter-params",
824
- description: "Create a dataset version from request log history using filter criteria. Populated asynchronously.",
850
+ description:
851
+ "Create a dataset version from request log history. Two modes:\n" +
852
+ " 1. request_log_ids — pin to an explicit list of IDs (≤50 sync, >50 async). Snapshot, not refreshable.\n" +
853
+ " 2. filter_group (+ optional q) — structured AND/OR filter, same shape as search-request-logs. " +
854
+ "Async; persisted on the dataset so refresh_dataset can replay it.\n" +
855
+ "Use variables_to_parse to extract specific input variables as dataset columns.",
825
856
  inputSchema: CreateDatasetVersionFromFilterParamsArgsSchema,
826
857
  annotations: { readOnlyHint: false },
827
858
  },
@@ -974,6 +1005,32 @@ export const TOOL_DEFINITIONS = {
974
1005
  annotations: { readOnlyHint: true },
975
1006
  },
976
1007
 
1008
+ // ── Tool Registry ───────────────────────────────────────────────────
1009
+ "list-tool-registries": {
1010
+ name: "list-tool-registries",
1011
+ description: "List all tools in the Tool Registry for the workspace. Returns tool names, IDs, and metadata.",
1012
+ inputSchema: ListToolRegistriesArgsSchema,
1013
+ annotations: { readOnlyHint: true },
1014
+ },
1015
+ "get-tool-registry": {
1016
+ name: "get-tool-registry",
1017
+ description: "Get a tool from the Tool Registry by ID or name. Optionally resolve a specific version by label or version number. Returns the tool definition and metadata.",
1018
+ inputSchema: GetToolRegistryArgsSchema,
1019
+ annotations: { readOnlyHint: true },
1020
+ },
1021
+ "create-tool-registry": {
1022
+ name: "create-tool-registry",
1023
+ description: "Create a new tool in the Tool Registry with an initial version. The tool definition should be in OpenAI function-calling format.",
1024
+ inputSchema: CreateToolRegistryArgsSchema,
1025
+ annotations: { readOnlyHint: false },
1026
+ },
1027
+ "create-tool-version": {
1028
+ name: "create-tool-version",
1029
+ description: "Create a new version of an existing tool in the Tool Registry. Each version is immutable — this adds a new version with the updated definition.",
1030
+ inputSchema: CreateToolVersionArgsSchema,
1031
+ annotations: { readOnlyHint: false },
1032
+ },
1033
+
977
1034
  // ── Folders ─────────────────────────────────────────────────────────
978
1035
  "create-folder": {
979
1036
  name: "create-folder",