@heilgar/pest-mcp 0.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pest contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @heilgar/pest-mcp
2
+
3
+ Prompt Evaluation & Scoring Toolkit - MCP server testing
4
+
5
+ ## Documentation
6
+
7
+ For detailed documentation, visit [https://heilgar.github.io/pest/](https://heilgar.github.io/pest/)
8
+
9
+ ## License
10
+
11
+ MIT
@@ -0,0 +1,76 @@
1
+ // src/matchers.ts
2
+ async function toExposeTools(received, names) {
3
+ const tools = await received.listTools();
4
+ const toolNames = tools.map((t) => t.name);
5
+ const missing = names.filter((n) => !toolNames.includes(n));
6
+ const pass = missing.length === 0;
7
+ return {
8
+ pass,
9
+ message: () => pass ? `Expected server NOT to expose tools [${names.join(", ")}], but it does.` : `Expected server to expose tools [${names.join(", ")}], but missing: [${missing.join(", ")}]. Found: [${toolNames.join(", ")}]`
10
+ };
11
+ }
12
+ async function toExposeTool(received, name) {
13
+ const tools = await received.listTools();
14
+ const toolNames = tools.map((t) => t.name);
15
+ const pass = toolNames.includes(name);
16
+ return {
17
+ pass,
18
+ message: () => pass ? `Expected server NOT to expose tool "${name}", but it does.` : `Expected server to expose tool "${name}". Found: [${toolNames.join(", ")}]`
19
+ };
20
+ }
21
+ async function toHaveValidToolSchemas(received) {
22
+ const tools = await received.listTools();
23
+ const invalid = [];
24
+ for (const tool of tools) {
25
+ const schema = tool.inputSchema;
26
+ if (!schema || typeof schema !== "object" || schema.type !== "object") {
27
+ invalid.push(tool.name);
28
+ }
29
+ }
30
+ const pass = invalid.length === 0;
31
+ return {
32
+ pass,
33
+ message: () => pass ? "Expected tool schemas NOT to be valid, but they are." : `Expected all tool schemas to be valid JSON Schema objects. Invalid: [${invalid.join(", ")}]`
34
+ };
35
+ }
36
+ async function toExposePrompts(received, names) {
37
+ const prompts = await received.listPrompts();
38
+ const promptNames = prompts.map((p) => p.name);
39
+ const missing = names.filter((n) => !promptNames.includes(n));
40
+ const pass = missing.length === 0;
41
+ return {
42
+ pass,
43
+ message: () => pass ? `Expected server NOT to expose prompts [${names.join(", ")}], but it does.` : `Expected server to expose prompts [${names.join(", ")}], but missing: [${missing.join(", ")}]. Found: [${promptNames.join(", ")}]`
44
+ };
45
+ }
46
+ async function toExposeResources(received, uris) {
47
+ const resources = await received.listResources();
48
+ const resourceUris = resources.map((r) => r.uri);
49
+ const missing = uris.filter((u) => !resourceUris.includes(u));
50
+ const pass = missing.length === 0;
51
+ return {
52
+ pass,
53
+ message: () => pass ? `Expected server NOT to expose resources [${uris.join(", ")}], but it does.` : `Expected server to expose resources [${uris.join(", ")}], but missing: [${missing.join(", ")}]. Found: [${resourceUris.join(", ")}]`
54
+ };
55
+ }
56
+ var mcpMatchers = {
57
+ async toExposeTools(received, names) {
58
+ return toExposeTools(received, names);
59
+ },
60
+ async toExposeTool(received, name) {
61
+ return toExposeTool(received, name);
62
+ },
63
+ async toHaveValidToolSchemas(received) {
64
+ return toHaveValidToolSchemas(received);
65
+ },
66
+ async toExposePrompts(received, names) {
67
+ return toExposePrompts(received, names);
68
+ },
69
+ async toExposeResources(received, uris) {
70
+ return toExposeResources(received, uris);
71
+ }
72
+ };
73
+
74
+ export {
75
+ mcpMatchers
76
+ };
@@ -0,0 +1,219 @@
1
+ // src/client.ts
2
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
5
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6
+ var DEFAULT_TIMEOUT = 3e4;
7
+ var McpClient = class _McpClient {
8
+ name;
9
+ client;
10
+ constructor(name, client) {
11
+ this.name = name;
12
+ this.client = client;
13
+ }
14
+ static async fromConfig(name, config, options) {
15
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
16
+ const client = new Client({ name: `pest-mcp-${name}`, version: "1.0.0" });
17
+ let transport;
18
+ if ("transport" in config && config.transport === "sse") {
19
+ transport = new SSEClientTransport(new URL(config.url));
20
+ } else if ("transport" in config && config.transport === "http") {
21
+ transport = new StreamableHTTPClientTransport(new URL(config.url));
22
+ } else {
23
+ transport = new StdioClientTransport({
24
+ command: config.command,
25
+ args: config.args,
26
+ env: config.env ? { ...process.env, ...config.env } : void 0
27
+ });
28
+ }
29
+ const connectPromise = client.connect(transport);
30
+ const timeoutPromise = new Promise(
31
+ (_, reject) => setTimeout(
32
+ () => reject(new Error(`MCP server "${name}" did not respond within ${timeout}ms`)),
33
+ timeout
34
+ )
35
+ );
36
+ await Promise.race([connectPromise, timeoutPromise]);
37
+ return new _McpClient(name, client);
38
+ }
39
+ async close() {
40
+ await this.client.close();
41
+ }
42
+ // --- Discovery ---
43
+ async listTools() {
44
+ const { tools } = await this.client.listTools();
45
+ return tools.map((t) => ({
46
+ name: t.name,
47
+ description: t.description,
48
+ inputSchema: t.inputSchema
49
+ }));
50
+ }
51
+ async listPrompts() {
52
+ const { prompts } = await this.client.listPrompts();
53
+ return prompts.map((p) => ({
54
+ name: p.name,
55
+ description: p.description,
56
+ arguments: p.arguments?.map((a) => ({
57
+ name: a.name,
58
+ description: a.description,
59
+ required: a.required
60
+ }))
61
+ }));
62
+ }
63
+ async listResources() {
64
+ const { resources } = await this.client.listResources();
65
+ return resources.map((r) => ({
66
+ uri: r.uri,
67
+ name: r.name,
68
+ description: r.description,
69
+ mimeType: r.mimeType
70
+ }));
71
+ }
72
+ // --- Execution ---
73
+ async callTool(name, args) {
74
+ const result = await this.client.callTool({ name, arguments: args });
75
+ return {
76
+ content: result.content ?? [],
77
+ structuredContent: result.structuredContent,
78
+ isError: result.isError
79
+ };
80
+ }
81
+ async getPrompt(name, args) {
82
+ const result = await this.client.getPrompt({ name, arguments: args });
83
+ return {
84
+ messages: result.messages.map((m) => {
85
+ const content = m.content;
86
+ return { role: m.role, content };
87
+ })
88
+ };
89
+ }
90
+ async readResource(uri) {
91
+ const result = await this.client.readResource({ uri });
92
+ return {
93
+ contents: result.contents.map((c) => ({
94
+ uri: c.uri,
95
+ text: "text" in c ? c.text : void 0,
96
+ blob: "blob" in c ? c.blob : void 0,
97
+ mimeType: c.mimeType
98
+ }))
99
+ };
100
+ }
101
+ // --- Conversion ---
102
+ async toPestTools() {
103
+ const tools = await this.listTools();
104
+ return tools.map((t) => ({
105
+ type: "function",
106
+ function: {
107
+ name: t.name,
108
+ description: t.description,
109
+ parameters: t.inputSchema
110
+ }
111
+ }));
112
+ }
113
+ };
114
+
115
+ // src/config.ts
116
+ import { existsSync } from "fs";
117
+ import { resolve } from "path";
118
+ import { pathToFileURL } from "url";
119
+ import { loadEnv } from "@heilgar/pest-core";
120
+ import * as v from "valibot";
121
+ var CONFIG_FILES = ["pest.config.ts", "pest.config.js", "pest.config.mjs"];
122
+ var McpStdioServerSchema = v.object({
123
+ command: v.string(),
124
+ args: v.optional(v.array(v.string())),
125
+ env: v.optional(v.record(v.string(), v.string()))
126
+ });
127
+ var McpSseServerSchema = v.object({
128
+ transport: v.literal("sse"),
129
+ url: v.string()
130
+ });
131
+ var McpHttpServerSchema = v.object({
132
+ transport: v.literal("http"),
133
+ url: v.string()
134
+ });
135
+ var McpServerConfigSchema = v.union([
136
+ McpStdioServerSchema,
137
+ McpSseServerSchema,
138
+ McpHttpServerSchema
139
+ ]);
140
+ var McpConfigSchema = v.object({
141
+ servers: v.record(v.string(), McpServerConfigSchema)
142
+ });
143
+ async function loadMcpConfig(cwd = process.cwd()) {
144
+ loadEnv(cwd);
145
+ let configPath;
146
+ for (const file of CONFIG_FILES) {
147
+ const candidate = resolve(cwd, file);
148
+ if (existsSync(candidate)) {
149
+ configPath = candidate;
150
+ break;
151
+ }
152
+ }
153
+ if (!configPath) {
154
+ throw new Error(
155
+ `No pest config found. Create one of: ${CONFIG_FILES.join(", ")}`
156
+ );
157
+ }
158
+ const configUrl = pathToFileURL(configPath).href;
159
+ let mod;
160
+ try {
161
+ mod = await import(configUrl);
162
+ } catch (err) {
163
+ if (configPath.endsWith(".ts") && err.code === "ERR_UNKNOWN_FILE_EXTENSION") {
164
+ throw new Error(
165
+ `Cannot import TypeScript config "${configPath}".
166
+ Run with tsx (npx tsx ...) or use pest.config.js / pest.config.mjs instead.`
167
+ );
168
+ }
169
+ throw err;
170
+ }
171
+ const raw = mod.default ?? mod;
172
+ const mcpRaw = raw.mcp;
173
+ if (!mcpRaw) {
174
+ throw new Error(
175
+ 'No "mcp" section found in pest config. Add mcp.servers to pest.config.ts.'
176
+ );
177
+ }
178
+ const result = v.safeParse(McpConfigSchema, mcpRaw);
179
+ if (!result.success) {
180
+ const issues = v.flatten(result.issues);
181
+ const messages = Object.entries(issues.nested ?? {}).map(([path, errors]) => ` ${path}: ${(errors ?? []).join(", ")}`).join("\n");
182
+ throw new Error(`Invalid MCP config:
183
+ ${messages}`);
184
+ }
185
+ return result.output;
186
+ }
187
+ var cache = /* @__PURE__ */ new Map();
188
+ async function useMcpServer(name) {
189
+ const cached = cache.get(name);
190
+ if (cached) return cached;
191
+ const config = await loadMcpConfig();
192
+ const serverConfig = config.servers[name];
193
+ if (!serverConfig) {
194
+ throw new Error(
195
+ `MCP server "${name}" not found in pest.config.ts. Available: ${Object.keys(config.servers).join(", ")}`
196
+ );
197
+ }
198
+ const client = await McpClient.fromConfig(name, serverConfig);
199
+ cache.set(name, client);
200
+ return client;
201
+ }
202
+ async function closeAllMcpServers() {
203
+ const clients = [...cache.values()];
204
+ cache.clear();
205
+ await Promise.allSettled(clients.map((c) => c.close()));
206
+ }
207
+ process.on("beforeExit", () => {
208
+ if (cache.size > 0) {
209
+ closeAllMcpServers().catch(() => {
210
+ });
211
+ }
212
+ });
213
+
214
+ export {
215
+ McpClient,
216
+ loadMcpConfig,
217
+ useMcpServer,
218
+ closeAllMcpServers
219
+ };
@@ -0,0 +1,126 @@
1
+ import { ToolDefinition, SendAgenticOptions, Provider, PestResponse } from '@heilgar/pest-core';
2
+
3
+ interface McpStdioServerConfig {
4
+ command: string;
5
+ args?: string[];
6
+ env?: Record<string, string>;
7
+ }
8
+ interface McpSseServerConfig {
9
+ transport: 'sse';
10
+ url: string;
11
+ }
12
+ interface McpHttpServerConfig {
13
+ transport: 'http';
14
+ url: string;
15
+ }
16
+ type McpServerConfig = McpStdioServerConfig | McpSseServerConfig | McpHttpServerConfig;
17
+ interface McpTool {
18
+ name: string;
19
+ description?: string;
20
+ inputSchema: Record<string, unknown>;
21
+ }
22
+ interface McpToolResult {
23
+ content: Array<{
24
+ type: string;
25
+ text?: string;
26
+ data?: string;
27
+ mimeType?: string;
28
+ }>;
29
+ structuredContent?: Record<string, unknown>;
30
+ isError?: boolean;
31
+ }
32
+ interface McpPrompt {
33
+ name: string;
34
+ description?: string;
35
+ arguments?: Array<{
36
+ name: string;
37
+ description?: string;
38
+ required?: boolean;
39
+ }>;
40
+ }
41
+ interface McpPromptResult {
42
+ messages: Array<{
43
+ role: string;
44
+ content: {
45
+ type: string;
46
+ text: string;
47
+ };
48
+ }>;
49
+ }
50
+ interface McpResource {
51
+ uri: string;
52
+ name: string;
53
+ description?: string;
54
+ mimeType?: string;
55
+ }
56
+ interface McpResourceResult {
57
+ contents: Array<{
58
+ uri: string;
59
+ text?: string;
60
+ blob?: string;
61
+ mimeType?: string;
62
+ }>;
63
+ }
64
+
65
+ declare class McpClient {
66
+ readonly name: string;
67
+ private client;
68
+ private constructor();
69
+ static fromConfig(name: string, config: McpServerConfig, options?: {
70
+ timeout?: number;
71
+ }): Promise<McpClient>;
72
+ close(): Promise<void>;
73
+ listTools(): Promise<McpTool[]>;
74
+ listPrompts(): Promise<McpPrompt[]>;
75
+ listResources(): Promise<McpResource[]>;
76
+ callTool(name: string, args?: Record<string, unknown>): Promise<McpToolResult>;
77
+ getPrompt(name: string, args?: Record<string, string>): Promise<McpPromptResult>;
78
+ readResource(uri: string): Promise<McpResourceResult>;
79
+ toPestTools(): Promise<ToolDefinition[]>;
80
+ }
81
+
82
+ interface McpConfig {
83
+ servers: Record<string, McpServerConfig>;
84
+ }
85
+ /**
86
+ * Load the MCP config section from pest.config.ts.
87
+ * Loads the raw config file independently from core's loadConfig()
88
+ * because core's valibot schema strips unknown keys.
89
+ */
90
+ declare function loadMcpConfig(cwd?: string): Promise<McpConfig>;
91
+ /**
92
+ * Resolve an MCP server from pest.config.ts by name.
93
+ * Cached — subsequent calls return the same connection.
94
+ */
95
+ declare function useMcpServer(name: string): Promise<McpClient>;
96
+ /**
97
+ * Close all cached MCP connections.
98
+ * Call in afterAll() for reliable cleanup.
99
+ */
100
+ declare function closeAllMcpServers(): Promise<void>;
101
+
102
+ interface SendWithMcpOptions extends Omit<SendAgenticOptions, 'executor' | 'tools'> {
103
+ mcpServer: McpClient;
104
+ additionalTools?: ToolDefinition[];
105
+ }
106
+ /**
107
+ * Send a message to an LLM with MCP server tools auto-discovered
108
+ * and tool calls auto-routed to the MCP server.
109
+ *
110
+ * Returns a standard PestResponse so all existing pest matchers work.
111
+ */
112
+ declare function sendWithMcp(provider: Provider, message: string, options: SendWithMcpOptions): Promise<PestResponse>;
113
+
114
+ interface MatcherResult {
115
+ pass: boolean;
116
+ message: () => string;
117
+ }
118
+ declare const mcpMatchers: {
119
+ toExposeTools(received: McpClient, names: string[]): Promise<MatcherResult>;
120
+ toExposeTool(received: McpClient, name: string): Promise<MatcherResult>;
121
+ toHaveValidToolSchemas(received: McpClient): Promise<MatcherResult>;
122
+ toExposePrompts(received: McpClient, names: string[]): Promise<MatcherResult>;
123
+ toExposeResources(received: McpClient, uris: string[]): Promise<MatcherResult>;
124
+ };
125
+
126
+ export { McpClient, type McpConfig, type McpHttpServerConfig, type McpPrompt, type McpPromptResult, type McpResource, type McpResourceResult, type McpServerConfig, type McpSseServerConfig, type McpStdioServerConfig, type McpTool, type McpToolResult, type SendWithMcpOptions, closeAllMcpServers, loadMcpConfig, mcpMatchers, sendWithMcp, useMcpServer };
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ import {
2
+ McpClient,
3
+ closeAllMcpServers,
4
+ loadMcpConfig,
5
+ useMcpServer
6
+ } from "./chunk-FRYT6IAD.js";
7
+ import {
8
+ mcpMatchers
9
+ } from "./chunk-CG5UCD4O.js";
10
+
11
+ // src/send-mcp.ts
12
+ import { sendAgentic } from "@heilgar/pest-core";
13
+ async function sendWithMcp(provider, message, options) {
14
+ const { mcpServer, additionalTools, ...rest } = options;
15
+ const mcpTools = await mcpServer.toPestTools();
16
+ const mcpToolNames = new Set(mcpTools.map((t) => t.function.name));
17
+ const tools = [...mcpTools, ...additionalTools ?? []];
18
+ const executor = async (name, args) => {
19
+ if (mcpToolNames.has(name)) {
20
+ const result = await mcpServer.callTool(name, args);
21
+ const texts = result.content.filter((c) => c.type === "text" && c.text).map((c) => c.text);
22
+ return texts.join("\n") || JSON.stringify(result.content);
23
+ }
24
+ return "[]";
25
+ };
26
+ return sendAgentic(provider, message, {
27
+ ...rest,
28
+ tools,
29
+ executor
30
+ });
31
+ }
32
+ export {
33
+ McpClient,
34
+ closeAllMcpServers,
35
+ loadMcpConfig,
36
+ mcpMatchers,
37
+ sendWithMcp,
38
+ useMcpServer
39
+ };
package/dist/qa.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ interface QaOptions {
2
+ verbose?: boolean;
3
+ }
4
+ declare function runMcpQa(serverName: string, options?: QaOptions): Promise<void>;
5
+
6
+ export { runMcpQa };
package/dist/qa.js ADDED
@@ -0,0 +1,106 @@
1
+ import {
2
+ McpClient,
3
+ loadMcpConfig
4
+ } from "./chunk-FRYT6IAD.js";
5
+
6
+ // src/qa.ts
7
+ import { ansi as c } from "@heilgar/pest-core";
8
+ function icon(status) {
9
+ if (status === "pass") return `${c.green}pass${c.reset}`;
10
+ if (status === "fail") return `${c.red}fail${c.reset}`;
11
+ return `${c.yellow}warn${c.reset}`;
12
+ }
13
+ async function runMcpQa(serverName, options) {
14
+ const config = await loadMcpConfig();
15
+ const serverConfig = config.servers[serverName];
16
+ if (!serverConfig) {
17
+ console.error(
18
+ `MCP server "${serverName}" not found in pest.config.ts. Available: ${Object.keys(config.servers).join(", ")}`
19
+ );
20
+ process.exit(1);
21
+ }
22
+ const command = "command" in serverConfig ? `${serverConfig.command} ${(serverConfig.args ?? []).join(" ")}`.trim() : serverConfig.url;
23
+ console.log(`
24
+ pest qa \u2014 ${serverName} (${command})
25
+ `);
26
+ const results = [];
27
+ let client;
28
+ try {
29
+ const start = performance.now();
30
+ client = await McpClient.fromConfig(serverName, serverConfig);
31
+ const elapsed = ((performance.now() - start) / 1e3).toFixed(1);
32
+ const transport = "command" in serverConfig ? "stdio" : serverConfig.transport;
33
+ results.push({ status: "pass", message: `Server started and connected via ${transport} (${elapsed}s)` });
34
+ } catch (err) {
35
+ results.push({ status: "fail", message: `Server failed to start: ${err}` });
36
+ for (const r of results) console.log(` [${icon(r.status)}] ${r.message}`);
37
+ console.log(`
38
+ Result: 0 passed, 1 failed
39
+ `);
40
+ process.exit(1);
41
+ return;
42
+ }
43
+ try {
44
+ try {
45
+ const tools = await client.listTools();
46
+ const names = tools.map((t) => t.name).join(", ");
47
+ results.push({ status: "pass", message: `tools/list: ${tools.length} tools (${names})` });
48
+ const invalid = tools.filter((t) => !t.inputSchema || t.inputSchema.type !== "object");
49
+ if (invalid.length === 0) {
50
+ results.push({ status: "pass", message: "Tool schemas valid" });
51
+ } else {
52
+ results.push({
53
+ status: "fail",
54
+ message: `Invalid tool schemas: ${invalid.map((t) => t.name).join(", ")}`
55
+ });
56
+ }
57
+ } catch (err) {
58
+ results.push({ status: "fail", message: `tools/list failed: ${err}` });
59
+ }
60
+ try {
61
+ const prompts = await client.listPrompts();
62
+ const names = prompts.map((p) => p.name).join(", ");
63
+ results.push({ status: "pass", message: `prompts/list: ${prompts.length} prompts${prompts.length > 0 ? ` (${names})` : ""}` });
64
+ } catch (err) {
65
+ results.push({ status: "warn", message: `prompts/list: ${err}` });
66
+ }
67
+ try {
68
+ const resources = await client.listResources();
69
+ results.push({ status: "pass", message: `resources/list: ${resources.length} resources` });
70
+ if (resources.length > 0 && options?.verbose) {
71
+ try {
72
+ const first = resources[0];
73
+ await client.readResource(first.uri);
74
+ results.push({ status: "pass", message: `resources/read: ${first.uri} readable` });
75
+ } catch (err) {
76
+ results.push({ status: "warn", message: `resources/read failed: ${err}` });
77
+ }
78
+ }
79
+ } catch (err) {
80
+ results.push({ status: "warn", message: `resources/list: ${err}` });
81
+ }
82
+ await client.close();
83
+ results.push({ status: "pass", message: "Server closed cleanly" });
84
+ } catch (err) {
85
+ results.push({ status: "fail", message: `Unexpected error: ${err}` });
86
+ try {
87
+ await client.close();
88
+ } catch {
89
+ }
90
+ }
91
+ for (const r of results) {
92
+ console.log(` [${icon(r.status)}] ${r.message}`);
93
+ }
94
+ const passed = results.filter((r) => r.status === "pass").length;
95
+ const failed = results.filter((r) => r.status === "fail").length;
96
+ const warned = results.filter((r) => r.status === "warn").length;
97
+ console.log("");
98
+ const parts = [`${passed} passed`, `${failed} failed`];
99
+ if (warned > 0) parts.push(`${warned} warnings`);
100
+ console.log(` Result: ${parts.join(", ")}
101
+ `);
102
+ if (failed > 0) process.exit(1);
103
+ }
104
+ export {
105
+ runMcpQa
106
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,6 @@
1
+ import {
2
+ mcpMatchers
3
+ } from "./chunk-CG5UCD4O.js";
4
+
5
+ // src/setup-jest.ts
6
+ expect.extend(mcpMatchers);
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,7 @@
1
+ import {
2
+ mcpMatchers
3
+ } from "./chunk-CG5UCD4O.js";
4
+
5
+ // src/setup-vitest.ts
6
+ import { expect } from "vitest";
7
+ expect.extend(mcpMatchers);
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@heilgar/pest-mcp",
3
+ "version": "0.0.2",
4
+ "description": "Prompt Evaluation & Scoring Toolkit - MCP server testing",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./setup/vitest": {
14
+ "import": "./dist/setup-vitest.js",
15
+ "types": "./dist/setup-vitest.d.ts"
16
+ },
17
+ "./setup/jest": {
18
+ "import": "./dist/setup-jest.js",
19
+ "types": "./dist/setup-jest.d.ts"
20
+ },
21
+ "./qa": {
22
+ "import": "./dist/qa.js",
23
+ "types": "./dist/qa.d.ts"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "keywords": [
30
+ "llm",
31
+ "prompt",
32
+ "testing",
33
+ "mcp",
34
+ "model-context-protocol",
35
+ "evaluation",
36
+ "ai"
37
+ ],
38
+ "license": "MIT",
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.27.0",
41
+ "valibot": "^1.2.0",
42
+ "@heilgar/pest-core": "0.0.2"
43
+ },
44
+ "peerDependencies": {
45
+ "vitest": ">=2.0.0",
46
+ "jest": ">=29.0.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "vitest": {
50
+ "optional": true
51
+ },
52
+ "jest": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^25.5.0",
58
+ "tsup": "^8.5.1",
59
+ "typescript": "^5.9.3",
60
+ "vitest": "^4.1.0"
61
+ },
62
+ "scripts": {
63
+ "build": "tsup",
64
+ "dev": "tsup --watch",
65
+ "lint": "tsc --noEmit"
66
+ }
67
+ }