proctor-mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +247 -0
  2. package/build/index.integration-with-mock.js +143 -0
  3. package/build/index.js +57 -0
  4. package/package.json +43 -0
  5. package/shared/index.d.ts +7 -0
  6. package/shared/index.js +4 -0
  7. package/shared/logging.d.ts +20 -0
  8. package/shared/logging.js +34 -0
  9. package/shared/proctor-client/lib/cancel-exam.d.ts +6 -0
  10. package/shared/proctor-client/lib/cancel-exam.js +36 -0
  11. package/shared/proctor-client/lib/destroy-machine.d.ts +7 -0
  12. package/shared/proctor-client/lib/destroy-machine.js +31 -0
  13. package/shared/proctor-client/lib/get-machines.d.ts +6 -0
  14. package/shared/proctor-client/lib/get-machines.js +27 -0
  15. package/shared/proctor-client/lib/get-metadata.d.ts +6 -0
  16. package/shared/proctor-client/lib/get-metadata.js +23 -0
  17. package/shared/proctor-client/lib/get-prior-result.d.ts +6 -0
  18. package/shared/proctor-client/lib/get-prior-result.js +35 -0
  19. package/shared/proctor-client/lib/run-exam.d.ts +7 -0
  20. package/shared/proctor-client/lib/run-exam.js +90 -0
  21. package/shared/proctor-client/lib/save-result.d.ts +6 -0
  22. package/shared/proctor-client/lib/save-result.js +42 -0
  23. package/shared/server.d.ts +66 -0
  24. package/shared/server.js +65 -0
  25. package/shared/tools/cancel-exam.d.ts +34 -0
  26. package/shared/tools/cancel-exam.js +99 -0
  27. package/shared/tools/destroy-machine.d.ts +30 -0
  28. package/shared/tools/destroy-machine.js +75 -0
  29. package/shared/tools/get-machines.d.ts +25 -0
  30. package/shared/tools/get-machines.js +83 -0
  31. package/shared/tools/get-metadata.d.ts +25 -0
  32. package/shared/tools/get-metadata.js +63 -0
  33. package/shared/tools/get-prior-result.d.ts +38 -0
  34. package/shared/tools/get-prior-result.js +106 -0
  35. package/shared/tools/run-exam.d.ts +58 -0
  36. package/shared/tools/run-exam.js +189 -0
  37. package/shared/tools/save-result.d.ts +52 -0
  38. package/shared/tools/save-result.js +122 -0
  39. package/shared/tools.d.ts +44 -0
  40. package/shared/tools.js +128 -0
  41. package/shared/types.d.ts +151 -0
  42. package/shared/types.js +4 -0
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # Proctor MCP Server
2
+
3
+ Haven't heard about MCP yet? The easiest way to keep up-to-date is to read our [weekly newsletter at PulseMCP](https://www.pulsemcp.com/).
4
+
5
+ ---
6
+
7
+ This is an MCP ([Model Context Protocol](https://modelcontextprotocol.io/)) Server for running Proctor exams against MCP servers. It provides tools for executing tests, managing exam infrastructure, and tracking test results through direct integration with the [PulseMCP Proctor API](https://admin.pulsemcp.com).
8
+
9
+ **Note**: This is an internal tool for the PulseMCP team. The source code is public for reference purposes, but the server requires API keys that are not publicly available.
10
+
11
+ # Table of Contents
12
+
13
+ - [Highlights](#highlights)
14
+ - [Capabilities](#capabilities)
15
+ - [Tool Groups](#tool-groups)
16
+ - [Usage Tips](#usage-tips)
17
+ - [Examples](#examples)
18
+ - [Setup](#setup)
19
+ - [Cheatsheet](#cheatsheet)
20
+ - [Claude Desktop](#claude-desktop)
21
+ - [Manual Setup](#manual-setup)
22
+
23
+ # Highlights
24
+
25
+ **Exam Execution**: Run Proctor exams against MCP servers to test functionality and protocol compliance.
26
+
27
+ **Result Management**: Save and retrieve exam results for comparison and regression testing.
28
+
29
+ **Infrastructure Control**: List, manage, and clean up Fly.io machines used for exam execution.
30
+
31
+ **Tool Groups**: Enable/disable tool groups via `TOOL_GROUPS` environment variable. Each group has a base variant (full access) and a `_readonly` variant (read-only access).
32
+
33
+ **Streaming Results**: Real-time exam execution logs with NDJSON streaming.
34
+
35
+ # Capabilities
36
+
37
+ This server is built and tested on macOS with Claude Desktop. It should work with other MCP clients as well.
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. |
48
+
49
+ # Tool Groups
50
+
51
+ This server organizes tools into groups that can be selectively enabled or disabled. Each group has two variants:
52
+
53
+ - **Base group** (e.g., `exams`): Full read + write access
54
+ - **Readonly group** (e.g., `exams_readonly`): Read-only access
55
+
56
+ ## Available Groups
57
+
58
+ | Group | Tools | Description |
59
+ | ------------------- | ----- | -------------------------------------- |
60
+ | `exams` | 4 | Full exam execution (read + write) |
61
+ | `exams_readonly` | 2 | Exam metadata and results (read only) |
62
+ | `machines` | 3 | Full machine management (read + write) |
63
+ | `machines_readonly` | 1 | Machine listing (read only) |
64
+
65
+ ### Tools by Group
66
+
67
+ - **exams** / **exams_readonly**:
68
+ - Read-only: `get_proctor_metadata`, `get_prior_result`
69
+ - Write: `run_exam`, `save_result`
70
+ - **machines** / **machines_readonly**:
71
+ - Read-only: `get_machines`
72
+ - Write: `destroy_machine`, `cancel_exam`
73
+
74
+ ## Environment Variables
75
+
76
+ | Variable | Description | Default |
77
+ | ----------------- | ------------------------------------------- | ----------------------------- |
78
+ | `PROCTOR_API_KEY` | API key for PulseMCP Proctor API (required) | - |
79
+ | `PROCTOR_API_URL` | Base URL for Proctor API | `https://admin.pulsemcp.com` |
80
+ | `TOOL_GROUPS` | Comma-separated list of enabled tool groups | `exams,machines` (all groups) |
81
+
82
+ ## Examples
83
+
84
+ Enable all tools with full access (default):
85
+
86
+ ```bash
87
+ # No TOOL_GROUPS needed - all base groups enabled
88
+ ```
89
+
90
+ Enable only exam tools:
91
+
92
+ ```bash
93
+ TOOL_GROUPS=exams
94
+ ```
95
+
96
+ Enable machines with read-only access:
97
+
98
+ ```bash
99
+ TOOL_GROUPS=machines_readonly
100
+ ```
101
+
102
+ Enable all groups with read-only access:
103
+
104
+ ```bash
105
+ TOOL_GROUPS=exams_readonly,machines_readonly
106
+ ```
107
+
108
+ Mix full and read-only access per group:
109
+
110
+ ```bash
111
+ # Full exam access, read-only machines
112
+ TOOL_GROUPS=exams,machines_readonly
113
+ ```
114
+
115
+ # Usage Tips
116
+
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`
120
+ - Use `get_machines` to monitor active exam infrastructure
121
+ - Clean up machines with `destroy_machine` when no longer needed
122
+ - Use `cancel_exam` to stop a stuck or slow exam before destroying the machine
123
+
124
+ # Examples
125
+
126
+ ## Get Available Exams
127
+
128
+ ```
129
+ User: What exams can I run with Proctor?
130
+ Assistant: I'll check what exams are available.
131
+
132
+ [Calls get_proctor_metadata]
133
+
134
+ Here are the available exams:
135
+
136
+ **Runtimes:**
137
+ - Proctor v0.0.37 (id: v0.0.37)
138
+
139
+ **Exams:**
140
+ - Auth Check (id: proctor-mcp-client-auth-check) - Verifies authentication mechanisms
141
+ - Init Tools List (id: proctor-mcp-client-init-tools-list) - Tests initialization and tool listing
142
+ ```
143
+
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
+ ## Monitor Infrastructure
163
+
164
+ ```
165
+ User: What machines are currently running?
166
+ Assistant: I'll check the active Fly machines.
167
+
168
+ [Calls get_machines]
169
+
170
+ There are 2 active machines:
171
+
172
+ 1. **machine-abc123** - Running in sjc region (created 10 minutes ago)
173
+ 2. **machine-def456** - Stopped in iad region (created 1 hour ago)
174
+ ```
175
+
176
+ # Setup
177
+
178
+ ## Cheatsheet
179
+
180
+ Quick setup:
181
+
182
+ ```bash
183
+ # Install dependencies
184
+ npm run install-all
185
+
186
+ # Build the server
187
+ npm run build
188
+
189
+ # Set your API key
190
+ export PROCTOR_API_KEY="your-api-key-here"
191
+
192
+ # Run the server
193
+ cd local && npm start
194
+ ```
195
+
196
+ ## Claude Desktop
197
+
198
+ Add to your Claude Desktop configuration:
199
+
200
+ ### macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
201
+
202
+ ### Windows: `%APPDATA%\Claude\claude_desktop_config.json`
203
+
204
+ ```json
205
+ {
206
+ "mcpServers": {
207
+ "proctor": {
208
+ "command": "node",
209
+ "args": ["/path/to/proctor/local/build/index.js"],
210
+ "env": {
211
+ "PROCTOR_API_KEY": "your-api-key-here",
212
+ "TOOL_GROUPS": "exams,machines"
213
+ }
214
+ }
215
+ }
216
+ }
217
+ ```
218
+
219
+ For read-only access:
220
+
221
+ ```json
222
+ {
223
+ "mcpServers": {
224
+ "proctor-readonly": {
225
+ "command": "node",
226
+ "args": ["/path/to/proctor/local/build/index.js"],
227
+ "env": {
228
+ "PROCTOR_API_KEY": "your-api-key-here",
229
+ "TOOL_GROUPS": "exams_readonly,machines_readonly"
230
+ }
231
+ }
232
+ }
233
+ }
234
+ ```
235
+
236
+ ### Manual Setup
237
+
238
+ If you prefer to run the server manually:
239
+
240
+ ```bash
241
+ cd /path/to/proctor/local
242
+ PROCTOR_API_KEY="your-api-key-here" node build/index.js
243
+ ```
244
+
245
+ ## License
246
+
247
+ MIT
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Integration test entry point with mock client
4
+ * This file is used for testing the MCP server with mocked external API calls
5
+ */
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { createMCPServer, logServerStart, logError } from '../shared/index.js';
8
+ /**
9
+ * Integration mock implementation of IProctorClient
10
+ */
11
+ class IntegrationMockProctorClient {
12
+ priorResults = new Map();
13
+ savedResultId = 0;
14
+ async getMetadata() {
15
+ return {
16
+ runtimes: [
17
+ {
18
+ id: 'v0.0.37',
19
+ name: 'Proctor v0.0.37',
20
+ image: 'registry.fly.io/proctor:v0.0.37',
21
+ },
22
+ {
23
+ id: 'v0.0.36',
24
+ name: 'Proctor v0.0.36',
25
+ image: 'registry.fly.io/proctor:v0.0.36',
26
+ },
27
+ ],
28
+ exams: [
29
+ {
30
+ id: 'proctor-mcp-client-auth-check',
31
+ name: 'Auth Check',
32
+ description: 'Verifies authentication mechanisms',
33
+ },
34
+ {
35
+ id: 'proctor-mcp-client-init-tools-list',
36
+ name: 'Init Tools List',
37
+ description: 'Tests initialization and tool listing',
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ async *runExam(params) {
43
+ yield {
44
+ 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' },
58
+ };
59
+ yield {
60
+ type: 'result',
61
+ data: {
62
+ status: 'success',
63
+ input: {
64
+ 'mcp.json': JSON.parse(params.mcp_config),
65
+ },
66
+ tests: [
67
+ { name: 'initialization', passed: true },
68
+ { name: 'tool_listing', passed: true },
69
+ ],
70
+ },
71
+ };
72
+ }
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
+ async getMachines() {
98
+ return {
99
+ machines: [
100
+ {
101
+ id: 'machine-123',
102
+ name: 'proctor-exam-1',
103
+ state: 'running',
104
+ region: 'sjc',
105
+ created_at: '2024-01-15T10:30:00Z',
106
+ },
107
+ {
108
+ id: 'machine-456',
109
+ name: 'proctor-exam-2',
110
+ state: 'stopped',
111
+ region: 'iad',
112
+ created_at: '2024-01-15T09:00:00Z',
113
+ },
114
+ ],
115
+ };
116
+ }
117
+ async destroyMachine(_machineId) {
118
+ return { success: true };
119
+ }
120
+ async cancelExam(_params) {
121
+ return {
122
+ success: true,
123
+ message: 'Exam cancelled successfully',
124
+ };
125
+ }
126
+ }
127
+ async function main() {
128
+ // Create server using factory
129
+ const { server, registerHandlers } = createMCPServer();
130
+ // Create mock client for testing
131
+ const mockClient = new IntegrationMockProctorClient();
132
+ // Register all handlers with mock client
133
+ await registerHandlers(server, () => mockClient);
134
+ // Start server
135
+ const transport = new StdioServerTransport();
136
+ await server.connect(transport);
137
+ logServerStart('proctor-mcp-server (integration-mock)');
138
+ }
139
+ // Run the server
140
+ main().catch((error) => {
141
+ logError('main', error);
142
+ process.exit(1);
143
+ });
package/build/index.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createMCPServer } from '../shared/index.js';
4
+ import { logServerStart, logError } from '../shared/logging.js';
5
+ // Validate required environment variables before starting
6
+ function validateEnvironment() {
7
+ const required = [
8
+ {
9
+ name: 'PROCTOR_API_KEY',
10
+ description: 'API key for PulseMCP Proctor API authentication',
11
+ },
12
+ ];
13
+ const optional = [
14
+ {
15
+ name: 'PROCTOR_API_URL',
16
+ description: 'Base URL for Proctor API (default: https://admin.pulsemcp.com)',
17
+ },
18
+ {
19
+ name: 'TOOL_GROUPS',
20
+ description: 'Comma-separated list of enabled tool groups (default: all)',
21
+ },
22
+ ];
23
+ const missing = required.filter(({ name }) => !process.env[name]);
24
+ if (missing.length > 0) {
25
+ logError('validateEnvironment', 'Missing required environment variables:');
26
+ missing.forEach(({ name, description }) => {
27
+ console.error(` - ${name}: ${description}`);
28
+ });
29
+ if (optional.length > 0) {
30
+ console.error('\nOptional environment variables:');
31
+ optional.forEach(({ name, description }) => {
32
+ console.error(` - ${name}: ${description}`);
33
+ });
34
+ }
35
+ console.error('\nPlease set the required environment variables and try again.');
36
+ console.error('Example:');
37
+ console.error(' export PROCTOR_API_KEY="your-api-key"');
38
+ process.exit(1);
39
+ }
40
+ }
41
+ async function main() {
42
+ // Validate environment variables first
43
+ validateEnvironment();
44
+ // Create server using factory
45
+ const { server, registerHandlers } = createMCPServer();
46
+ // Register all handlers (resources and tools)
47
+ await registerHandlers(server);
48
+ // Start server
49
+ const transport = new StdioServerTransport();
50
+ await server.connect(transport);
51
+ logServerStart('proctor-mcp-server');
52
+ }
53
+ // Run the server
54
+ main().catch((error) => {
55
+ logError('main', error);
56
+ process.exit(1);
57
+ });
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "proctor-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "Local implementation of Proctor MCP server",
5
+ "main": "build/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "proctor-mcp-server": "./build/index.js"
9
+ },
10
+ "files": [
11
+ "build/**/*.js",
12
+ "build/**/*.d.ts",
13
+ "shared/**/*.js",
14
+ "shared/**/*.d.ts",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc && npm run build:integration",
19
+ "build:integration": "tsc -p tsconfig.integration.json",
20
+ "start": "node build/index.js",
21
+ "dev": "tsx src/index.ts",
22
+ "predev": "cd ../shared && npm run build && cd ../local && node setup-dev.js",
23
+ "prebuild": "cd ../shared && npm run build && cd ../local && node setup-dev.js",
24
+ "prepublishOnly": "node prepare-publish.js && node ../scripts/prepare-npm-readme.js",
25
+ "lint": "eslint . --ext .ts,.tsx",
26
+ "lint:fix": "eslint . --ext .ts,.tsx --fix",
27
+ "format": "prettier --write .",
28
+ "format:check": "prettier --check .",
29
+ "stage-publish": "npm version"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.19.1",
33
+ "zod": "^3.24.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.10.6",
37
+ "tsx": "^4.19.4",
38
+ "typescript": "^5.7.3"
39
+ },
40
+ "keywords": [],
41
+ "author": "PulseMCP",
42
+ "license": "MIT"
43
+ }
@@ -0,0 +1,7 @@
1
+ export { createMCPServer, ProctorClient } from './server.js';
2
+ export type { IProctorClient, ClientFactory } from './server.js';
3
+ export { createRegisterTools, parseEnabledToolGroups } from './tools.js';
4
+ export type { ToolGroup } from './tools.js';
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';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ // Main exports for the Proctor MCP server shared module
2
+ export { createMCPServer, ProctorClient } from './server.js';
3
+ export { createRegisterTools, parseEnabledToolGroups } from './tools.js';
4
+ export { logServerStart, logError, logWarning, logDebug } from './logging.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Logging utilities for consistent output across MCP servers
3
+ */
4
+ /**
5
+ * Log server startup message
6
+ */
7
+ export declare function logServerStart(serverName: string, transport?: string): void;
8
+ /**
9
+ * Log an error with context
10
+ */
11
+ export declare function logError(context: string, error: unknown): void;
12
+ /**
13
+ * Log a warning
14
+ */
15
+ export declare function logWarning(context: string, message: string): void;
16
+ /**
17
+ * Log debug information (only in development)
18
+ */
19
+ export declare function logDebug(context: string, message: string): void;
20
+ //# sourceMappingURL=logging.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Logging utilities for consistent output across MCP servers
3
+ */
4
+ /**
5
+ * Log server startup message
6
+ */
7
+ export function logServerStart(serverName, transport = 'stdio') {
8
+ console.error(`MCP server ${serverName} running on ${transport}`);
9
+ }
10
+ /**
11
+ * Log an error with context
12
+ */
13
+ export function logError(context, error) {
14
+ const message = error instanceof Error ? error.message : String(error);
15
+ const stack = error instanceof Error ? error.stack : undefined;
16
+ console.error(`[ERROR] ${context}: ${message}`);
17
+ if (stack) {
18
+ console.error(stack);
19
+ }
20
+ }
21
+ /**
22
+ * Log a warning
23
+ */
24
+ export function logWarning(context, message) {
25
+ console.error(`[WARN] ${context}: ${message}`);
26
+ }
27
+ /**
28
+ * Log debug information (only in development)
29
+ */
30
+ export function logDebug(context, message) {
31
+ if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
32
+ console.error(`[DEBUG] ${context}: ${message}`);
33
+ }
34
+ }
@@ -0,0 +1,6 @@
1
+ import type { CancelExamParams, CancelExamResponse } from '../../types.js';
2
+ /**
3
+ * Cancel a running exam
4
+ */
5
+ export declare function cancelExam(apiKey: string, baseUrl: string, params: CancelExamParams): Promise<CancelExamResponse>;
6
+ //# sourceMappingURL=cancel-exam.d.ts.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cancel a running exam
3
+ */
4
+ export async function cancelExam(apiKey, baseUrl, params) {
5
+ const url = new URL('/api/proctor/cancel_exam', baseUrl);
6
+ const response = await fetch(url.toString(), {
7
+ method: 'POST',
8
+ headers: {
9
+ 'X-API-Key': apiKey,
10
+ 'Content-Type': 'application/json',
11
+ Accept: 'application/json',
12
+ },
13
+ body: JSON.stringify({
14
+ machine_id: params.machine_id,
15
+ exam_id: params.exam_id,
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 || 'Invalid parameters'}`);
28
+ }
29
+ if (response.status === 422) {
30
+ const errorData = (await response.json());
31
+ throw new Error(`Service error: ${errorData.error || 'Unknown error'}`);
32
+ }
33
+ throw new Error(`Failed to cancel exam: ${response.status} ${response.statusText}`);
34
+ }
35
+ return (await response.json());
36
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Delete a Fly.io machine
3
+ */
4
+ export declare function destroyMachine(apiKey: string, baseUrl: string, machineId: string): Promise<{
5
+ success: boolean;
6
+ }>;
7
+ //# sourceMappingURL=destroy-machine.d.ts.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Delete a Fly.io machine
3
+ */
4
+ export async function destroyMachine(apiKey, baseUrl, machineId) {
5
+ const url = new URL(`/api/proctor/machines/${encodeURIComponent(machineId)}`, baseUrl);
6
+ const response = await fetch(url.toString(), {
7
+ method: 'DELETE',
8
+ headers: {
9
+ 'X-API-Key': apiKey,
10
+ Accept: 'application/json',
11
+ },
12
+ });
13
+ if (!response.ok) {
14
+ if (response.status === 401) {
15
+ throw new Error('Invalid API key');
16
+ }
17
+ if (response.status === 403) {
18
+ throw new Error('User lacks admin privileges or insufficient permissions');
19
+ }
20
+ if (response.status === 400) {
21
+ const errorData = (await response.json());
22
+ throw new Error(`Bad request: ${errorData.error || 'Invalid machine ID format'}`);
23
+ }
24
+ if (response.status === 422) {
25
+ const errorData = (await response.json());
26
+ throw new Error(`Service error: ${errorData.error || 'Unknown error'}`);
27
+ }
28
+ throw new Error(`Failed to destroy machine: ${response.status} ${response.statusText}`);
29
+ }
30
+ return (await response.json());
31
+ }
@@ -0,0 +1,6 @@
1
+ import type { MachinesResponse } from '../../types.js';
2
+ /**
3
+ * List active Fly.io machines for proctor exams
4
+ */
5
+ export declare function getMachines(apiKey: string, baseUrl: string): Promise<MachinesResponse>;
6
+ //# sourceMappingURL=get-machines.d.ts.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * List active Fly.io machines for proctor exams
3
+ */
4
+ export async function getMachines(apiKey, baseUrl) {
5
+ const url = new URL('/api/proctor/machines', baseUrl);
6
+ const response = await fetch(url.toString(), {
7
+ method: 'GET',
8
+ headers: {
9
+ 'X-API-Key': apiKey,
10
+ Accept: 'application/json',
11
+ },
12
+ });
13
+ if (!response.ok) {
14
+ if (response.status === 401) {
15
+ throw new Error('Invalid API key');
16
+ }
17
+ if (response.status === 403) {
18
+ throw new Error('User lacks admin privileges or insufficient permissions');
19
+ }
20
+ if (response.status === 422) {
21
+ const errorData = (await response.json());
22
+ throw new Error(`Service error: ${errorData.error || 'Unknown error'}`);
23
+ }
24
+ throw new Error(`Failed to get machines: ${response.status} ${response.statusText}`);
25
+ }
26
+ return (await response.json());
27
+ }