@promptlayer/mcp-server 1.0.0 → 1.1.1

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,26 @@
1
+ {
2
+ "name": "promptlayer-mcp-gcp",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=build/server.mjs --packages=external",
7
+ "gcp-build": "",
8
+ "start": "node build/server.mjs",
9
+ "dev": "tsx src/index.ts"
10
+ },
11
+ "dependencies": {
12
+ "@modelcontextprotocol/sdk": "^1.25.3",
13
+ "express": "^4.21.0",
14
+ "zod": "^3.25.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/express": "^4.17.21",
18
+ "@types/node": "^22.15.0",
19
+ "esbuild": "^0.25.0",
20
+ "tsx": "^4.20.0",
21
+ "typescript": "^5.7.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=20.0.0"
25
+ }
26
+ }
@@ -0,0 +1,187 @@
1
+ import express from "express";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { TOOL_DEFINITIONS } from "../../src/types.js";
5
+ import { PromptLayerClient } from "../../src/client.js";
6
+
7
+ // ── Tool handler mapping ────────────────────────────────────────────────────
8
+
9
+ type Args = Record<string, unknown>;
10
+ type ToolHandler = (client: PromptLayerClient, args: Args) => Promise<unknown>;
11
+
12
+ function body(args: Args): Args {
13
+ const { api_key: _, ...rest } = args;
14
+ return rest;
15
+ }
16
+
17
+ const TOOL_HANDLERS: Record<string, ToolHandler> = {
18
+ // Prompt Templates
19
+ "get-prompt-template": (c, { api_key: _, prompt_name, ...p }) =>
20
+ c.getPromptTemplate(prompt_name as string, p),
21
+ "get-prompt-template-raw": (c, { api_key: _, identifier, ...p }) =>
22
+ c.getPromptTemplateRaw(identifier as string, p),
23
+ "list-prompt-templates": (c, a) => c.listPromptTemplates(body(a)),
24
+ "publish-prompt-template": (c, a) => c.publishPromptTemplate(body(a)),
25
+ "list-prompt-template-labels": (c, { identifier }) =>
26
+ c.listPromptTemplateLabels(identifier as string),
27
+ "create-prompt-label": (c, { api_key: _, prompt_id, ...b }) =>
28
+ c.createPromptLabel(prompt_id as number, b),
29
+ "move-prompt-label": (c, { api_key: _, prompt_label_id, ...b }) =>
30
+ c.movePromptLabel(prompt_label_id as number, b),
31
+ "delete-prompt-label": (c, { prompt_label_id }) =>
32
+ c.deletePromptLabel(prompt_label_id as number),
33
+ "get-snippet-usage": (c, { api_key: _, identifier, ...p }) =>
34
+ c.getSnippetUsage(identifier as string, p),
35
+
36
+ // Request Logs
37
+ "search-request-logs": (c, a) => c.searchRequestLogs(body(a)),
38
+ "get-request": (c, { request_id }) => c.getRequest(request_id as number),
39
+
40
+ // Tracking
41
+ "log-request": (c, a) => c.logRequest(body(a)),
42
+ "create-spans-bulk": (c, a) => c.createSpansBulk(body(a)),
43
+
44
+ // Datasets
45
+ "list-datasets": (c, a) => c.listDatasets(body(a)),
46
+ "create-dataset-group": (c, a) => c.createDatasetGroup(body(a)),
47
+ "create-dataset-version-from-file": (c, a) => c.createDatasetVersionFromFile(body(a)),
48
+ "create-dataset-version-from-filter-params": (c, a) => c.createDatasetVersionFromFilterParams(body(a)),
49
+
50
+ // Evaluations
51
+ "list-evaluations": (c, a) => c.listEvaluations(body(a)),
52
+ "create-report": (c, a) => c.createReport(body(a)),
53
+ "run-report": (c, { api_key: _, report_id, ...b }) =>
54
+ c.runReport(report_id as number, b),
55
+ "get-report": (c, { report_id }) => c.getReport(report_id as number),
56
+ "get-report-score": (c, { report_id }) => c.getReportScore(report_id as number),
57
+ "update-report-score-card": (c, { api_key: _, report_id, ...b }) =>
58
+ c.updateReportScoreCard(report_id as number, b),
59
+ "delete-reports-by-name": (c, { report_name }) =>
60
+ c.deleteReportsByName(report_name as string),
61
+
62
+ // Agents
63
+ "list-workflows": (c, a) => c.listWorkflows(body(a)),
64
+ "create-workflow": (c, a) => c.createWorkflow(body(a)),
65
+ "patch-workflow": (c, { api_key: _, workflow_id_or_name, ...b }) =>
66
+ c.patchWorkflow(workflow_id_or_name as string, b),
67
+ "run-workflow": (c, { api_key: _, workflow_name, ...b }) =>
68
+ c.runWorkflow(workflow_name as string, b),
69
+ "get-workflow-version-execution-results": (c, a) =>
70
+ c.getWorkflowVersionExecutionResults(body(a)),
71
+ "get-workflow": (c, { workflow_id_or_name }) =>
72
+ c.getWorkflow(workflow_id_or_name as string),
73
+
74
+ // Folders
75
+ "create-folder": (c, a) => c.createFolder(body(a)),
76
+ "edit-folder": (c, { api_key: _, folder_id, ...b }) =>
77
+ c.editFolder(folder_id as number, b),
78
+ "get-folder-entities": (c, a) => c.getFolderEntities(body(a)),
79
+ "move-folder-entities": (c, a) => c.moveFolderEntities(body(a)),
80
+ "delete-folder-entities": (c, a) => c.deleteFolderEntities(body(a)),
81
+ "resolve-folder-id": (c, a) => c.resolveFolderId(body(a)),
82
+ };
83
+
84
+ // ── Instructions ────────────────────────────────────────────────────────────
85
+
86
+ const INSTRUCTIONS = `
87
+ PromptLayer is a prompt management and observability platform. This MCP server lets you manage PromptLayer resources.
88
+
89
+ ## Key entities and naming
90
+
91
+ - **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.
92
+ - **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.
93
+ - **Release label**: A pointer (e.g. "prod", "staging") attached to a specific prompt version. Move labels between versions for deployment.
94
+ - **Agent** (backend name: workflow): A multi-step pipeline of nodes. Each node has a type, configuration, and dependencies. Agents are versioned like prompts.
95
+ - **Evaluation pipeline** (backend name: report): Runs evaluation columns against a dataset and produces scores. Columns can be LLM assertions, code execution, comparisons, etc.
96
+ - **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.
97
+ - **Folder**: Organizes prompts, agents, datasets, evaluations, and other entities into a hierarchy.
98
+
99
+ ## Working with prompts and snippets
100
+
101
+ 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.
102
+
103
+ 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.
104
+
105
+ 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.
106
+
107
+ ## Working with evaluations
108
+
109
+ 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.
110
+
111
+ ## Additional documentation
112
+
113
+ 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.
114
+ `.trim();
115
+
116
+ function resolveApiKey(argKey?: string, headerKey?: string): string {
117
+ const key = argKey || headerKey;
118
+ if (!key) {
119
+ throw new Error(
120
+ "No API key provided. Set authorization_token in your MCP config " +
121
+ "or pass api_key to each tool call.",
122
+ );
123
+ }
124
+ if (!key.startsWith("pl_")) {
125
+ throw new Error("Invalid API key format. PromptLayer API keys must start with 'pl_'.");
126
+ }
127
+ return key;
128
+ }
129
+
130
+ function createMcpServer(defaultApiKey?: string): McpServer {
131
+ const server = new McpServer(
132
+ { name: "promptlayer-server", version: "1.0.0" },
133
+ { instructions: INSTRUCTIONS },
134
+ );
135
+
136
+ for (const [name, def] of Object.entries(TOOL_DEFINITIONS)) {
137
+ const handler = TOOL_HANDLERS[name];
138
+ if (!handler) continue;
139
+
140
+ server.tool(name, def.description, def.inputSchema.shape, async (args: Args) => {
141
+ try {
142
+ const apiKey = resolveApiKey(args.api_key as string | undefined, defaultApiKey);
143
+ const client = new PromptLayerClient(apiKey);
144
+ const result = await handler(client, args);
145
+ return {
146
+ content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
147
+ };
148
+ } catch (error) {
149
+ return {
150
+ content: [{
151
+ type: "text" as const,
152
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
153
+ }],
154
+ isError: true,
155
+ };
156
+ }
157
+ });
158
+ }
159
+
160
+ return server;
161
+ }
162
+
163
+ // ── Express app ─────────────────────────────────────────────────────────────
164
+
165
+ const app = express();
166
+ app.use(express.json());
167
+
168
+ async function handleMcp(req: express.Request, res: express.Response) {
169
+ const auth = req.headers.authorization;
170
+ const apiKey = auth?.startsWith("Bearer ") ? auth.slice(7) : undefined;
171
+
172
+ const transport = new StreamableHTTPServerTransport({
173
+ sessionIdGenerator: undefined,
174
+ });
175
+ const server = createMcpServer(apiKey);
176
+ await server.connect(transport);
177
+ await transport.handleRequest(req, res, req.body);
178
+ }
179
+
180
+ app.post("/mcp", handleMcp);
181
+ app.get("/mcp", handleMcp);
182
+ app.delete("/mcp", handleMcp);
183
+
184
+ const PORT = parseInt(process.env.PORT || "8080");
185
+ app.listen(PORT, () => {
186
+ console.log(`PromptLayer MCP server listening on port ${PORT}`);
187
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "esnext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["es2022"],
7
+ "noEmit": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "resolveJsonModule": true,
12
+ "forceConsistentCasingInFileNames": true
13
+ },
14
+ "include": ["src/**/*.ts"]
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptlayer/mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Model Context Protocol server for PromptLayer",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -105,6 +105,33 @@ const KNOWN_EXCEPTIONS: string[] = [
105
105
  // The OpenAPI spec defines scores as object but backend uses a list of {name, operator, value}.
106
106
  // Our array type matches what the backend actually accepts.
107
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]',
108
135
  ];
