feat: add SHD widget suite
Validate SHD MCP plugin / validate (push) Has been cancelled

This commit is contained in:
2026-08-24 21:46:52 +03:00
parent 2f82ab5f6b
commit cc63037e1f
150 changed files with 7225 additions and 61 deletions
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env node
import process from 'node:process';
const runtimeUrl = process.env.SHD_MCP_RUNTIME_URL || process.env.MCP_RUNTIME_URL || '';
const accessToken = process.env.SHD_MCP_ACCESS_TOKEN || process.env.MCP_ACCESS_TOKEN || '';
const requireAuth = process.argv.includes('--require-auth');
const jsonOutput = process.argv.includes('--json');
if (!runtimeUrl) {
fail('Set SHD_MCP_RUNTIME_URL to the approved MCP endpoint; no endpoint is assumed.');
process.exit(1);
}
const mcpUrl = new URL(runtimeUrl);
const origin = mcpUrl.origin;
const checks = [];
function fail(message) {
console.error(`MCP runtime smoke: ERROR: ${message}`);
process.exitCode = 1;
}
function addCheck(name, ok, detail = '') {
checks.push({ name, ok, detail });
if (!ok) throw new Error(`${name}${detail ? `: ${detail}` : ''}`);
}
function safeErrorBody(value) {
if (!value) return '';
return String(value).replace(/Bearer\s+[^\s]+/gi, 'Bearer [redacted]').replace(/(access_token|refresh_token|code|client_secret|cookie)=?[^&\s]*/gi, '$1=[redacted]').slice(0, 240);
}
async function request(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
Accept: 'application/json',
...(options.body ? { 'Content-Type': 'application/json' } : {}),
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
...(options.headers || {}),
},
});
const text = await response.text();
let body = null;
try { body = text ? JSON.parse(text) : null; } catch { body = null; }
return { response, body, text };
}
async function expectJson(url, name, expectedStatus = 200) {
const result = await request(url);
addCheck(`${name} HTTP`, result.response.status === expectedStatus, `received ${result.response.status}`);
addCheck(`${name} JSON`, result.body && typeof result.body === 'object', safeErrorBody(result.text));
return result.body;
}
async function rpc(method, params, id, sessionId) {
const headers = { Accept: 'application/json, text/event-stream' };
if (sessionId) headers['MCP-Session-Id'] = sessionId;
const result = await request(mcpUrl, {
method: 'POST',
headers,
body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
});
addCheck(`${method} HTTP`, result.response.ok, `received ${result.response.status}`);
addCheck(`${method} JSON`, result.body && typeof result.body === 'object', safeErrorBody(result.text));
if (result.body?.error) throw new Error(`${method}: MCP returned an error`);
return { result: result.body?.result || {}, sessionId: result.response.headers.get('MCP-Session-Id') || sessionId };
}
async function notifyRpc(method, params, sessionId) {
const headers = { Accept: 'application/json, text/event-stream' };
if (sessionId) headers['MCP-Session-Id'] = sessionId;
const result = await request(mcpUrl, {
method: 'POST',
headers,
body: JSON.stringify({ jsonrpc: '2.0', method, params }),
});
addCheck(`${method} HTTP`, result.response.ok, `received ${result.response.status}`);
}
async function run() {
const authorization = await expectJson(`${origin}/.well-known/oauth-authorization-server`, 'OAuth authorization metadata');
addCheck('OAuth issuer', typeof authorization.issuer === 'string' && authorization.issuer.length > 0);
addCheck('OAuth authorization endpoint', typeof authorization.authorization_endpoint === 'string');
addCheck('OAuth token endpoint', typeof authorization.token_endpoint === 'string');
addCheck('OAuth PKCE S256', Array.isArray(authorization.code_challenge_methods_supported) && authorization.code_challenge_methods_supported.includes('S256'));
const protectedResource = await expectJson(`${origin}/.well-known/oauth-protected-resource/mcp`, 'OAuth protected-resource metadata');
addCheck('Protected resource', typeof protectedResource.resource === 'string' && protectedResource.resource.startsWith(origin));
addCheck('Bearer header support', Array.isArray(protectedResource.bearer_methods_supported) && protectedResource.bearer_methods_supported.includes('header'));
if (!accessToken) {
addCheck('Authenticated MCP checks', !requireAuth, 'SHD_MCP_ACCESS_TOKEN is not set');
return;
}
let sessionId;
const initialized = await rpc('initialize', {
protocolVersion: '2025-11-25',
capabilities: {},
clientInfo: { name: 'shd-mcp-plugin-runtime-smoke', version: '1.0.0' },
}, 1);
sessionId = initialized.sessionId;
addCheck('MCP protocol', initialized.result.protocolVersion === '2025-11-25');
await notifyRpc('notifications/initialized', {}, sessionId);
const tools = (await rpc('tools/list', {}, 2, sessionId)).result.tools || [];
const toolNames = new Set(tools.map((tool) => tool.name));
for (const name of ['shd_list_projects', 'shd_render_projects_widget', 'shd_render_documents_widget', 'shd_render_tasks_widget', 'shd_render_finance_widget', 'shd_render_crm_widget', 'shd_render_discussions_widget', 'shd_render_notifications_widget', 'shd_render_scheduling_widget', 'shd_render_inventory_widget', 'shd_render_agents_widget', 'shd_render_status_page_widget', 'shd_render_project_db_widget', 'shd_render_notes_widget', 'shd_render_project_overview_widget', 'shd_render_files_widget', 'shd_render_proposals_widget', 'shd_render_terms_widget', 'shd_render_activity_widget', 'shd_render_organizations_widget', 'shd_render_gitea_widget', 'shd_render_schemes_widget', 'shd_render_task_kanban_widget', 'shd_render_team_workload_widget', 'shd_render_project_timeline_widget', 'shd_render_crm_funnel_widget', 'shd_render_finance_dashboard_widget', 'shd_render_proposal_approvals_widget', 'shd_render_documents_completeness_widget', 'shd_render_agents_health_widget', 'shd_render_schedule_calendar_widget', 'shd_render_acl_matrix_widget', 'shd_render_inventory_warnings_widget', 'shd_render_gitea_board_widget', 'shd_render_schemes_progress_widget', 'shd_render_notes_tree_widget', 'shd_render_project_db_preview_widget']) {
addCheck(`Tool ${name}`, toolNames.has(name));
}
for (const [name, items] of [
['shd_render_projects_widget', { projects: [{ id: 1, code: 'runtime-smoke', display_name: 'Runtime smoke', status: 'check', deadline: null }] }],
['shd_render_documents_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'check', date: null, value: null }] }],
['shd_render_tasks_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'check', date: null, value: null }] }],
['shd_render_finance_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'check', date: null, value: null }] }],
['shd_render_crm_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'check', date: null, value: null }] }],
['shd_render_discussions_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'open', date: null, value: null }] }],
['shd_render_notifications_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'unread', date: null, value: null }] }],
['shd_render_scheduling_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'scheduled', date: null, value: null }] }],
['shd_render_inventory_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'available', date: null, value: null }] }],
['shd_render_agents_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'online', date: null, value: null }] }],
['shd_render_status_page_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'up', date: null, value: '100%' }] }],
['shd_render_project_db_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'ready', date: null, value: null }] }],
['shd_render_notes_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'published', date: null, value: null }] }],
['shd_render_project_overview_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'ready', date: null, value: null }] }],
['shd_render_files_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'available', date: null, value: null }] }],
['shd_render_proposals_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'draft', date: null, value: null }] }],
['shd_render_terms_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'ready', date: null, value: null }] }],
['shd_render_activity_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'new', date: null, value: null }] }],
['shd_render_organizations_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'active', date: null, value: null }] }],
['shd_render_gitea_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'open', date: null, value: null }] }],
['shd_render_schemes_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'ready', date: null, value: null }] }],
['shd_render_task_kanban_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'todo', date: null, value: null }] }],
['shd_render_team_workload_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'normal', date: null, value: '8' }] }],
['shd_render_project_timeline_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'planned', date: null, value: null }] }],
['shd_render_crm_funnel_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'qualified', date: null, value: null }] }],
['shd_render_finance_dashboard_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'draft', date: null, value: '100' }] }],
['shd_render_proposal_approvals_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'pending', date: null, value: null }] }],
['shd_render_documents_completeness_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'complete', date: null, value: '100' }] }],
['shd_render_agents_health_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'online', date: null, value: null }] }],
['shd_render_schedule_calendar_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'scheduled', date: null, value: null }] }],
['shd_render_acl_matrix_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'member', date: null, value: null }] }],
['shd_render_inventory_warnings_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'low_stock', date: null, value: '1' }] }],
['shd_render_gitea_board_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'open', date: null, value: null }] }],
['shd_render_schemes_progress_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'in_progress', date: null, value: '50' }] }],
['shd_render_notes_tree_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'active', date: null, value: null }] }],
['shd_render_project_db_preview_widget', { items: [{ id: 'runtime-smoke', title: 'Runtime smoke', status: 'ready', date: null, value: null }] }],
]) {
const render = await rpc('tools/call', { name, arguments: items }, 20 + checks.length, sessionId);
const output = render.result.structuredContent;
addCheck(`${name} output`, output?.ok === true && Array.isArray(output.data));
}
const resources = (await rpc('resources/list', {}, 3, sessionId)).result.resources || [];
for (const uri of ['ui://shd/active-projects/v1.html', 'ui://shd/documents/v1.html', 'ui://shd/tasks/v1.html', 'ui://shd/finance/v1.html', 'ui://shd/crm/v1.html', 'ui://shd/discussions/v1.html', 'ui://shd/notifications/v1.html', 'ui://shd/scheduling/v1.html', 'ui://shd/inventory/v1.html', 'ui://shd/agents/v1.html', 'ui://shd/status-page/v1.html', 'ui://shd/project-db/v1.html', 'ui://shd/notes/v1.html', 'ui://shd/project-overview/v1.html', 'ui://shd/files/v1.html', 'ui://shd/proposals/v1.html', 'ui://shd/terms/v1.html', 'ui://shd/activity/v1.html', 'ui://shd/organizations/v1.html', 'ui://shd/gitea/v1.html', 'ui://shd/schemes/v1.html', 'ui://shd/task-kanban/v1.html', 'ui://shd/team-workload/v1.html', 'ui://shd/project-timeline/v1.html', 'ui://shd/crm-funnel/v1.html', 'ui://shd/finance-dashboard/v1.html', 'ui://shd/proposal-approvals/v1.html', 'ui://shd/documents-completeness/v1.html', 'ui://shd/agents-health/v1.html', 'ui://shd/schedule-calendar/v1.html', 'ui://shd/acl-matrix/v1.html', 'ui://shd/inventory-warnings/v1.html', 'ui://shd/gitea-board/v1.html', 'ui://shd/schemes-progress/v1.html', 'ui://shd/notes-tree/v1.html', 'ui://shd/project-db-preview/v1.html']) {
addCheck(`Resource ${uri}`, resources.some((resource) => resource.uri === uri));
const resourceIndex = resources.findIndex((resource) => resource.uri === uri);
if (resourceIndex < 0) continue;
const content = (await rpc('resources/read', { uri }, 10 + resourceIndex, sessionId)).result.contents?.[0];
addCheck(`${uri} MIME`, content?.mimeType === 'text/html;profile=mcp-app');
addCheck(`${uri} bridge`, ['ui/initialize', 'ui/notifications/initialized', 'tools/call'].every((marker) => content?.text?.includes(marker)));
}
addCheck('Authenticated MCP resource checks', true);
}
try {
await run();
const result = { status: 'ok', authenticated: Boolean(accessToken), checks };
if (jsonOutput) console.log(JSON.stringify(result, null, 2));
else {
console.log(`MCP runtime smoke: ok (${checks.length} checks; authenticated=${Boolean(accessToken)})`);
for (const check of checks) console.log(`- ${check.name}`);
}
} catch (error) {
const message = error instanceof Error ? error.message : 'unknown error';
if (jsonOutput) console.log(JSON.stringify({ status: 'error', authenticated: Boolean(accessToken), checks, error: message }, null, 2));
else fail(message);
process.exitCode = 1;
}