refactor(mcp): address review — split files, fix naming, stateless router

Split the monolithic mcp.router.ts into focused modules:
- mcp.service.ts: coordination helpers (rundownListResponse, renameRundown, deleteRundown)
- mcp.tools.ts: tool definitions (ontime_ prefix, annotation presets, shared field schemas,
  CHARACTER_LIMIT truncation) + handleToolCall dispatcher
- mcp.prompts.ts: prompt definitions + handleGetPrompt
- mcp.resources.ts: resource definitions (schema, live data, docs) + handleReadResource
- mcp.server.ts: createMcpServer factory that wires the above modules
- mcp.router.ts: thin stateless Express router (POST/GET405/DELETE405)

Other changes per review comments:
- Rename ontime_delete_event → ontime_delete_entry (entry vs event naming convention)
- Remove hardcoded cue/colour prefix conventions from tool descriptions and prompts;
  replace with "ask the user what convention they prefer"
- Remove automations tools (left for a later PR)
- Add Bearer auth comment in authenticate.ts explaining the MCP use case
- Extract McpSection component from FeaturePanel to isolate URL state re-renders
- Update bulk_edit prompt to mention linkStart cascade behaviour
- Add 405 comments explaining why GET/DELETE are not supported in stateless mode

https://claude.ai/code/session_01U24MeuUacYXeQhbX3tatEe
This commit is contained in:
Claude
2026-05-05 19:16:25 +00:00
committed by Carlos Valente
parent b850a51204
commit 71a7f26493
9 changed files with 1191 additions and 1217 deletions
@@ -1,14 +1,10 @@
import { useEffect, useState } from 'react';
import useScrollIntoView from '../../../../common/hooks/useScrollIntoView';
import { generateUrl } from '../../../../common/api/session';
import CopyTag from '../../../../common/components/copy-tag/CopyTag';
import useInfo from '../../../../common/hooks-query/useInfo';
import { isOntimeCloud, serverURL } from '../../../../externals';
import { isOntimeCloud } from '../../../../externals';
import GenerateLinkFormExport from '../../../sharing/GenerateLinkFormExport';
import type { PanelBaseProps } from '../../panel-list/PanelList';
import * as Panel from '../../panel-utils/PanelUtils';
import InfoNif from '../network-panel/NetworkInterfaces';
import McpSection from './McpSection';
import ReportSettings from './ReportSettings';
import URLPresets from './URLPresets';
@@ -18,29 +14,6 @@ export default function FeaturePanel({ location }: PanelBaseProps) {
const reportRef = useScrollIntoView<HTMLDivElement>('report', location);
const mcpRef = useScrollIntoView<HTMLDivElement>('mcp', location);
const { data: infoData } = useInfo();
const [mcpEndpointUrl, setMcpEndpointUrl] = useState('');
useEffect(() => {
const baseUrl = isOntimeCloud
? serverURL
: infoData.networkInterfaces.length > 0
? `http://${infoData.networkInterfaces[0].address}:${infoData.serverPort}`
: serverURL;
generateUrl({ baseUrl, path: '/mcp', authenticate: true, lockConfig: false, lockNav: false })
.then(setMcpEndpointUrl)
.catch(() => {
setMcpEndpointUrl(`${baseUrl}/mcp`);
});
}, [infoData]);
const claudeDesktopConfig = JSON.stringify(
{ mcpServers: { ontime: { url: mcpEndpointUrl } } },
null,
2,
);
return (
<>
<Panel.Header>Sharing and reporting</Panel.Header>
@@ -63,30 +36,7 @@ export default function FeaturePanel({ location }: PanelBaseProps) {
</Panel.Section>
</div>
<div ref={mcpRef}>
<Panel.Section>
<Panel.Card>
<Panel.SubHeader>MCP Server (AI Agent Integration)</Panel.SubHeader>
<Panel.Paragraph>
Connect AI agents (e.g. Claude Desktop) to Ontime via the Model Context Protocol endpoint.
</Panel.Paragraph>
<Panel.Divider />
<Panel.Field
title='MCP Endpoint URL'
description='Use this URL in your MCP client configuration'
/>
{mcpEndpointUrl && (
<CopyTag copyValue={mcpEndpointUrl}>{mcpEndpointUrl}</CopyTag>
)}
<Panel.Divider />
<Panel.Field
title='Claude Desktop config'
description='Paste this into your claude_desktop_config.json under "mcpServers"'
/>
{mcpEndpointUrl && (
<CopyTag copyValue={claudeDesktopConfig}>{claudeDesktopConfig}</CopyTag>
)}
</Panel.Card>
</Panel.Section>
<McpSection />
</div>
<div ref={reportRef}>
<ReportSettings />
@@ -0,0 +1,47 @@
import { useEffect, useState } from 'react';
import { generateUrl } from '../../../../common/api/session';
import CopyTag from '../../../../common/components/copy-tag/CopyTag';
import useInfo from '../../../../common/hooks-query/useInfo';
import { isOntimeCloud, serverURL } from '../../../../externals';
import * as Panel from '../../panel-utils/PanelUtils';
/** MCP endpoint card — isolated so that URL state changes don't re-render the rest of FeaturePanel */
export default function McpSection() {
const { data: infoData } = useInfo();
const [mcpEndpointUrl, setMcpEndpointUrl] = useState('');
useEffect(() => {
const baseUrl = isOntimeCloud
? serverURL
: infoData.networkInterfaces.length > 0
? `http://${infoData.networkInterfaces[0].address}:${infoData.serverPort}`
: serverURL;
generateUrl({ baseUrl, path: '/mcp', authenticate: true, lockConfig: false, lockNav: false })
.then(setMcpEndpointUrl)
.catch(() => {
setMcpEndpointUrl(`${baseUrl}/mcp`);
});
}, [infoData]);
const mcpClientConfig = JSON.stringify({ mcpServers: { ontime: { url: mcpEndpointUrl } } }, null, 2);
return (
<Panel.Section>
<Panel.Card>
<Panel.SubHeader>MCP Server</Panel.SubHeader>
<Panel.Paragraph>Connect any MCP-compatible AI agent to Ontime using the endpoint below.</Panel.Paragraph>
<Panel.Divider />
<Panel.Field title='Endpoint URL' description='Add this URL to your MCP client settings' />
{mcpEndpointUrl && <CopyTag copyValue={mcpEndpointUrl}>{mcpEndpointUrl}</CopyTag>}
<Panel.Divider />
<Panel.Field
title='Client configuration snippet'
description='Paste this into your AI agent settings under "mcpServers"'
/>
{mcpEndpointUrl && <CopyTag copyValue={mcpClientConfig}>{mcpClientConfig}</CopyTag>}
</Panel.Card>
</Panel.Section>
);
}