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 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
- | `save_result` | exams | write | Save exam results to the database for future comparison. |
44
- | `get_prior_result` | exams | read | Retrieve a previous exam result for comparison. |
45
- | `get_machines` | machines | read | List active Fly.io machines used for Proctor exams. |
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` | 4 | Full exam execution (read + write) |
61
- | `exams_readonly` | 2 | Exam metadata and results (read only) |
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`, `get_prior_result`
69
- - Write: `run_exam`, `save_result`
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` first to discover available runtimes and exam types
118
- - The `mcp_config` parameter for `run_exam` must be a valid JSON string
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
- ## Cheatsheet
179
-
180
- Quick setup:
181
-
182
- ```bash
183
- # Install dependencies
184
- npm run install-all
157
+ ## Claude Desktop
185
158
 
186
- # Build the server
187
- npm run build
159
+ Make sure you have your Proctor API key ready.
188
160
 
189
- # Set your API key
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
- # Run the server
193
- cd local && npm start
194
- ```
163
+ ### Manual Setup
195
164
 
196
- ## Claude Desktop
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
- Add to your Claude Desktop configuration:
167
+ macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
199
168
 
200
- ### macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
169
+ Windows: `%APPDATA%\Claude\claude_desktop_config.json`
201
170
 
202
- ### Windows: `%APPDATA%\Claude\claude_desktop_config.json`
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": "node",
209
- "args": ["/path/to/proctor/local/build/index.js"],
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": "node",
226
- "args": ["/path/to/proctor/local/build/index.js"],
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
- ### Manual Setup
207
+ ## Development
237
208
 
238
- If you prefer to run the server manually:
209
+ ### Quick Setup
239
210
 
240
211
  ```bash
241
- cd /path/to/proctor/local
242
- PROCTOR_API_KEY="your-api-key-here" node build/index.js
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(params) {
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: new Date().toISOString(), message: 'Starting exam...' },
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: 'success',
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: 'tool_listing', passed: true },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proctor-mcp-server",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Local implementation of Proctor MCP server",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
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, SaveResultParams, SaveResultResponse, PriorResultParams, PriorResultResponse, FlyMachine, MachinesResponse, CancelExamParams, CancelExamResponse, ApiError, } from './types.js';
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.mcp_config,
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: {
@@ -1,10 +1,8 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import type { ProctorMetadataResponse, RunExamParams, ExamStreamEntry, SaveResultParams, SaveResultResponse, PriorResultParams, PriorResultResponse, MachinesResponse, CancelExamParams, CancelExamResponse } from './types.js';
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.0',
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 before running an exam
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
- mcp_config: {
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
  };
@@ -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
- mcp_config: 'JSON string of the mcp.json configuration for the MCP server. Must be a valid JSON object with server configurations.',
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
- mcp_config: z.string().min(1).describe(PARAM_DESCRIPTIONS.mcp_config),
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 mcp_config must be a valid JSON string representing the mcp.json format
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
- mcp_config: {
53
+ mcp_json: {
59
54
  type: 'string',
60
- description: PARAM_DESCRIPTIONS.mcp_config,
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', 'mcp_config'],
70
+ required: ['runtime_id', 'exam_id', 'mcp_json'],
84
71
  },
85
72
  handler: async (args) => {
86
73
  const validatedArgs = RunExamSchema.parse(args);
87
- // Validate mcp_config is valid JSON
74
+ // Validate mcp_json is valid JSON
88
75
  try {
89
- JSON.parse(validatedArgs.mcp_config);
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: mcp_config must be a valid JSON string',
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
- mcp_config: validatedArgs.mcp_config,
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, get_prior_result)
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, get_prior_result)
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
- mcp_config: string;
70
+ mcp_json: string;
71
71
  server_json?: string;
72
72
  custom_runtime_image?: string;
73
73
  max_retries?: number;
74
- mcp_server_slug?: string;
75
- mcp_json_id?: number;
76
- }
77
- /**
78
- * Parameters for saving exam results
79
- */
80
- export interface SaveResultParams {
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
- }