109
136
  // ─────────────────────────────────────────────────────────────────────────────
110
137
 
@@ -28,6 +28,8 @@ const TOOL_TO_ENDPOINT: Record<string, { method: string; path: string }> = {
28
28
  "move-prompt-label": { method: "PATCH", path: "/prompt-labels/{prompt_label_id}" },
29
29
  "delete-prompt-label": { method: "DELETE", path: "/prompt-labels/{prompt_label_id}" },
30
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}" },
31
33
  "log-request": { method: "POST", path: "/log-request" },
32
34
  "create-spans-bulk": { method: "POST", path: "/spans-bulk" },
33
35
  "list-datasets": { method: "GET", path: "/api/public/v2/datasets" },
package/src/client.ts CHANGED
@@ -55,6 +55,10 @@ export class PromptLayerClient {
55
55
  deletePromptLabel(labelId: number) { return this.del(`/prompt-labels/${labelId}`); }
56
56
  getSnippetUsage(id: string, params?: Body) { return this.get(`/prompt-templates/${this.enc(id)}/snippet-usage`, params); }
57
57
 
58
+ // Request Logs
59
+ searchRequestLogs(body: Body) { return this.post("/api/public/v2/requests/search", body); }
60
+ getRequest(requestId: number) { return this.get(`/api/public/v2/requests/${requestId}`); }
61
+
58
62
  // Tracking
59
63
  logRequest(body: Body) { return this.post("/log-request", body); }
60
64
  createSpansBulk(body: Body) { return this.post("/spans-bulk", body); }
package/src/handlers.ts CHANGED
@@ -50,6 +50,13 @@ export function registerAllTools(server: any) {
50
50
  (c, a) => { const { api_key: _, identifier, ...p } = a as { identifier: string; api_key?: string } & Args; return c.getSnippetUsage(identifier, p); },
51
51
  (r) => `${Array.isArray(r) ? r.length : 0} prompt(s) using snippet`);
52
52
 
53
+ // Request Logs
54
+ reg(t["search-request-logs"], (c, a) => c.searchRequestLogs(body(a)),
55
+ (r) => { const { items, total, page, pages } = r as { items?: unknown[]; total?: number; page?: number; pages?: number }; return `${items?.length ?? 0} request(s) (page ${page ?? 1}/${pages ?? 1}, total ${total ?? "?"})`; });
56
+ reg(t["get-request"],
57
+ (c, a) => c.getRequest((a as { request_id: number }).request_id),
58
+ (r) => { const { request_id, model } = r as { request_id?: number; model?: string }; return `Request ${request_id ?? ""}${model ? ` (${model})` : ""} retrieved`; });
59
+
53
60
  // Tracking
54
61
  reg(t["log-request"], (c, a) => c.logRequest(body(a)),
55
62
  (r) => { const id = (r as { request_id?: unknown }).request_id; return id ? `Logged (ID: ${id})` : "Logged"; });
package/src/types.ts CHANGED
@@ -71,6 +71,7 @@ export const ListPromptTemplatesArgsSchema = z.object({
71
71
  per_page: z.number().int().optional().describe("Items per page"),
72
72
  label: z.string().optional().describe("Filter by release label"),
73
73
  name: z.string().optional().describe("Filter by name (case-insensitive partial match)"),
74
+ tags: z.union([z.string(), z.array(z.string())]).optional().describe("Filter by tag(s). Only templates whose tags contain all specified values are returned."),
74
75
  status: z.enum(["active", "deleted", "all"]).optional().describe("Filter by status (default: 'active')"),
75
76
  workspace_id: z.number().int().optional().describe("Workspace ID"),
76
77
  api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
@@ -434,7 +435,11 @@ const EntityTypeEnum = z.enum([
434
435
  export const GetFolderEntitiesArgsSchema = z.object({
435
436
  folder_id: z.number().int().optional().describe("Folder ID to list (root if omitted)"),
436
437
  filter_type: z.union([EntityTypeEnum, z.array(EntityTypeEnum)]).optional().describe("Entity type(s) to include (default: all)"),
437
- search_query: z.string().optional().describe("Search by name (case-insensitive partial match)"),
438
+ search_query: z.string().optional().describe("Search by name (case-insensitive partial match). For prompts, also searches across prompt version content."),
439
+ semantic_search: z.boolean().optional().describe("Enable semantic (vector) search instead of text matching. Requires search_query to be set. Currently supports prompts and folders."),
440
+ semantic_search_top_k: z.number().int().optional().describe("Max results from semantic search (default: 100, range: 1-500)"),
441
+ semantic_search_threshold: z.number().optional().describe("Max distance threshold for semantic search results (range: (0, 2])"),
442
+ tags: z.array(z.string()).optional().describe("Filter entities by tags (AND logic — all must match). Applies to prompts, workflows, datasets, evaluations."),
438
443
  flatten: z.boolean().optional().describe("Flatten nested folder hierarchy (default: false)"),
439
444
  include_metadata: z.boolean().optional().describe("Include entity metadata like latest_version_number (default: false)"),
440
445
  workspace_id: z.number().int().optional().describe("Workspace ID"),
@@ -480,6 +485,58 @@ export const ResolveFolderIdArgsSchema = z.object({
480
485
  });
481
486
 
482
487
 
488
+ // ── Search Request Logs (POST /api/public/v2/requests/search) ────────────
489
+ // NOTE: The StructuredFilter and StructuredFilterGroup schemas use loose types
490
+ // (z.unknown()) for value/filters because the backend validates operator-field
491
+ // compatibility at runtime. This is tracked as a known exception in scripts/diff-endpoints.ts.
492
+
493
+ const StructuredFilterSchema = z.object({
494
+ field: z.enum([
495
+ "pl_id", "prompt_id", "engine", "provider_type", "input_text", "output_text",
496
+ "prompt_version_number", "input_tokens", "output_tokens", "cost", "latency_ms",
497
+ "request_start_time", "request_end_time", "status",
498
+ "is_json", "is_tool_call", "is_plain_text",
499
+ "tags", "metadata_keys", "metadata", "tool_names",
500
+ "output", "output_keys", "input_variables", "input_variable_keys",
501
+ ]).describe("Request log field to filter on"),
502
+ operator: z.enum([
503
+ "is", "is_not", "in", "not_in",
504
+ "contains", "not_contains", "starts_with", "ends_with",
505
+ "eq", "neq", "gt", "gte", "lt", "lte", "between",
506
+ "before", "after",
507
+ "is_true", "is_false", "is_empty", "is_not_empty",
508
+ "is_null", "is_not_null",
509
+ "key_equals", "key_not_equals", "key_contains",
510
+ ]).describe("Filter operator (availability depends on field type)"),
511
+ value: z.unknown().optional().describe("Filter value (type depends on operator)"),
512
+ nested_key: z.string().optional().describe("Key name for nested field operators (metadata, output, input_variables)"),
513
+ });
514
+
515
+ const StructuredFilterGroupSchema: z.ZodType = z.object({
516
+ logic: z.enum(["AND", "OR"]).optional().describe("Logical operator (default: AND)"),
517
+ filters: z.array(z.union([StructuredFilterSchema, z.lazy(() => StructuredFilterGroupSchema)])).describe("Filters or nested filter groups"),
518
+ });
519
+
520
+ export const SearchRequestLogsArgsSchema = z.object({
521
+ filters: z.array(StructuredFilterSchema).optional().describe("Structured filters (combined with AND logic)"),
522
+ filter_group: StructuredFilterGroupSchema.optional().describe("Filter group with AND/OR logic, supports nesting"),
523
+ q: z.string().optional().describe("Free-text search across prompt input and LLM output"),
524
+ page: z.number().int().optional().describe("Page number (default: 1)"),
525
+ per_page: z.number().int().optional().describe("Items per page (max: 25)"),
526
+ sort_by: z.enum(["request_start_time", "input_tokens", "output_tokens", "cost", "latency_ms", "status"]).optional().describe("Sort field"),
527
+ sort_order: z.enum(["asc", "desc"]).optional().describe("Sort direction (must be provided with sort_by)"),
528
+ include_prompt_name: z.boolean().optional().describe("Include prompt template name in results"),
529
+ api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
530
+ });
531
+
532
+ // ── Get Request (GET /api/public/v2/requests/{request_id}) ───────────────
533
+
534
+ export const GetRequestArgsSchema = z.object({
535
+ request_id: z.number().int().describe("Request ID to retrieve"),
536
+ api_key: z.string().optional().describe("PromptLayer API key (optional, defaults to PROMPTLAYER_API_KEY env var)"),
537
+ });
538
+
539
+
483
540
  export type GetPromptTemplateParams = Omit<
484
541
  z.infer<typeof GetPromptTemplateArgsSchema>,
485
542
  "prompt_name" | "api_key"
@@ -563,6 +620,40 @@ export const TOOL_DEFINITIONS = {
563
620
  annotations: { readOnlyHint: true },
564
621
  },
565
622
 
623
+ // ── Request Logs ──────────────────────────────────────────────────
624
+ "search-request-logs": {
625
+ name: "search-request-logs",
626
+ description:
627
+ "Search and filter request logs using structured filters, free-text search, and sorting. " +
628
+ "Rate limited to 10 req/min, max 25 results/page.\n\n" +
629
+ "FILTER SYNTAX: Each filter is {field, operator, value, nested_key?}.\n" +
630
+ "Operators by field type:\n" +
631
+ " - String fields (engine, provider_type): is, is_not, in, not_in\n" +
632
+ " - Text fields (input_text, output_text): contains, not_contains, starts_with, ends_with\n" +
633
+ " - 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" +
634
+ " - Datetime fields (request_start_time, request_end_time): is, before, after, between (value=[start,end] as ISO 8601)\n" +
635
+ " - Boolean fields (is_json, is_tool_call, is_plain_text): is_true, is_false\n" +
636
+ " - Array fields (tags, metadata_keys, tool_names, output_keys, input_variable_keys): contains, not_contains, in, not_in, is_empty, is_not_empty\n" +
637
+ " - 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" +
638
+ "EXAMPLES:\n" +
639
+ ' Find GPT-4o requests: {filters: [{field:"engine", operator:"is", value:"gpt-4o"}]}\n' +
640
+ ' Expensive requests: {filters: [{field:"cost", operator:"gte", value:0.10}]}\n' +
641
+ ' By metadata: {filters: [{field:"metadata", operator:"key_equals", value:"customer_123", nested_key:"user_id"}]}\n' +
642
+ ' Free-text search: {q: "refund policy"}\n' +
643
+ ' Complex AND/OR: {filter_group: {logic:"OR", filters: [{field:"tags", operator:"contains", value:"prod"}, {logic:"AND", filters: [...]}]}}',
644
+ inputSchema: SearchRequestLogsArgsSchema,
645
+ annotations: { readOnlyHint: true },
646
+ },
647
+ "get-request": {
648
+ name: "get-request",
649
+ description:
650
+ "Retrieve a single request's full payload by ID, returned as a prompt blueprint. " +
651
+ "Includes the prompt template content, model configuration, provider, token counts, " +
652
+ "cost, and timing data. Useful for debugging, replaying requests, or extracting data for evaluations.",
653
+ inputSchema: GetRequestArgsSchema,
654
+ annotations: { readOnlyHint: true },
655
+ },
656
+
566
657
  // ── Tracking ────────────────────────────────────────────────────────
567
658
  "log-request": {
568
659
  name: "log-request",
@@ -1,16 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm run build:*)",
5
- "Bash(npm install:*)",
6
- "Bash(npm run sync:check:*)",
7
- "WebSearch",
8
- "WebFetch(domain:blog.modelcontextprotocol.io)",
9
- "Bash(npx tsx:*)",
10
- "WebFetch(domain:docs.promptlayer.com)",
11
- "Bash(git add:*)",
12
- "Bash(git commit:*)",
13
- "Bash(git push:*)"
14
- ]
15
- }
16
- }