proctor-mcp-server 0.1.0 → 0.1.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/README.md +40 -60
- package/build/index.integration-with-mock.js +6 -45
- package/package.json +1 -1
- package/shared/index.d.ts +1 -1
- package/shared/proctor-client/lib/run-exam.js +3 -7
- package/shared/server.d.ts +1 -5
- package/shared/server.js +1 -9
- package/shared/tools/get-metadata.js +3 -7
- package/shared/tools/run-exam.d.ts +1 -9
- package/shared/tools/run-exam.js +11 -26
- package/shared/tools.d.ts +1 -1
- package/shared/tools.js +1 -5
- package/shared/types.d.ts +8 -39
- package/shared/proctor-client/lib/get-prior-result.d.ts +0 -6
- package/shared/proctor-client/lib/get-prior-result.js +0 -35
- package/shared/proctor-client/lib/save-result.d.ts +0 -6
- package/shared/proctor-client/lib/save-result.js +0 -42
- package/shared/tools/get-prior-result.d.ts +0 -38
- package/shared/tools/get-prior-result.js +0 -106
- package/shared/tools/save-result.d.ts +0 -52
- package/shared/tools/save-result.js +0 -122
package/README.md
CHANGED
|
@@ -36,15 +36,13 @@ This is an MCP ([Model Context Protocol](https://modelcontextprotocol.io/)) Serv
|
|
|
36
36
|
|
|
37
37
|
This server is built and tested on macOS with Claude Desktop. It should work with other MCP clients as well.
|
|
38
38
|
|
|
39
|
-
| Tool Name | Tool Group | Read/Write | Description
|
|
40
|
-
| ---------------------- | ---------- | ---------- |
|
|
41
|
-
| `get_proctor_metadata` | exams | read | Get available runtimes and exams for Proctor testing.
|
|
42
|
-
| `run_exam` | exams | write | Execute a Proctor exam against an MCP server.
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `destroy_machine` | machines | write | Delete a Fly.io machine. |
|
|
47
|
-
| `cancel_exam` | machines | write | Cancel a running Proctor exam. |
|
|
39
|
+
| Tool Name | Tool Group | Read/Write | Description |
|
|
40
|
+
| ---------------------- | ---------- | ---------- | ----------------------------------------------------- |
|
|
41
|
+
| `get_proctor_metadata` | exams | read | Get available runtimes and exams for Proctor testing. |
|
|
42
|
+
| `run_exam` | exams | write | Execute a Proctor exam against an MCP server. |
|
|
43
|
+
| `get_machines` | machines | read | List active Fly.io machines used for Proctor exams. |
|
|
44
|
+
| `destroy_machine` | machines | write | Delete a Fly.io machine. |
|
|
45
|
+
| `cancel_exam` | machines | write | Cancel a running Proctor exam. |
|
|
48
46
|
|
|
49
47
|
# Tool Groups
|
|
50
48
|
|
|
@@ -57,16 +55,16 @@ This server organizes tools into groups that can be selectively enabled or disab
|
|
|
57
55
|
|
|
58
56
|
| Group | Tools | Description |
|
|
59
57
|
| ------------------- | ----- | -------------------------------------- |
|
|
60
|
-
| `exams` |
|
|
61
|
-
| `exams_readonly` |
|
|
58
|
+
| `exams` | 2 | Full exam execution (read + write) |
|
|
59
|
+
| `exams_readonly` | 1 | Exam metadata (read only) |
|
|
62
60
|
| `machines` | 3 | Full machine management (read + write) |
|
|
63
61
|
| `machines_readonly` | 1 | Machine listing (read only) |
|
|
64
62
|
|
|
65
63
|
### Tools by Group
|
|
66
64
|
|
|
67
65
|
- **exams** / **exams_readonly**:
|
|
68
|
-
- Read-only: `get_proctor_metadata
|
|
69
|
-
- Write: `run_exam
|
|
66
|
+
- Read-only: `get_proctor_metadata`
|
|
67
|
+
- Write: `run_exam`
|
|
70
68
|
- **machines** / **machines_readonly**:
|
|
71
69
|
- Read-only: `get_machines`
|
|
72
70
|
- Write: `destroy_machine`, `cancel_exam`
|
|
@@ -114,9 +112,8 @@ TOOL_GROUPS=exams,machines_readonly
|
|
|
114
112
|
|
|
115
113
|
# Usage Tips
|
|
116
114
|
|
|
117
|
-
- Use `get_proctor_metadata`
|
|
118
|
-
-
|
|
119
|
-
- Save results with `save_result` to enable future comparisons with `get_prior_result`
|
|
115
|
+
- Use `get_proctor_metadata` to discover available runtimes and exam types
|
|
116
|
+
- Run exams with `run_exam` to test MCP servers with streaming progress logs
|
|
120
117
|
- Use `get_machines` to monitor active exam infrastructure
|
|
121
118
|
- Clean up machines with `destroy_machine` when no longer needed
|
|
122
119
|
- Use `cancel_exam` to stop a stuck or slow exam before destroying the machine
|
|
@@ -141,24 +138,6 @@ Here are the available exams:
|
|
|
141
138
|
- Init Tools List (id: proctor-mcp-client-init-tools-list) - Tests initialization and tool listing
|
|
142
139
|
```
|
|
143
140
|
|
|
144
|
-
## Run an Exam
|
|
145
|
-
|
|
146
|
-
```
|
|
147
|
-
User: Run the init-tools-list exam against my MCP server at https://example.com/mcp
|
|
148
|
-
Assistant: I'll run the exam against your server.
|
|
149
|
-
|
|
150
|
-
[Calls run_exam with runtime_id="v0.0.37", exam_id="proctor-mcp-client-init-tools-list", mcp_config=...]
|
|
151
|
-
|
|
152
|
-
The exam completed successfully. Here are the results:
|
|
153
|
-
|
|
154
|
-
**Status:** Success
|
|
155
|
-
**Tests Passed:** 5/5
|
|
156
|
-
- Initialization: Passed
|
|
157
|
-
- Tool listing: Passed
|
|
158
|
-
- Tool execution: Passed
|
|
159
|
-
...
|
|
160
|
-
```
|
|
161
|
-
|
|
162
141
|
## Monitor Infrastructure
|
|
163
142
|
|
|
164
143
|
```
|
|
@@ -175,38 +154,28 @@ There are 2 active machines:
|
|
|
175
154
|
|
|
176
155
|
# Setup
|
|
177
156
|
|
|
178
|
-
##
|
|
179
|
-
|
|
180
|
-
Quick setup:
|
|
181
|
-
|
|
182
|
-
```bash
|
|
183
|
-
# Install dependencies
|
|
184
|
-
npm run install-all
|
|
157
|
+
## Claude Desktop
|
|
185
158
|
|
|
186
|
-
|
|
187
|
-
npm run build
|
|
159
|
+
Make sure you have your Proctor API key ready.
|
|
188
160
|
|
|
189
|
-
|
|
190
|
-
export PROCTOR_API_KEY="your-api-key-here"
|
|
161
|
+
Then proceed to the setup instructions below. If this is your first time using MCP Servers, you'll want to make sure you have the [Claude Desktop application](https://claude.ai/download) and follow the [official MCP setup instructions](https://modelcontextprotocol.io/quickstart/user).
|
|
191
162
|
|
|
192
|
-
|
|
193
|
-
cd local && npm start
|
|
194
|
-
```
|
|
163
|
+
### Manual Setup
|
|
195
164
|
|
|
196
|
-
|
|
165
|
+
You're going to need Node working on your machine so you can run `npx` commands in your terminal. If you don't have Node, you can install it from [nodejs.org](https://nodejs.org/en/download).
|
|
197
166
|
|
|
198
|
-
|
|
167
|
+
macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
199
168
|
|
|
200
|
-
|
|
169
|
+
Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
201
170
|
|
|
202
|
-
|
|
171
|
+
Modify your `claude_desktop_config.json` file to add the following:
|
|
203
172
|
|
|
204
173
|
```json
|
|
205
174
|
{
|
|
206
175
|
"mcpServers": {
|
|
207
176
|
"proctor": {
|
|
208
|
-
"command": "
|
|
209
|
-
"args": ["
|
|
177
|
+
"command": "npx",
|
|
178
|
+
"args": ["-y", "proctor-mcp-server"],
|
|
210
179
|
"env": {
|
|
211
180
|
"PROCTOR_API_KEY": "your-api-key-here",
|
|
212
181
|
"TOOL_GROUPS": "exams,machines"
|
|
@@ -216,14 +185,16 @@ Add to your Claude Desktop configuration:
|
|
|
216
185
|
}
|
|
217
186
|
```
|
|
218
187
|
|
|
188
|
+
Restart Claude Desktop and you should be ready to go!
|
|
189
|
+
|
|
219
190
|
For read-only access:
|
|
220
191
|
|
|
221
192
|
```json
|
|
222
193
|
{
|
|
223
194
|
"mcpServers": {
|
|
224
195
|
"proctor-readonly": {
|
|
225
|
-
"command": "
|
|
226
|
-
"args": ["
|
|
196
|
+
"command": "npx",
|
|
197
|
+
"args": ["-y", "proctor-mcp-server"],
|
|
227
198
|
"env": {
|
|
228
199
|
"PROCTOR_API_KEY": "your-api-key-here",
|
|
229
200
|
"TOOL_GROUPS": "exams_readonly,machines_readonly"
|
|
@@ -233,13 +204,22 @@ For read-only access:
|
|
|
233
204
|
}
|
|
234
205
|
```
|
|
235
206
|
|
|
236
|
-
|
|
207
|
+
## Development
|
|
237
208
|
|
|
238
|
-
|
|
209
|
+
### Quick Setup
|
|
239
210
|
|
|
240
211
|
```bash
|
|
241
|
-
|
|
242
|
-
|
|
212
|
+
# Install dependencies
|
|
213
|
+
npm run install-all
|
|
214
|
+
|
|
215
|
+
# Build the server
|
|
216
|
+
npm run build
|
|
217
|
+
|
|
218
|
+
# Set your API key
|
|
219
|
+
export PROCTOR_API_KEY="your-api-key-here"
|
|
220
|
+
|
|
221
|
+
# Run the server
|
|
222
|
+
cd local && npm start
|
|
243
223
|
```
|
|
244
224
|
|
|
245
225
|
## License
|
|
@@ -9,8 +9,6 @@ import { createMCPServer, logServerStart, logError } from '../shared/index.js';
|
|
|
9
9
|
* Integration mock implementation of IProctorClient
|
|
10
10
|
*/
|
|
11
11
|
class IntegrationMockProctorClient {
|
|
12
|
-
priorResults = new Map();
|
|
13
|
-
savedResultId = 0;
|
|
14
12
|
async getMetadata() {
|
|
15
13
|
return {
|
|
16
14
|
runtimes: [
|
|
@@ -39,61 +37,24 @@ class IntegrationMockProctorClient {
|
|
|
39
37
|
],
|
|
40
38
|
};
|
|
41
39
|
}
|
|
42
|
-
async *runExam(
|
|
40
|
+
async *runExam(_params) {
|
|
41
|
+
yield { type: 'log', data: { time: '2024-01-15T10:30:00Z', message: 'Starting exam...' } };
|
|
43
42
|
yield {
|
|
44
43
|
type: 'log',
|
|
45
|
-
data: { time:
|
|
46
|
-
};
|
|
47
|
-
yield {
|
|
48
|
-
type: 'log',
|
|
49
|
-
data: { time: new Date().toISOString(), message: `Using runtime: ${params.runtime_id}` },
|
|
50
|
-
};
|
|
51
|
-
yield {
|
|
52
|
-
type: 'log',
|
|
53
|
-
data: { time: new Date().toISOString(), message: `Running exam: ${params.exam_id}` },
|
|
54
|
-
};
|
|
55
|
-
yield {
|
|
56
|
-
type: 'log',
|
|
57
|
-
data: { time: new Date().toISOString(), message: 'Exam completed successfully' },
|
|
44
|
+
data: { time: '2024-01-15T10:30:01Z', message: 'Initializing MCP client...' },
|
|
58
45
|
};
|
|
46
|
+
yield { type: 'log', data: { time: '2024-01-15T10:30:02Z', message: 'Running tests...' } };
|
|
59
47
|
yield {
|
|
60
48
|
type: 'result',
|
|
61
49
|
data: {
|
|
62
|
-
status: '
|
|
63
|
-
input: {
|
|
64
|
-
'mcp.json': JSON.parse(params.mcp_config),
|
|
65
|
-
},
|
|
50
|
+
status: 'passed',
|
|
66
51
|
tests: [
|
|
67
52
|
{ name: 'initialization', passed: true },
|
|
68
|
-
{ name: '
|
|
53
|
+
{ name: 'tools_list', passed: true },
|
|
69
54
|
],
|
|
70
55
|
},
|
|
71
56
|
};
|
|
72
57
|
}
|
|
73
|
-
async saveResult(params) {
|
|
74
|
-
this.savedResultId++;
|
|
75
|
-
const result = {
|
|
76
|
-
success: true,
|
|
77
|
-
id: this.savedResultId,
|
|
78
|
-
};
|
|
79
|
-
const key = `${params.mirror_id}-${params.exam_id}`;
|
|
80
|
-
this.priorResults.set(key, {
|
|
81
|
-
id: this.savedResultId,
|
|
82
|
-
datetime_performed: new Date().toISOString(),
|
|
83
|
-
results: typeof params.results === 'string' ? JSON.parse(params.results) : params.results,
|
|
84
|
-
runtime_image: params.runtime_id,
|
|
85
|
-
match_type: 'exact',
|
|
86
|
-
});
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
async getPriorResult(params) {
|
|
90
|
-
const key = `${params.mirror_id}-${params.exam_id}`;
|
|
91
|
-
const result = this.priorResults.get(key);
|
|
92
|
-
if (!result) {
|
|
93
|
-
throw new Error('No prior result found');
|
|
94
|
-
}
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
58
|
async getMachines() {
|
|
98
59
|
return {
|
|
99
60
|
machines: [
|
package/package.json
CHANGED
package/shared/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export type { IProctorClient, ClientFactory } from './server.js';
|
|
|
3
3
|
export { createRegisterTools, parseEnabledToolGroups } from './tools.js';
|
|
4
4
|
export type { ToolGroup } from './tools.js';
|
|
5
5
|
export { logServerStart, logError, logWarning, logDebug } from './logging.js';
|
|
6
|
-
export type { ProctorRuntime, ProctorExam, ProctorMetadataResponse, ExamLogEntry, ExamStreamLog, ExamStreamResult, ExamStreamError, ExamStreamEntry, ExamResult, RunExamParams,
|
|
6
|
+
export type { ProctorRuntime, ProctorExam, ProctorMetadataResponse, ExamLogEntry, ExamStreamLog, ExamStreamResult, ExamStreamError, ExamStreamEntry, ExamResult, RunExamParams, FlyMachine, MachinesResponse, CancelExamParams, CancelExamResponse, ApiError, } from './types.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -7,7 +7,9 @@ export async function* runExam(apiKey, baseUrl, params) {
|
|
|
7
7
|
const body = {
|
|
8
8
|
runtime_id: params.runtime_id,
|
|
9
9
|
exam_id: params.exam_id,
|
|
10
|
-
mcp_config: params.
|
|
10
|
+
mcp_config: params.mcp_json,
|
|
11
|
+
// Always disable OAuth credential persistence - the MCP server manages its own credentials
|
|
12
|
+
no_result_persistence: true,
|
|
11
13
|
};
|
|
12
14
|
if (params.server_json) {
|
|
13
15
|
body.server_json = params.server_json;
|
|
@@ -18,12 +20,6 @@ export async function* runExam(apiKey, baseUrl, params) {
|
|
|
18
20
|
if (params.max_retries !== undefined) {
|
|
19
21
|
body.max_retries = params.max_retries;
|
|
20
22
|
}
|
|
21
|
-
if (params.mcp_server_slug) {
|
|
22
|
-
body.mcp_server_slug = params.mcp_server_slug;
|
|
23
|
-
}
|
|
24
|
-
if (params.mcp_json_id !== undefined) {
|
|
25
|
-
body.mcp_json_id = params.mcp_json_id;
|
|
26
|
-
}
|
|
27
23
|
const response = await fetch(url.toString(), {
|
|
28
24
|
method: 'POST',
|
|
29
25
|
headers: {
|
package/shared/server.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import type { ProctorMetadataResponse, RunExamParams, ExamStreamEntry,
|
|
2
|
+
import type { ProctorMetadataResponse, RunExamParams, ExamStreamEntry, MachinesResponse, CancelExamParams, CancelExamResponse } from './types.js';
|
|
3
3
|
export interface IProctorClient {
|
|
4
4
|
getMetadata(): Promise<ProctorMetadataResponse>;
|
|
5
5
|
runExam(params: RunExamParams): AsyncGenerator<ExamStreamEntry, void, unknown>;
|
|
6
|
-
saveResult(params: SaveResultParams): Promise<SaveResultResponse>;
|
|
7
|
-
getPriorResult(params: PriorResultParams): Promise<PriorResultResponse>;
|
|
8
6
|
getMachines(): Promise<MachinesResponse>;
|
|
9
7
|
destroyMachine(machineId: string): Promise<{
|
|
10
8
|
success: boolean;
|
|
@@ -17,8 +15,6 @@ export declare class ProctorClient implements IProctorClient {
|
|
|
17
15
|
constructor(apiKey: string, baseUrl?: string);
|
|
18
16
|
getMetadata(): Promise<ProctorMetadataResponse>;
|
|
19
17
|
runExam(params: RunExamParams): AsyncGenerator<ExamStreamEntry, void, unknown>;
|
|
20
|
-
saveResult(params: SaveResultParams): Promise<SaveResultResponse>;
|
|
21
|
-
getPriorResult(params: PriorResultParams): Promise<PriorResultResponse>;
|
|
22
18
|
getMachines(): Promise<MachinesResponse>;
|
|
23
19
|
destroyMachine(machineId: string): Promise<{
|
|
24
20
|
success: boolean;
|
package/shared/server.js
CHANGED
|
@@ -16,14 +16,6 @@ export class ProctorClient {
|
|
|
16
16
|
const { runExam } = await import('./proctor-client/lib/run-exam.js');
|
|
17
17
|
yield* runExam(this.apiKey, this.baseUrl, params);
|
|
18
18
|
}
|
|
19
|
-
async saveResult(params) {
|
|
20
|
-
const { saveResult } = await import('./proctor-client/lib/save-result.js');
|
|
21
|
-
return saveResult(this.apiKey, this.baseUrl, params);
|
|
22
|
-
}
|
|
23
|
-
async getPriorResult(params) {
|
|
24
|
-
const { getPriorResult } = await import('./proctor-client/lib/get-prior-result.js');
|
|
25
|
-
return getPriorResult(this.apiKey, this.baseUrl, params);
|
|
26
|
-
}
|
|
27
19
|
async getMachines() {
|
|
28
20
|
const { getMachines } = await import('./proctor-client/lib/get-machines.js');
|
|
29
21
|
return getMachines(this.apiKey, this.baseUrl);
|
|
@@ -40,7 +32,7 @@ export class ProctorClient {
|
|
|
40
32
|
export function createMCPServer() {
|
|
41
33
|
const server = new Server({
|
|
42
34
|
name: 'proctor-mcp-server',
|
|
43
|
-
version: '0.1.
|
|
35
|
+
version: '0.1.2',
|
|
44
36
|
}, {
|
|
45
37
|
capabilities: {
|
|
46
38
|
tools: {},
|
|
@@ -3,20 +3,16 @@ export function getMetadata(_server, clientFactory) {
|
|
|
3
3
|
name: 'get_proctor_metadata',
|
|
4
4
|
description: `Get available runtimes and exams for Proctor testing.
|
|
5
5
|
|
|
6
|
-
Returns the list of available runtime environments (Docker images) and exam types
|
|
7
|
-
that can be used with the run_exam tool.
|
|
6
|
+
Returns the list of available runtime environments (Docker images) and exam types.
|
|
8
7
|
|
|
9
8
|
**Returns:**
|
|
10
9
|
- runtimes: Array of runtime configurations with id, name, and Docker image
|
|
11
10
|
- exams: Array of exam types with id, name, and description
|
|
12
11
|
|
|
13
12
|
**Use cases:**
|
|
14
|
-
- Discover available runtime environments
|
|
13
|
+
- Discover available runtime environments
|
|
15
14
|
- Find the correct exam ID for a specific test type
|
|
16
|
-
- Check which runtime versions are available
|
|
17
|
-
- Plan which exam to run against an MCP server
|
|
18
|
-
|
|
19
|
-
**Note:** Use the runtime_id and exam_id values from this response when calling run_exam.`,
|
|
15
|
+
- Check which runtime versions are available`,
|
|
20
16
|
inputSchema: {
|
|
21
17
|
type: 'object',
|
|
22
18
|
properties: {},
|
|
@@ -14,7 +14,7 @@ export declare function runExam(_server: Server, clientFactory: ClientFactory):
|
|
|
14
14
|
type: string;
|
|
15
15
|
description: "Exam ID from get_proctor_metadata. Example: \"proctor-mcp-client-init-tools-list\" or \"proctor-mcp-client-auth-check\"";
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
mcp_json: {
|
|
18
18
|
type: string;
|
|
19
19
|
description: "JSON string of the mcp.json configuration for the MCP server. Must be a valid JSON object with server configurations.";
|
|
20
20
|
};
|
|
@@ -30,14 +30,6 @@ export declare function runExam(_server: Server, clientFactory: ClientFactory):
|
|
|
30
30
|
type: string;
|
|
31
31
|
description: "Maximum number of retry attempts (0-10). Default is 0.";
|
|
32
32
|
};
|
|
33
|
-
mcp_server_slug: {
|
|
34
|
-
type: string;
|
|
35
|
-
description: "Optional MCP server slug for auto-injection of proctor files and OAuth credentials.";
|
|
36
|
-
};
|
|
37
|
-
mcp_json_id: {
|
|
38
|
-
type: string;
|
|
39
|
-
description: "Optional McpJson ID for preloaded OAuth credentials.";
|
|
40
|
-
};
|
|
41
33
|
};
|
|
42
34
|
required: string[];
|
|
43
35
|
};
|
package/shared/tools/run-exam.js
CHANGED
|
@@ -3,22 +3,18 @@ import { z } from 'zod';
|
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
4
|
runtime_id: 'Runtime ID from get_proctor_metadata, or "__custom__" for a custom Docker image. Example: "v0.0.37"',
|
|
5
5
|
exam_id: 'Exam ID from get_proctor_metadata. Example: "proctor-mcp-client-init-tools-list" or "proctor-mcp-client-auth-check"',
|
|
6
|
-
|
|
6
|
+
mcp_json: 'JSON string of the mcp.json configuration for the MCP server. Must be a valid JSON object with server configurations.',
|
|
7
7
|
server_json: 'Optional JSON string of server.json for result enrichment. Provides additional context about the server being tested.',
|
|
8
8
|
custom_runtime_image: 'Required if runtime_id is "__custom__". Docker image URL in format: registry/image:tag',
|
|
9
9
|
max_retries: 'Maximum number of retry attempts (0-10). Default is 0.',
|
|
10
|
-
mcp_server_slug: 'Optional MCP server slug for auto-injection of proctor files and OAuth credentials.',
|
|
11
|
-
mcp_json_id: 'Optional McpJson ID for preloaded OAuth credentials.',
|
|
12
10
|
};
|
|
13
11
|
const RunExamSchema = z.object({
|
|
14
12
|
runtime_id: z.string().min(1).describe(PARAM_DESCRIPTIONS.runtime_id),
|
|
15
13
|
exam_id: z.string().min(1).describe(PARAM_DESCRIPTIONS.exam_id),
|
|
16
|
-
|
|
14
|
+
mcp_json: z.string().min(1).describe(PARAM_DESCRIPTIONS.mcp_json),
|
|
17
15
|
server_json: z.string().optional().describe(PARAM_DESCRIPTIONS.server_json),
|
|
18
16
|
custom_runtime_image: z.string().optional().describe(PARAM_DESCRIPTIONS.custom_runtime_image),
|
|
19
17
|
max_retries: z.number().min(0).max(10).optional().describe(PARAM_DESCRIPTIONS.max_retries),
|
|
20
|
-
mcp_server_slug: z.string().optional().describe(PARAM_DESCRIPTIONS.mcp_server_slug),
|
|
21
|
-
mcp_json_id: z.number().optional().describe(PARAM_DESCRIPTIONS.mcp_json_id),
|
|
22
18
|
});
|
|
23
19
|
export function runExam(_server, clientFactory) {
|
|
24
20
|
return {
|
|
@@ -41,9 +37,8 @@ tests the MCP server's functionality and returns detailed results.
|
|
|
41
37
|
|
|
42
38
|
**Note:**
|
|
43
39
|
- Use get_proctor_metadata first to discover available runtimes and exams
|
|
44
|
-
- The
|
|
45
|
-
- Custom runtime images require the "__custom__" runtime_id and custom_runtime_image parameter
|
|
46
|
-
- Results can be saved using save_result for future comparison`,
|
|
40
|
+
- The mcp_json must be a valid JSON string representing the mcp.json format
|
|
41
|
+
- Custom runtime images require the "__custom__" runtime_id and custom_runtime_image parameter`,
|
|
47
42
|
inputSchema: {
|
|
48
43
|
type: 'object',
|
|
49
44
|
properties: {
|
|
@@ -55,9 +50,9 @@ tests the MCP server's functionality and returns detailed results.
|
|
|
55
50
|
type: 'string',
|
|
56
51
|
description: PARAM_DESCRIPTIONS.exam_id,
|
|
57
52
|
},
|
|
58
|
-
|
|
53
|
+
mcp_json: {
|
|
59
54
|
type: 'string',
|
|
60
|
-
description: PARAM_DESCRIPTIONS.
|
|
55
|
+
description: PARAM_DESCRIPTIONS.mcp_json,
|
|
61
56
|
},
|
|
62
57
|
server_json: {
|
|
63
58
|
type: 'string',
|
|
@@ -71,29 +66,21 @@ tests the MCP server's functionality and returns detailed results.
|
|
|
71
66
|
type: 'number',
|
|
72
67
|
description: PARAM_DESCRIPTIONS.max_retries,
|
|
73
68
|
},
|
|
74
|
-
mcp_server_slug: {
|
|
75
|
-
type: 'string',
|
|
76
|
-
description: PARAM_DESCRIPTIONS.mcp_server_slug,
|
|
77
|
-
},
|
|
78
|
-
mcp_json_id: {
|
|
79
|
-
type: 'number',
|
|
80
|
-
description: PARAM_DESCRIPTIONS.mcp_json_id,
|
|
81
|
-
},
|
|
82
69
|
},
|
|
83
|
-
required: ['runtime_id', 'exam_id', '
|
|
70
|
+
required: ['runtime_id', 'exam_id', 'mcp_json'],
|
|
84
71
|
},
|
|
85
72
|
handler: async (args) => {
|
|
86
73
|
const validatedArgs = RunExamSchema.parse(args);
|
|
87
|
-
// Validate
|
|
74
|
+
// Validate mcp_json is valid JSON
|
|
88
75
|
try {
|
|
89
|
-
JSON.parse(validatedArgs.
|
|
76
|
+
JSON.parse(validatedArgs.mcp_json);
|
|
90
77
|
}
|
|
91
78
|
catch {
|
|
92
79
|
return {
|
|
93
80
|
content: [
|
|
94
81
|
{
|
|
95
82
|
type: 'text',
|
|
96
|
-
text: 'Error:
|
|
83
|
+
text: 'Error: mcp_json must be a valid JSON string',
|
|
97
84
|
},
|
|
98
85
|
],
|
|
99
86
|
isError: true,
|
|
@@ -120,12 +107,10 @@ tests the MCP server's functionality and returns detailed results.
|
|
|
120
107
|
for await (const entry of client.runExam({
|
|
121
108
|
runtime_id: validatedArgs.runtime_id,
|
|
122
109
|
exam_id: validatedArgs.exam_id,
|
|
123
|
-
|
|
110
|
+
mcp_json: validatedArgs.mcp_json,
|
|
124
111
|
server_json: validatedArgs.server_json,
|
|
125
112
|
custom_runtime_image: validatedArgs.custom_runtime_image,
|
|
126
113
|
max_retries: validatedArgs.max_retries,
|
|
127
|
-
mcp_server_slug: validatedArgs.mcp_server_slug,
|
|
128
|
-
mcp_json_id: validatedArgs.mcp_json_id,
|
|
129
114
|
})) {
|
|
130
115
|
if (entry.type === 'log') {
|
|
131
116
|
const logData = entry.data;
|
package/shared/tools.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare function parseEnabledToolGroups(enabledGroupsParam?: string): Too
|
|
|
31
31
|
*
|
|
32
32
|
* Available tool groups:
|
|
33
33
|
* - exams: All exam-related tools (read + write)
|
|
34
|
-
* - exams_readonly: Exam tools (read only - get_proctor_metadata
|
|
34
|
+
* - exams_readonly: Exam tools (read only - get_proctor_metadata)
|
|
35
35
|
* - machines: All machine management tools (read + write)
|
|
36
36
|
* - machines_readonly: Machine tools (read only - get_machines)
|
|
37
37
|
*
|
package/shared/tools.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
import { getMetadata } from './tools/get-metadata.js';
|
|
3
3
|
import { runExam } from './tools/run-exam.js';
|
|
4
|
-
import { saveResult } from './tools/save-result.js';
|
|
5
|
-
import { getPriorResult } from './tools/get-prior-result.js';
|
|
6
4
|
import { getMachines } from './tools/get-machines.js';
|
|
7
5
|
import { destroyMachine } from './tools/destroy-machine.js';
|
|
8
6
|
import { cancelExam } from './tools/cancel-exam.js';
|
|
@@ -10,8 +8,6 @@ const ALL_TOOLS = [
|
|
|
10
8
|
// Exam tools
|
|
11
9
|
{ factory: getMetadata, group: 'exams', isWriteOperation: false },
|
|
12
10
|
{ factory: runExam, group: 'exams', isWriteOperation: true },
|
|
13
|
-
{ factory: saveResult, group: 'exams', isWriteOperation: true },
|
|
14
|
-
{ factory: getPriorResult, group: 'exams', isWriteOperation: false },
|
|
15
11
|
// Machine management tools
|
|
16
12
|
{ factory: getMachines, group: 'machines', isWriteOperation: false },
|
|
17
13
|
{ factory: destroyMachine, group: 'machines', isWriteOperation: true },
|
|
@@ -81,7 +77,7 @@ function shouldIncludeTool(toolDef, enabledGroups) {
|
|
|
81
77
|
*
|
|
82
78
|
* Available tool groups:
|
|
83
79
|
* - exams: All exam-related tools (read + write)
|
|
84
|
-
* - exams_readonly: Exam tools (read only - get_proctor_metadata
|
|
80
|
+
* - exams_readonly: Exam tools (read only - get_proctor_metadata)
|
|
85
81
|
* - machines: All machine management tools (read + write)
|
|
86
82
|
* - machines_readonly: Machine tools (read only - get_machines)
|
|
87
83
|
*
|
package/shared/types.d.ts
CHANGED
|
@@ -67,48 +67,17 @@ export interface ExamResult {
|
|
|
67
67
|
export interface RunExamParams {
|
|
68
68
|
runtime_id: string;
|
|
69
69
|
exam_id: string;
|
|
70
|
-
|
|
70
|
+
mcp_json: string;
|
|
71
71
|
server_json?: string;
|
|
72
72
|
custom_runtime_image?: string;
|
|
73
73
|
max_retries?: number;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
runtime_id: string;
|
|
82
|
-
exam_id: string;
|
|
83
|
-
mcp_server_slug: string;
|
|
84
|
-
mirror_id: number;
|
|
85
|
-
results: string | Record<string, unknown>;
|
|
86
|
-
custom_runtime_image?: string;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Response from save_result endpoint
|
|
90
|
-
*/
|
|
91
|
-
export interface SaveResultResponse {
|
|
92
|
-
success: boolean;
|
|
93
|
-
id: number;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Parameters for getting prior results
|
|
97
|
-
*/
|
|
98
|
-
export interface PriorResultParams {
|
|
99
|
-
mirror_id: number;
|
|
100
|
-
exam_id: string;
|
|
101
|
-
input_json?: string;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Response from prior_result endpoint
|
|
105
|
-
*/
|
|
106
|
-
export interface PriorResultResponse {
|
|
107
|
-
id: number;
|
|
108
|
-
datetime_performed: string;
|
|
109
|
-
results: ExamResult;
|
|
110
|
-
runtime_image: string;
|
|
111
|
-
match_type: 'exact' | 'entry_key';
|
|
74
|
+
/**
|
|
75
|
+
* When true, OAuth credentials obtained via web bridge are not persisted in the database.
|
|
76
|
+
* Instead, the user receives a one-time copy-to-clipboard page with their credentials.
|
|
77
|
+
* This is useful for the MCP server since it manages its own credential storage.
|
|
78
|
+
* @internal Hardcoded to true in the API client - not exposed as a tool parameter
|
|
79
|
+
*/
|
|
80
|
+
no_result_persistence?: boolean;
|
|
112
81
|
}
|
|
113
82
|
/**
|
|
114
83
|
* Fly.io machine information
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { PriorResultParams, PriorResultResponse } from '../../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Get a prior exam result for comparison
|
|
4
|
-
*/
|
|
5
|
-
export declare function getPriorResult(apiKey: string, baseUrl: string, params: PriorResultParams): Promise<PriorResultResponse>;
|
|
6
|
-
//# sourceMappingURL=get-prior-result.d.ts.map
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Get a prior exam result for comparison
|
|
3
|
-
*/
|
|
4
|
-
export async function getPriorResult(apiKey, baseUrl, params) {
|
|
5
|
-
const url = new URL('/api/proctor/prior_result', baseUrl);
|
|
6
|
-
url.searchParams.append('mirror_id', String(params.mirror_id));
|
|
7
|
-
url.searchParams.append('exam_id', params.exam_id);
|
|
8
|
-
if (params.input_json) {
|
|
9
|
-
url.searchParams.append('input_json', params.input_json);
|
|
10
|
-
}
|
|
11
|
-
const response = await fetch(url.toString(), {
|
|
12
|
-
method: 'GET',
|
|
13
|
-
headers: {
|
|
14
|
-
'X-API-Key': apiKey,
|
|
15
|
-
Accept: 'application/json',
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
if (!response.ok) {
|
|
19
|
-
if (response.status === 401) {
|
|
20
|
-
throw new Error('Invalid API key');
|
|
21
|
-
}
|
|
22
|
-
if (response.status === 403) {
|
|
23
|
-
throw new Error('User lacks admin privileges or insufficient permissions');
|
|
24
|
-
}
|
|
25
|
-
if (response.status === 400) {
|
|
26
|
-
const errorData = (await response.json());
|
|
27
|
-
throw new Error(`Bad request: ${errorData.error || 'Missing required parameters'}`);
|
|
28
|
-
}
|
|
29
|
-
if (response.status === 404) {
|
|
30
|
-
throw new Error('No prior result found');
|
|
31
|
-
}
|
|
32
|
-
throw new Error(`Failed to get prior result: ${response.status} ${response.statusText}`);
|
|
33
|
-
}
|
|
34
|
-
return (await response.json());
|
|
35
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { SaveResultParams, SaveResultResponse } from '../../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Save exam results to the database
|
|
4
|
-
*/
|
|
5
|
-
export declare function saveResult(apiKey: string, baseUrl: string, params: SaveResultParams): Promise<SaveResultResponse>;
|
|
6
|
-
//# sourceMappingURL=save-result.d.ts.map
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Save exam results to the database
|
|
3
|
-
*/
|
|
4
|
-
export async function saveResult(apiKey, baseUrl, params) {
|
|
5
|
-
const url = new URL('/api/proctor/save_result', baseUrl);
|
|
6
|
-
const body = {
|
|
7
|
-
runtime_id: params.runtime_id,
|
|
8
|
-
exam_id: params.exam_id,
|
|
9
|
-
mcp_server_slug: params.mcp_server_slug,
|
|
10
|
-
mirror_id: params.mirror_id,
|
|
11
|
-
results: typeof params.results === 'string' ? params.results : JSON.stringify(params.results),
|
|
12
|
-
};
|
|
13
|
-
if (params.custom_runtime_image) {
|
|
14
|
-
body.custom_runtime_image = params.custom_runtime_image;
|
|
15
|
-
}
|
|
16
|
-
const response = await fetch(url.toString(), {
|
|
17
|
-
method: 'POST',
|
|
18
|
-
headers: {
|
|
19
|
-
'X-API-Key': apiKey,
|
|
20
|
-
'Content-Type': 'application/json',
|
|
21
|
-
Accept: 'application/json',
|
|
22
|
-
},
|
|
23
|
-
body: JSON.stringify(body),
|
|
24
|
-
});
|
|
25
|
-
if (!response.ok) {
|
|
26
|
-
if (response.status === 401) {
|
|
27
|
-
throw new Error('Invalid API key');
|
|
28
|
-
}
|
|
29
|
-
if (response.status === 403) {
|
|
30
|
-
throw new Error('User lacks admin privileges or insufficient permissions');
|
|
31
|
-
}
|
|
32
|
-
if (response.status === 404) {
|
|
33
|
-
throw new Error('Mirror not found');
|
|
34
|
-
}
|
|
35
|
-
if (response.status === 422) {
|
|
36
|
-
const errorData = (await response.json());
|
|
37
|
-
throw new Error(`Validation error: ${errorData.error || 'Unknown validation error'}`);
|
|
38
|
-
}
|
|
39
|
-
throw new Error(`Failed to save result: ${response.status} ${response.statusText}`);
|
|
40
|
-
}
|
|
41
|
-
return (await response.json());
|
|
42
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import type { ClientFactory } from '../server.js';
|
|
3
|
-
export declare function getPriorResult(_server: Server, clientFactory: ClientFactory): {
|
|
4
|
-
name: string;
|
|
5
|
-
description: string;
|
|
6
|
-
inputSchema: {
|
|
7
|
-
type: string;
|
|
8
|
-
properties: {
|
|
9
|
-
mirror_id: {
|
|
10
|
-
type: string;
|
|
11
|
-
description: "ID of the unofficial mirror to get prior results for.";
|
|
12
|
-
};
|
|
13
|
-
exam_id: {
|
|
14
|
-
type: string;
|
|
15
|
-
description: "Exam ID to filter results by.";
|
|
16
|
-
};
|
|
17
|
-
input_json: {
|
|
18
|
-
type: string;
|
|
19
|
-
description: "Optional JSON string of the current mcp.json for matching. If provided, returns the most recent result with matching config.";
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
required: string[];
|
|
23
|
-
};
|
|
24
|
-
handler: (args: unknown) => Promise<{
|
|
25
|
-
content: {
|
|
26
|
-
type: string;
|
|
27
|
-
text: string;
|
|
28
|
-
}[];
|
|
29
|
-
isError?: undefined;
|
|
30
|
-
} | {
|
|
31
|
-
content: {
|
|
32
|
-
type: string;
|
|
33
|
-
text: string;
|
|
34
|
-
}[];
|
|
35
|
-
isError: boolean;
|
|
36
|
-
}>;
|
|
37
|
-
};
|
|
38
|
-
//# sourceMappingURL=get-prior-result.d.ts.map
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
// Parameter descriptions - single source of truth
|
|
3
|
-
const PARAM_DESCRIPTIONS = {
|
|
4
|
-
mirror_id: 'ID of the unofficial mirror to get prior results for.',
|
|
5
|
-
exam_id: 'Exam ID to filter results by.',
|
|
6
|
-
input_json: 'Optional JSON string of the current mcp.json for matching. If provided, returns the most recent result with matching config.',
|
|
7
|
-
};
|
|
8
|
-
const GetPriorResultSchema = z.object({
|
|
9
|
-
mirror_id: z.number().describe(PARAM_DESCRIPTIONS.mirror_id),
|
|
10
|
-
exam_id: z.string().min(1).describe(PARAM_DESCRIPTIONS.exam_id),
|
|
11
|
-
input_json: z.string().optional().describe(PARAM_DESCRIPTIONS.input_json),
|
|
12
|
-
});
|
|
13
|
-
export function getPriorResult(_server, clientFactory) {
|
|
14
|
-
return {
|
|
15
|
-
name: 'get_prior_result',
|
|
16
|
-
description: `Retrieve a previous exam result for comparison.
|
|
17
|
-
|
|
18
|
-
Finds the most recent prior result for the specified mirror and exam, optionally
|
|
19
|
-
matching against the current input configuration.
|
|
20
|
-
|
|
21
|
-
**Returns:**
|
|
22
|
-
- id: Result record ID
|
|
23
|
-
- datetime_performed: When the exam was run (ISO 8601)
|
|
24
|
-
- results: The full exam results
|
|
25
|
-
- runtime_image: Docker image used for the exam
|
|
26
|
-
- match_type: "exact" if mcp.json matches exactly, "entry_key" if only entry key matches
|
|
27
|
-
|
|
28
|
-
**Use cases:**
|
|
29
|
-
- Compare current test results with previous runs
|
|
30
|
-
- Detect regressions in MCP server functionality
|
|
31
|
-
- Review historical test outcomes
|
|
32
|
-
- Validate that changes haven't broken existing behavior
|
|
33
|
-
|
|
34
|
-
**Note:**
|
|
35
|
-
- Returns 404 if no prior result exists
|
|
36
|
-
- The match_type indicates how closely the prior result matches your input_json`,
|
|
37
|
-
inputSchema: {
|
|
38
|
-
type: 'object',
|
|
39
|
-
properties: {
|
|
40
|
-
mirror_id: {
|
|
41
|
-
type: 'number',
|
|
42
|
-
description: PARAM_DESCRIPTIONS.mirror_id,
|
|
43
|
-
},
|
|
44
|
-
exam_id: {
|
|
45
|
-
type: 'string',
|
|
46
|
-
description: PARAM_DESCRIPTIONS.exam_id,
|
|
47
|
-
},
|
|
48
|
-
input_json: {
|
|
49
|
-
type: 'string',
|
|
50
|
-
description: PARAM_DESCRIPTIONS.input_json,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
required: ['mirror_id', 'exam_id'],
|
|
54
|
-
},
|
|
55
|
-
handler: async (args) => {
|
|
56
|
-
const validatedArgs = GetPriorResultSchema.parse(args);
|
|
57
|
-
const client = clientFactory();
|
|
58
|
-
try {
|
|
59
|
-
const response = await client.getPriorResult({
|
|
60
|
-
mirror_id: validatedArgs.mirror_id,
|
|
61
|
-
exam_id: validatedArgs.exam_id,
|
|
62
|
-
input_json: validatedArgs.input_json,
|
|
63
|
-
});
|
|
64
|
-
let content = '## Prior Result\n\n';
|
|
65
|
-
content += `**Result ID:** ${response.id}\n`;
|
|
66
|
-
content += `**Date Performed:** ${response.datetime_performed}\n`;
|
|
67
|
-
content += `**Runtime Image:** ${response.runtime_image}\n`;
|
|
68
|
-
content += `**Match Type:** ${response.match_type}\n\n`;
|
|
69
|
-
content += '### Results\n\n```json\n';
|
|
70
|
-
content += JSON.stringify(response.results, null, 2);
|
|
71
|
-
content += '\n```\n';
|
|
72
|
-
return {
|
|
73
|
-
content: [
|
|
74
|
-
{
|
|
75
|
-
type: 'text',
|
|
76
|
-
text: content.trim(),
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
-
// Handle "no prior result found" as a non-error case
|
|
84
|
-
if (message.includes('No prior result found')) {
|
|
85
|
-
return {
|
|
86
|
-
content: [
|
|
87
|
-
{
|
|
88
|
-
type: 'text',
|
|
89
|
-
text: 'No prior result found for this mirror and exam combination.',
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
content: [
|
|
96
|
-
{
|
|
97
|
-
type: 'text',
|
|
98
|
-
text: `Error getting prior result: ${message}`,
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
isError: true,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import type { ClientFactory } from '../server.js';
|
|
3
|
-
export declare function saveResult(_server: Server, clientFactory: ClientFactory): {
|
|
4
|
-
name: string;
|
|
5
|
-
description: string;
|
|
6
|
-
inputSchema: {
|
|
7
|
-
type: string;
|
|
8
|
-
properties: {
|
|
9
|
-
runtime_id: {
|
|
10
|
-
type: string;
|
|
11
|
-
description: "Runtime ID used for the exam, or \"__custom__\" if a custom Docker image was used.";
|
|
12
|
-
};
|
|
13
|
-
exam_id: {
|
|
14
|
-
type: string;
|
|
15
|
-
description: "Exam ID that was executed.";
|
|
16
|
-
};
|
|
17
|
-
mcp_server_slug: {
|
|
18
|
-
type: string;
|
|
19
|
-
description: "Slug of the MCP server that was tested.";
|
|
20
|
-
};
|
|
21
|
-
mirror_id: {
|
|
22
|
-
type: string;
|
|
23
|
-
description: "ID of the unofficial mirror associated with this test.";
|
|
24
|
-
};
|
|
25
|
-
results: {
|
|
26
|
-
oneOf: {
|
|
27
|
-
type: string;
|
|
28
|
-
}[];
|
|
29
|
-
description: "Exam results as a JSON string or object. This is the full result from run_exam.";
|
|
30
|
-
};
|
|
31
|
-
custom_runtime_image: {
|
|
32
|
-
type: string;
|
|
33
|
-
description: "Required if runtime_id is \"__custom__\". The Docker image URL that was used.";
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
required: string[];
|
|
37
|
-
};
|
|
38
|
-
handler: (args: unknown) => Promise<{
|
|
39
|
-
content: {
|
|
40
|
-
type: string;
|
|
41
|
-
text: string;
|
|
42
|
-
}[];
|
|
43
|
-
isError: boolean;
|
|
44
|
-
} | {
|
|
45
|
-
content: {
|
|
46
|
-
type: string;
|
|
47
|
-
text: string;
|
|
48
|
-
}[];
|
|
49
|
-
isError?: undefined;
|
|
50
|
-
}>;
|
|
51
|
-
};
|
|
52
|
-
//# sourceMappingURL=save-result.d.ts.map
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
// Parameter descriptions - single source of truth
|
|
3
|
-
const PARAM_DESCRIPTIONS = {
|
|
4
|
-
runtime_id: 'Runtime ID used for the exam, or "__custom__" if a custom Docker image was used.',
|
|
5
|
-
exam_id: 'Exam ID that was executed.',
|
|
6
|
-
mcp_server_slug: 'Slug of the MCP server that was tested.',
|
|
7
|
-
mirror_id: 'ID of the unofficial mirror associated with this test.',
|
|
8
|
-
results: 'Exam results as a JSON string or object. This is the full result from run_exam.',
|
|
9
|
-
custom_runtime_image: 'Required if runtime_id is "__custom__". The Docker image URL that was used.',
|
|
10
|
-
};
|
|
11
|
-
const SaveResultSchema = z.object({
|
|
12
|
-
runtime_id: z.string().min(1).describe(PARAM_DESCRIPTIONS.runtime_id),
|
|
13
|
-
exam_id: z.string().min(1).describe(PARAM_DESCRIPTIONS.exam_id),
|
|
14
|
-
mcp_server_slug: z.string().min(1).describe(PARAM_DESCRIPTIONS.mcp_server_slug),
|
|
15
|
-
mirror_id: z.number().describe(PARAM_DESCRIPTIONS.mirror_id),
|
|
16
|
-
results: z.union([z.string(), z.record(z.unknown())]).describe(PARAM_DESCRIPTIONS.results),
|
|
17
|
-
custom_runtime_image: z.string().optional().describe(PARAM_DESCRIPTIONS.custom_runtime_image),
|
|
18
|
-
});
|
|
19
|
-
export function saveResult(_server, clientFactory) {
|
|
20
|
-
return {
|
|
21
|
-
name: 'save_result',
|
|
22
|
-
description: `Save exam results to the database for future comparison.
|
|
23
|
-
|
|
24
|
-
Stores the results of a Proctor exam run so they can be retrieved later for
|
|
25
|
-
comparison with new test runs.
|
|
26
|
-
|
|
27
|
-
**Returns:**
|
|
28
|
-
- success: boolean indicating if the save was successful
|
|
29
|
-
- id: ID of the saved result record
|
|
30
|
-
|
|
31
|
-
**Use cases:**
|
|
32
|
-
- Persist exam results after running tests
|
|
33
|
-
- Create a baseline for future comparisons
|
|
34
|
-
- Track test history for an MCP server
|
|
35
|
-
- Enable regression testing by comparing against prior results
|
|
36
|
-
|
|
37
|
-
**Note:**
|
|
38
|
-
- The mirror_id must be a valid unofficial mirror ID
|
|
39
|
-
- Results should be the full output from run_exam
|
|
40
|
-
- Custom runtime images require the custom_runtime_image parameter`,
|
|
41
|
-
inputSchema: {
|
|
42
|
-
type: 'object',
|
|
43
|
-
properties: {
|
|
44
|
-
runtime_id: {
|
|
45
|
-
type: 'string',
|
|
46
|
-
description: PARAM_DESCRIPTIONS.runtime_id,
|
|
47
|
-
},
|
|
48
|
-
exam_id: {
|
|
49
|
-
type: 'string',
|
|
50
|
-
description: PARAM_DESCRIPTIONS.exam_id,
|
|
51
|
-
},
|
|
52
|
-
mcp_server_slug: {
|
|
53
|
-
type: 'string',
|
|
54
|
-
description: PARAM_DESCRIPTIONS.mcp_server_slug,
|
|
55
|
-
},
|
|
56
|
-
mirror_id: {
|
|
57
|
-
type: 'number',
|
|
58
|
-
description: PARAM_DESCRIPTIONS.mirror_id,
|
|
59
|
-
},
|
|
60
|
-
results: {
|
|
61
|
-
oneOf: [{ type: 'string' }, { type: 'object' }],
|
|
62
|
-
description: PARAM_DESCRIPTIONS.results,
|
|
63
|
-
},
|
|
64
|
-
custom_runtime_image: {
|
|
65
|
-
type: 'string',
|
|
66
|
-
description: PARAM_DESCRIPTIONS.custom_runtime_image,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
required: ['runtime_id', 'exam_id', 'mcp_server_slug', 'mirror_id', 'results'],
|
|
70
|
-
},
|
|
71
|
-
handler: async (args) => {
|
|
72
|
-
const validatedArgs = SaveResultSchema.parse(args);
|
|
73
|
-
// Validate custom runtime requirements
|
|
74
|
-
if (validatedArgs.runtime_id === '__custom__' && !validatedArgs.custom_runtime_image) {
|
|
75
|
-
return {
|
|
76
|
-
content: [
|
|
77
|
-
{
|
|
78
|
-
type: 'text',
|
|
79
|
-
text: 'Error: custom_runtime_image is required when runtime_id is "__custom__"',
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
isError: true,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
const client = clientFactory();
|
|
86
|
-
try {
|
|
87
|
-
const response = await client.saveResult({
|
|
88
|
-
runtime_id: validatedArgs.runtime_id,
|
|
89
|
-
exam_id: validatedArgs.exam_id,
|
|
90
|
-
mcp_server_slug: validatedArgs.mcp_server_slug,
|
|
91
|
-
mirror_id: validatedArgs.mirror_id,
|
|
92
|
-
results: validatedArgs.results,
|
|
93
|
-
custom_runtime_image: validatedArgs.custom_runtime_image,
|
|
94
|
-
});
|
|
95
|
-
let content = '## Result Saved\n\n';
|
|
96
|
-
content += `**Success:** ${response.success}\n`;
|
|
97
|
-
content += `**Result ID:** ${response.id}\n\n`;
|
|
98
|
-
content +=
|
|
99
|
-
'The exam result has been saved and can be retrieved for comparison using get_prior_result.';
|
|
100
|
-
return {
|
|
101
|
-
content: [
|
|
102
|
-
{
|
|
103
|
-
type: 'text',
|
|
104
|
-
text: content.trim(),
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
return {
|
|
111
|
-
content: [
|
|
112
|
-
{
|
|
113
|
-
type: 'text',
|
|
114
|
-
text: `Error saving result: ${error instanceof Error ? error.message : String(error)}`,
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
isError: true,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|