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.
- package/README.md +247 -0
- package/build/index.integration-with-mock.js +143 -0
- package/build/index.js +57 -0
- package/package.json +43 -0
- package/shared/index.d.ts +7 -0
- package/shared/index.js +4 -0
- package/shared/logging.d.ts +20 -0
- package/shared/logging.js +34 -0
- package/shared/proctor-client/lib/cancel-exam.d.ts +6 -0
- package/shared/proctor-client/lib/cancel-exam.js +36 -0
- package/shared/proctor-client/lib/destroy-machine.d.ts +7 -0
- package/shared/proctor-client/lib/destroy-machine.js +31 -0
- package/shared/proctor-client/lib/get-machines.d.ts +6 -0
- package/shared/proctor-client/lib/get-machines.js +27 -0
- package/shared/proctor-client/lib/get-metadata.d.ts +6 -0
- package/shared/proctor-client/lib/get-metadata.js +23 -0
- package/shared/proctor-client/lib/get-prior-result.d.ts +6 -0
- package/shared/proctor-client/lib/get-prior-result.js +35 -0
- package/shared/proctor-client/lib/run-exam.d.ts +7 -0
- package/shared/proctor-client/lib/run-exam.js +90 -0
- package/shared/proctor-client/lib/save-result.d.ts +6 -0
- package/shared/proctor-client/lib/save-result.js +42 -0
- package/shared/server.d.ts +66 -0
- package/shared/server.js +65 -0
- package/shared/tools/cancel-exam.d.ts +34 -0
- package/shared/tools/cancel-exam.js +99 -0
- package/shared/tools/destroy-machine.d.ts +30 -0
- package/shared/tools/destroy-machine.js +75 -0
- package/shared/tools/get-machines.d.ts +25 -0
- package/shared/tools/get-machines.js +83 -0
- package/shared/tools/get-metadata.d.ts +25 -0
- package/shared/tools/get-metadata.js +63 -0
- package/shared/tools/get-prior-result.d.ts +38 -0
- package/shared/tools/get-prior-result.js +106 -0
- package/shared/tools/run-exam.d.ts +58 -0
- package/shared/tools/run-exam.js +189 -0
- package/shared/tools/save-result.d.ts +52 -0
- package/shared/tools/save-result.js +122 -0
- package/shared/tools.d.ts +44 -0
- package/shared/tools.js +128 -0
- package/shared/types.d.ts +151 -0
- 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
|
package/shared/index.js
ADDED
|
@@ -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,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,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
|
+
}
|