pulsemcp-cms-admin-mcp-server 0.7.4 → 0.9.1
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 +2 -2
- package/build/shared/src/pulsemcp-admin-client/lib/get-proctor-runs.js +68 -0
- package/build/shared/src/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +6 -0
- package/build/shared/src/server.js +4 -0
- package/build/shared/src/tools/get-mcp-server.js +2 -2
- package/build/shared/src/tools/list-proctor-runs.js +171 -0
- package/build/shared/src/tools/save-mcp-implementation.js +3 -3
- package/build/shared/src/tools/save-results-for-mirror.js +60 -105
- package/build/shared/src/tools/update-mcp-server.js +4 -4
- package/build/shared/src/tools.js +3 -1
- package/package.json +1 -1
- package/shared/pulsemcp-admin-client/lib/get-proctor-runs.d.ts +11 -0
- package/shared/pulsemcp-admin-client/lib/get-proctor-runs.js +68 -0
- package/shared/pulsemcp-admin-client/pulsemcp-admin-client.integration-mock.js +6 -0
- package/shared/server.d.ts +3 -1
- package/shared/server.js +4 -0
- package/shared/tools/get-mcp-server.js +2 -2
- package/shared/tools/list-proctor-runs.d.ts +58 -0
- package/shared/tools/list-proctor-runs.js +171 -0
- package/shared/tools/save-mcp-implementation.d.ts +1 -1
- package/shared/tools/save-mcp-implementation.js +3 -3
- package/shared/tools/save-results-for-mirror.d.ts +1 -24
- package/shared/tools/save-results-for-mirror.js +60 -105
- package/shared/tools/update-mcp-server.js +4 -4
- package/shared/tools.d.ts +2 -2
- package/shared/tools.js +3 -1
- package/shared/types.d.ts +38 -1
|
@@ -3,38 +3,19 @@ import { examResultStore, extractExamId, extractStatus } from '../exam-result-st
|
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
4
|
mirror_id: 'The ID of the unofficial mirror to save results for',
|
|
5
5
|
runtime_id: 'The runtime ID that was used to run the exams',
|
|
6
|
-
result_id: 'The UUID returned by run_exam_for_mirror.
|
|
7
|
-
results: 'Array of exam results to save. Each result must include exam_id, status, and optional data. Only needed if result_id is not provided.',
|
|
8
|
-
exam_id: 'The exam identifier (e.g., "auth-check", "init-tools-list")',
|
|
9
|
-
status: 'The result status (e.g., "pass", "fail", "error", "skip")',
|
|
10
|
-
data: 'Optional detailed result data. Sensitive fields (tokens, secrets, passwords) will be automatically redacted before storage.',
|
|
6
|
+
result_id: 'The UUID returned by run_exam_for_mirror. The server retrieves the full result from the local file store automatically.',
|
|
11
7
|
};
|
|
12
|
-
const
|
|
13
|
-
exam_id: z.string().describe(PARAM_DESCRIPTIONS.exam_id),
|
|
14
|
-
status: z.string().describe(PARAM_DESCRIPTIONS.status),
|
|
15
|
-
data: z.record(z.unknown()).optional().describe(PARAM_DESCRIPTIONS.data),
|
|
16
|
-
});
|
|
17
|
-
const SaveResultsForMirrorSchema = z
|
|
18
|
-
.object({
|
|
8
|
+
const SaveResultsForMirrorSchema = z.object({
|
|
19
9
|
mirror_id: z.number().describe(PARAM_DESCRIPTIONS.mirror_id),
|
|
20
10
|
runtime_id: z.string().optional().describe(PARAM_DESCRIPTIONS.runtime_id),
|
|
21
|
-
result_id: z.string().uuid().
|
|
22
|
-
results: z.array(ResultSchema).optional().describe(PARAM_DESCRIPTIONS.results),
|
|
23
|
-
})
|
|
24
|
-
.refine((data) => data.result_id || (data.results && data.results.length > 0), {
|
|
25
|
-
message: 'Either result_id or a non-empty results array must be provided',
|
|
26
|
-
})
|
|
27
|
-
.refine((data) => !(data.result_id && data.results && data.results.length > 0), {
|
|
28
|
-
message: 'Provide either result_id or results, not both. Use result_id (preferred) to retrieve from the store, or results for direct submission.',
|
|
11
|
+
result_id: z.string().uuid().describe(PARAM_DESCRIPTIONS.result_id),
|
|
29
12
|
});
|
|
30
13
|
export function saveResultsForMirror(_server, clientFactory) {
|
|
31
14
|
return {
|
|
32
15
|
name: 'save_results_for_mirror',
|
|
33
16
|
description: `Save proctor exam results for an unofficial mirror.
|
|
34
17
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
**Fallback**: Pass results directly (as before) if result_id is not available.
|
|
18
|
+
Pass the \`result_id\` returned by \`run_exam_for_mirror\`. The full result is retrieved from the local file store server-side — no need to pass the large results payload through the LLM context.
|
|
38
19
|
|
|
39
20
|
Results are sanitized server-side to redact sensitive data (OAuth tokens, client secrets, passwords, etc.) before being persisted.
|
|
40
21
|
|
|
@@ -54,109 +35,85 @@ Typical workflow:
|
|
|
54
35
|
format: 'uuid',
|
|
55
36
|
description: PARAM_DESCRIPTIONS.result_id,
|
|
56
37
|
},
|
|
57
|
-
results: {
|
|
58
|
-
type: 'array',
|
|
59
|
-
items: {
|
|
60
|
-
type: 'object',
|
|
61
|
-
properties: {
|
|
62
|
-
exam_id: { type: 'string', description: PARAM_DESCRIPTIONS.exam_id },
|
|
63
|
-
status: { type: 'string', description: PARAM_DESCRIPTIONS.status },
|
|
64
|
-
data: {
|
|
65
|
-
type: 'object',
|
|
66
|
-
additionalProperties: true,
|
|
67
|
-
description: PARAM_DESCRIPTIONS.data,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
required: ['exam_id', 'status'],
|
|
71
|
-
},
|
|
72
|
-
description: PARAM_DESCRIPTIONS.results,
|
|
73
|
-
},
|
|
74
38
|
},
|
|
75
|
-
required: ['mirror_id'],
|
|
39
|
+
required: ['mirror_id', 'result_id'],
|
|
76
40
|
},
|
|
77
41
|
handler: async (args) => {
|
|
78
42
|
const validatedArgs = SaveResultsForMirrorSchema.parse(args);
|
|
79
43
|
const client = clientFactory();
|
|
80
44
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
45
|
+
const stored = examResultStore.get(validatedArgs.result_id);
|
|
46
|
+
if (!stored) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: `No stored result found for result_id "${validatedArgs.result_id}". The result file may have been cleaned up or the /tmp directory cleared. Re-run run_exam_for_mirror to generate a new result.`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Extract exam_result lines from stored data.
|
|
58
|
+
//
|
|
59
|
+
// The real proctor API returns a deeply nested structure:
|
|
60
|
+
// line.data = {
|
|
61
|
+
// mirror_id, server_slug, exam_id, ...,
|
|
62
|
+
// result: { ← envelope
|
|
63
|
+
// exam_id, machine_id, status,
|
|
64
|
+
// result: { ← actual payload
|
|
65
|
+
// input: {...}, output: {...}, processedBy: {...}
|
|
66
|
+
// },
|
|
67
|
+
// error, logs
|
|
68
|
+
// }
|
|
69
|
+
// }
|
|
70
|
+
//
|
|
71
|
+
// The PulseMCP API expects the actual payload { input, output,
|
|
72
|
+
// processedBy } at the top level of the saved results column.
|
|
73
|
+
// We must unwrap through data.result.result to reach it.
|
|
74
|
+
const results = stored.lines
|
|
75
|
+
.filter((line) => line.type === 'exam_result')
|
|
76
|
+
.map((line) => {
|
|
77
|
+
const data = line.data;
|
|
78
|
+
// Unwrap nested result objects to find the exam payload
|
|
79
|
+
// containing { input, output, processedBy }.
|
|
80
|
+
let resultData = data;
|
|
81
|
+
// Level 1: data.result (envelope with exam_id, machine_id, logs, etc.)
|
|
82
|
+
if (resultData?.result &&
|
|
83
|
+
typeof resultData.result === 'object' &&
|
|
84
|
+
!Array.isArray(resultData.result)) {
|
|
85
|
+
resultData = resultData.result;
|
|
86
|
+
// Level 2: data.result.result (actual payload with input, output, processedBy)
|
|
87
|
+
if (resultData.result &&
|
|
123
88
|
typeof resultData.result === 'object' &&
|
|
124
89
|
!Array.isArray(resultData.result)) {
|
|
125
90
|
resultData = resultData.result;
|
|
126
|
-
// Level 2: data.result.result (actual payload with input, output, processedBy)
|
|
127
|
-
if (resultData.result &&
|
|
128
|
-
typeof resultData.result === 'object' &&
|
|
129
|
-
!Array.isArray(resultData.result)) {
|
|
130
|
-
resultData = resultData.result;
|
|
131
|
-
}
|
|
132
91
|
}
|
|
133
|
-
return {
|
|
134
|
-
exam_id: extractExamId(line),
|
|
135
|
-
status: extractStatus(line),
|
|
136
|
-
...(resultData ? { data: resultData } : {}),
|
|
137
|
-
};
|
|
138
|
-
});
|
|
139
|
-
if (!runtimeId) {
|
|
140
|
-
runtimeId = stored.runtime_id;
|
|
141
92
|
}
|
|
142
|
-
|
|
143
|
-
|
|
93
|
+
return {
|
|
94
|
+
exam_id: extractExamId(line),
|
|
95
|
+
status: extractStatus(line),
|
|
96
|
+
...(resultData ? { data: resultData } : {}),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
if (results.length === 0) {
|
|
144
100
|
return {
|
|
145
101
|
content: [
|
|
146
102
|
{
|
|
147
103
|
type: 'text',
|
|
148
|
-
text: 'No exam results
|
|
104
|
+
text: 'No exam results found in the stored result. The stored data may not contain any exam_result lines.',
|
|
149
105
|
},
|
|
150
106
|
],
|
|
151
107
|
isError: true,
|
|
152
108
|
};
|
|
153
109
|
}
|
|
110
|
+
const runtimeId = validatedArgs.runtime_id || stored.runtime_id;
|
|
154
111
|
if (!runtimeId) {
|
|
155
112
|
return {
|
|
156
113
|
content: [
|
|
157
114
|
{
|
|
158
115
|
type: 'text',
|
|
159
|
-
text: 'runtime_id is required. Provide it directly or
|
|
116
|
+
text: 'runtime_id is required. Provide it directly or ensure the stored result includes it.',
|
|
160
117
|
},
|
|
161
118
|
],
|
|
162
119
|
isError: true,
|
|
@@ -170,9 +127,7 @@ Typical workflow:
|
|
|
170
127
|
let content = `**Proctor Results Saved**\n\n`;
|
|
171
128
|
content += `Mirror ID: ${validatedArgs.mirror_id}\n`;
|
|
172
129
|
content += `Runtime: ${runtimeId}\n`;
|
|
173
|
-
|
|
174
|
-
content += `Result ID: ${validatedArgs.result_id}\n`;
|
|
175
|
-
}
|
|
130
|
+
content += `Result ID: ${validatedArgs.result_id}\n`;
|
|
176
131
|
content += '\n';
|
|
177
132
|
if (response.saved.length > 0) {
|
|
178
133
|
content += `**Successfully Saved (${response.saved.length}):**\n`;
|
|
@@ -193,7 +148,7 @@ Typical workflow:
|
|
|
193
148
|
}
|
|
194
149
|
}
|
|
195
150
|
// Clean up stored result after successful save (all results persisted)
|
|
196
|
-
if (
|
|
151
|
+
if (response.errors.length === 0) {
|
|
197
152
|
examResultStore.delete(validatedArgs.result_id);
|
|
198
153
|
}
|
|
199
154
|
return { content: [{ type: 'text', text: content.trim() }] };
|
|
@@ -25,8 +25,8 @@ const PARAM_DESCRIPTIONS = {
|
|
|
25
25
|
const CanonicalUrlSchema = z.object({
|
|
26
26
|
url: z.string().describe('The canonical URL'),
|
|
27
27
|
scope: z
|
|
28
|
-
.enum(['domain', 'subdomain', '
|
|
29
|
-
.describe('Scope of the canonical: domain, subdomain,
|
|
28
|
+
.enum(['domain', 'subdomain', 'url'])
|
|
29
|
+
.describe('Scope of the canonical: domain, subdomain, or url (exact match)'),
|
|
30
30
|
note: z.string().optional().describe('Optional note about this canonical URL'),
|
|
31
31
|
});
|
|
32
32
|
const RemoteEndpointSchema = z.object({
|
|
@@ -126,7 +126,7 @@ Providing canonical_urls replaces ALL existing canonical URLs:
|
|
|
126
126
|
{
|
|
127
127
|
"implementation_id": 456,
|
|
128
128
|
"canonical_urls": [
|
|
129
|
-
{ "url": "https://github.com/org/repo", "scope": "
|
|
129
|
+
{ "url": "https://github.com/org/repo", "scope": "domain" },
|
|
130
130
|
{ "url": "https://npmjs.com/package/name", "scope": "url" }
|
|
131
131
|
]
|
|
132
132
|
}
|
|
@@ -237,7 +237,7 @@ Create new provider:
|
|
|
237
237
|
url: { type: 'string', description: 'The canonical URL' },
|
|
238
238
|
scope: {
|
|
239
239
|
type: 'string',
|
|
240
|
-
enum: ['domain', 'subdomain', '
|
|
240
|
+
enum: ['domain', 'subdomain', 'url'],
|
|
241
241
|
description: 'Scope of the canonical',
|
|
242
242
|
},
|
|
243
243
|
note: { type: 'string', description: 'Optional note' },
|
package/shared/tools.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { ClientFactory } from './server.js';
|
|
|
23
23
|
* - mcp_servers / mcp_servers_readonly: Unified MCP server tools (abstracted interface)
|
|
24
24
|
* - redirects / redirects_readonly: URL redirect management tools
|
|
25
25
|
* - good_jobs / good_jobs_readonly: GoodJob background job management tools
|
|
26
|
-
* - proctor / proctor_readonly: Proctor exam execution and result storage tools. The readonly variant includes get_exam_result for retrieving stored results without running exams or saving
|
|
26
|
+
* - proctor / proctor_readonly: Proctor exam execution and result storage tools. The readonly variant includes get_exam_result and list_proctor_runs for retrieving stored results without running exams or saving
|
|
27
27
|
* - discovered_urls / discovered_urls_readonly: Discovered URL management tools for processing URLs into MCP implementations
|
|
28
28
|
* - notifications: Notification email tools (send_impl_posted_notif). Separated from server_directory so notification capability can be granted independently.
|
|
29
29
|
*/
|
|
@@ -69,7 +69,7 @@ export declare function parseEnabledToolGroups(enabledGroupsParam?: string): Too
|
|
|
69
69
|
* - good_jobs: GoodJob background job management tools (read + write)
|
|
70
70
|
* - good_jobs_readonly: GoodJob tools (read only)
|
|
71
71
|
* - proctor: Proctor exam execution and result storage tools (read + write)
|
|
72
|
-
* - proctor_readonly: Proctor tools (read only - get_exam_result
|
|
72
|
+
* - proctor_readonly: Proctor tools (read only - get_exam_result and list_proctor_runs)
|
|
73
73
|
* - discovered_urls: Discovered URL management tools for processing URLs into MCP implementations (read + write)
|
|
74
74
|
* - discovered_urls_readonly: Discovered URL tools (read only - list and stats)
|
|
75
75
|
* - notifications: Notification email tools - send_impl_posted_notif (write-only, no readonly variant)
|
package/shared/tools.js
CHANGED
|
@@ -60,6 +60,7 @@ import { cleanupGoodJobs } from './tools/cleanup-good-jobs.js';
|
|
|
60
60
|
import { runExamForMirror } from './tools/run-exam-for-mirror.js';
|
|
61
61
|
import { getExamResult } from './tools/get-exam-result.js';
|
|
62
62
|
import { saveResultsForMirror } from './tools/save-results-for-mirror.js';
|
|
63
|
+
import { listProctorRuns } from './tools/list-proctor-runs.js';
|
|
63
64
|
// Discovered URLs tools
|
|
64
65
|
import { listDiscoveredUrls } from './tools/list-discovered-urls.js';
|
|
65
66
|
import { markDiscoveredUrlProcessed } from './tools/mark-discovered-url-processed.js';
|
|
@@ -230,6 +231,7 @@ const ALL_TOOLS = [
|
|
|
230
231
|
{ factory: runExamForMirror, groups: ['proctor'], isWriteOperation: true },
|
|
231
232
|
{ factory: getExamResult, groups: ['proctor'], isWriteOperation: false },
|
|
232
233
|
{ factory: saveResultsForMirror, groups: ['proctor'], isWriteOperation: true },
|
|
234
|
+
{ factory: listProctorRuns, groups: ['proctor'], isWriteOperation: false },
|
|
233
235
|
// Discovered URLs tools
|
|
234
236
|
{ factory: listDiscoveredUrls, groups: ['discovered_urls'], isWriteOperation: false },
|
|
235
237
|
{
|
|
@@ -367,7 +369,7 @@ function shouldIncludeTool(toolDef, enabledGroups) {
|
|
|
367
369
|
* - good_jobs: GoodJob background job management tools (read + write)
|
|
368
370
|
* - good_jobs_readonly: GoodJob tools (read only)
|
|
369
371
|
* - proctor: Proctor exam execution and result storage tools (read + write)
|
|
370
|
-
* - proctor_readonly: Proctor tools (read only - get_exam_result
|
|
372
|
+
* - proctor_readonly: Proctor tools (read only - get_exam_result and list_proctor_runs)
|
|
371
373
|
* - discovered_urls: Discovered URL management tools for processing URLs into MCP implementations (read + write)
|
|
372
374
|
* - discovered_urls_readonly: Discovered URL tools (read only - list and stats)
|
|
373
375
|
* - notifications: Notification email tools - send_impl_posted_notif (write-only, no readonly variant)
|
package/shared/types.d.ts
CHANGED
|
@@ -105,7 +105,7 @@ export interface RemoteEndpointParams {
|
|
|
105
105
|
}
|
|
106
106
|
export interface CanonicalUrlParams {
|
|
107
107
|
url: string;
|
|
108
|
-
scope: 'domain' | 'subdomain' | '
|
|
108
|
+
scope: 'domain' | 'subdomain' | 'url';
|
|
109
109
|
note?: string;
|
|
110
110
|
}
|
|
111
111
|
export interface MCPServer {
|
|
@@ -691,6 +691,43 @@ export interface ProctorSaveResultsResponse {
|
|
|
691
691
|
error: string;
|
|
692
692
|
}>;
|
|
693
693
|
}
|
|
694
|
+
export interface ProctorRun {
|
|
695
|
+
id: number;
|
|
696
|
+
slug: string;
|
|
697
|
+
name: string | null;
|
|
698
|
+
recommended: boolean;
|
|
699
|
+
mirrors_count: number;
|
|
700
|
+
tenant_count: number;
|
|
701
|
+
latest_version: string | null;
|
|
702
|
+
latest_mirror_id: number | null;
|
|
703
|
+
latest_mirror_name: string | null;
|
|
704
|
+
latest_tested: boolean;
|
|
705
|
+
last_auth_check_days: number | null;
|
|
706
|
+
last_tools_list_days: number | null;
|
|
707
|
+
auth_types: string[];
|
|
708
|
+
num_tools: number | null;
|
|
709
|
+
packages: string[];
|
|
710
|
+
remotes: string[];
|
|
711
|
+
}
|
|
712
|
+
export interface ProctorRunsResponse {
|
|
713
|
+
runs: ProctorRun[];
|
|
714
|
+
pagination?: {
|
|
715
|
+
current_page: number;
|
|
716
|
+
total_pages: number;
|
|
717
|
+
total_count: number;
|
|
718
|
+
has_next?: boolean;
|
|
719
|
+
limit?: number;
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
export interface GetProctorRunsParams {
|
|
723
|
+
q?: string;
|
|
724
|
+
recommended?: boolean;
|
|
725
|
+
tenant_ids?: string;
|
|
726
|
+
sort?: 'slug' | 'name' | 'mirrors' | 'recommended' | 'tenants' | 'latest_tested' | 'last_auth_check' | 'last_tools_list';
|
|
727
|
+
direction?: 'asc' | 'desc';
|
|
728
|
+
limit?: number;
|
|
729
|
+
offset?: number;
|
|
730
|
+
}
|
|
694
731
|
export type DiscoveredUrlResult = 'posted' | 'skipped' | 'rejected' | 'error';
|
|
695
732
|
export interface DiscoveredUrl {
|
|
696
733
|
id: number;
|