diff --git a/apps/server/src/api-mcp/mcp.prompts.ts b/apps/server/src/api-mcp/mcp.prompts.ts index 5da816e08..d4d0ddb6f 100644 --- a/apps/server/src/api-mcp/mcp.prompts.ts +++ b/apps/server/src/api-mcp/mcp.prompts.ts @@ -46,12 +46,7 @@ export function handleGetPrompt(name: string, args: Record): Get content: { type: 'text', text: `Convert the following agenda into an Ontime rundown. - -Data model: -- Times are milliseconds from midnight. 09:00 = 32400000, 10:30 = 37800000, etc. -- duration = timeEnd - timeStart -- endAction: "load-next" for back-to-back sessions, "none" otherwise -- Ask the user what cue naming and colour conventions they prefer +Read the ontime://schema resource if you need a data model reference. Steps: 1. Call ontime_get_rundown to see current state and identify an \`after\` anchor if appending. diff --git a/apps/server/src/api-mcp/mcp.resources.ts b/apps/server/src/api-mcp/mcp.resources.ts index e57b10c3b..cdc237216 100644 --- a/apps/server/src/api-mcp/mcp.resources.ts +++ b/apps/server/src/api-mcp/mcp.resources.ts @@ -4,118 +4,7 @@ import { normalisedToRundownArray } from '../api-data/rundown/rundown.utils.js'; import { getDataProvider } from '../classes/data-provider/DataProvider.js'; import type { ListResourcesResult, ReadResourceResult } from '@modelcontextprotocol/sdk/types.js'; -// Static Ontime data model reference — agents read this once per session to understand -// the rundown structure, time format, and entry types before issuing tool calls. -const ONTIME_SCHEMA_MARKDOWN = `# Ontime data model - -A concise reference for how Ontime structures rundowns, events, and related data. - -## Rundown - -A rundown is an ordered list of entries rendered as a show schedule. A project can contain multiple rundowns; one is "loaded" at a time. - -\`\`\` -Rundown { - id: string - title: string - order: EntryId[] // top-level entry order - flatOrder: EntryId[] // includes entries nested in groups - entries: { [id: EntryId]: OntimeEntry } - revision: number -} -\`\`\` - -## Entries - -There are three entry types discriminated by \`type\`: - -### \`event\` — OntimeEvent (a timed show item) -\`\`\` -{ - type: 'event' - id: EntryId - cue: string // human-facing cue label - title: string - note: string - colour: string // hex, e.g. "#4A90D9" - timeStart: number // ms from midnight (09:00 = 32400000) - timeEnd: number // ms from midnight - duration: number // ms (= timeEnd - timeStart) - delay: number // accumulated delay in ms - timerType: 'count-down' | 'count-up' | 'time-to-end' | 'clock' - endAction: 'none' | 'stop' | 'load-next' | 'play-next' - linkStart: boolean // chain start to previous event's end - countToEnd: boolean // timer counts to planned end time - skip: boolean // event is skipped during playback - timeWarning: number // ms before end to trigger 'warning' state - timeDanger: number // ms before end to trigger 'danger' state - custom: { [key: string]: string } // custom field values - triggers: AutomationTrigger[] -} -\`\`\` - -### \`delay\` — Delay (schedule shift applied to following events) -\`\`\` -{ type: 'delay', id, duration: number } -\`\`\` - -### \`group\` — Group (nested container of entries) -\`\`\` -{ type: 'group', id, title, colour, note, entries: EntryId[], targetDuration?: number } -\`\`\` - -## Time format -All time fields are **milliseconds from midnight (local)**. Examples: -- 09:00:00 = 32400000 -- 09:30:00 = 34200000 -- 14:15:00 = 51300000 -- duration of 45 min = 2700000 - -## Custom fields -Custom fields are project-scoped name/type/colour definitions stored at \`ontime://project/custom-fields\`. Each event stores values at \`event.custom[fieldKey]\`. - -## Playback states (runtime only) -\`'stop' | 'play' | 'pause' | 'armed' | 'roll'\`. When playback is not \`stop\`, mutating tools warn that changes are visible immediately. - -## Useful resource URIs -- \`ontime://schema\` — this document -- \`ontime://rundown/current\` — the currently loaded rundown (JSON) -- \`ontime://rundowns\` — all rundowns in the project (JSON) -- \`ontime://project/info\` — project metadata (JSON) -- \`ontime://project/custom-fields\` — custom field definitions (JSON) -- \`ontime://docs\` — index of Ontime documentation topics with URLs - -## Further reading -Full Ontime documentation: **https://docs.getontime.no** -Read \`ontime://docs\` for a topic index with direct links. -`; - -// Curated documentation index — agents read this to find official docs on specific topics. -const ONTIME_DOCS_MARKDOWN = `# Ontime Documentation Index - -Main site: https://docs.getontime.no - -## Getting started -- Installation & setup: https://docs.getontime.no/installation/ - -## Core concepts -- Rundown: https://docs.getontime.no/concepts/rundown/ -- Timer types (count-down, count-up, time-to-end, clock): https://docs.getontime.no/concepts/timer/ -- Time entry format: https://docs.getontime.no/concepts/time-entry/ -- Event actions (end action, link start): https://docs.getontime.no/concepts/event-actions/ -- Delays and blocks: https://docs.getontime.no/concepts/delays-and-blocks/ - -## Features -- Custom fields: https://docs.getontime.no/features/custom-fields/ -- Automations (triggers, filters, outputs): https://docs.getontime.no/features/automations/ -- URL presets / shared views: https://docs.getontime.no/features/url-presets/ -- HTTP Integration: https://docs.getontime.no/api/http/ -- OSC Integration: https://docs.getontime.no/api/osc/ - -## API reference -- REST API overview: https://docs.getontime.no/api/ -- WebSocket events: https://docs.getontime.no/api/websocket/ -`; +import { ONTIME_DOCS_MARKDOWN, ONTIME_SCHEMA_MARKDOWN } from './mcp.schema.js'; export const RESOURCE_DEFINITIONS: ListResourcesResult['resources'] = [ { diff --git a/apps/server/src/api-mcp/mcp.schema.ts b/apps/server/src/api-mcp/mcp.schema.ts new file mode 100644 index 000000000..c39437dd7 --- /dev/null +++ b/apps/server/src/api-mcp/mcp.schema.ts @@ -0,0 +1,168 @@ +/** + * Single source of truth for Ontime MCP documentation. + * + * Tool field schemas (EVENT_TIMER_FIELDS, EVENT_WRITABLE_FIELDS) and the schema resource + * (ONTIME_SCHEMA_MARKDOWN) are maintained here so that a single edit keeps tool descriptions, + * prompt guidance, and the agent-readable reference document in sync. + * + * Canonical type definitions live in packages/types/src/definitions/core/OntimeEntry.ts. + * Update this file when the data model changes. + */ + +// ---- Shared event field JSON schemas ---- +// Imported by mcp.tools.ts and spread into tool inputSchema.properties. + +export const EVENT_TIMER_FIELDS = { + timerType: { + type: 'string', + enum: ['count-down', 'count-up', 'clock', 'none'], + description: 'count-down: countdown from duration; count-up: elapsed time; clock: wall clock; none: no timer shown', + }, + endAction: { + type: 'string', + enum: ['none', 'load-next', 'play-next'], + description: 'Action when event ends: none = stop, load-next = cue next event, play-next = auto-start next event', + }, + linkStart: { + type: 'boolean', + description: + "Chain this event's start time to the previous event's end time — changing the first linked event propagates schedule changes to all linked followers", + }, + countToEnd: { type: 'boolean', description: 'Timer counts toward the scheduled end time rather than elapsed time' }, + timeWarning: { type: 'number', description: 'ms before timeEnd to enter warning state (e.g. 300000 = 5 min)' }, + timeDanger: { type: 'number', description: 'ms before timeEnd to enter danger state (e.g. 60000 = 1 min)' }, +} as const; + +export const EVENT_WRITABLE_FIELDS = { + cue: { type: 'string', description: 'Short free-form cue label — ask the user what naming convention they prefer' }, + title: { type: 'string', description: 'Event title shown in the rundown and views' }, + note: { type: 'string', description: 'Free-text note for production notes or references' }, + colour: { + type: 'string', + description: 'Hex colour (#RRGGBB) for visual grouping — ask the user what colour convention they use', + }, + skip: { type: 'boolean', description: 'If true, event is skipped during playback' }, + ...EVENT_TIMER_FIELDS, +} as const; + +// ---- Agent-readable schema document ---- +// Served at ontime://schema. Agents read this once per session to orient themselves +// before issuing tool calls. + +export const ONTIME_SCHEMA_MARKDOWN = `# Ontime data model + +A concise reference for how Ontime structures rundowns, events, and related data. + +## Rundown + +A rundown is an ordered list of entries rendered as a show schedule. A project can contain multiple rundowns; one is "loaded" at a time. + +\`\`\` +Rundown { + id: string + title: string + order: EntryId[] // top-level entry order + flatOrder: EntryId[] // includes entries nested in groups + entries: { [id: EntryId]: OntimeEntry } + revision: number +} +\`\`\` + +## Entries + +There are four entry types discriminated by \`type\`: + +### \`event\` — OntimeEvent (a timed show item) +\`\`\` +{ + type: 'event' + id: EntryId + cue: string // human-facing cue label + title: string + note: string + colour: string // hex, e.g. "#4A90D9" + timeStart: number // ms from midnight (09:00 = 32400000) + timeEnd: number // ms from midnight + duration: number // ms (= timeEnd - timeStart) + delay: number // accumulated delay in ms (runtime) + timerType: 'count-down' | 'count-up' | 'clock' | 'none' + endAction: 'none' | 'load-next' | 'play-next' + linkStart: boolean // chain start to previous event's end + countToEnd: boolean // timer counts to planned end time + skip: boolean // event is skipped during playback + timeWarning: number // ms before end to trigger 'warning' state + timeDanger: number // ms before end to trigger 'danger' state + custom: { [key: string]: string } // custom field values +} +\`\`\` + +### \`delay\` — OntimeDelay (schedule shift applied to following events) +\`\`\` +{ type: 'delay', id, duration: number } +\`\`\` + +### \`group\` — OntimeGroup (nested container of entries) +\`\`\` +{ type: 'group', id, title, colour, note, entries: EntryId[], targetDuration?: number } +\`\`\` + +### \`milestone\` — OntimeMilestone (marker with no timer) +\`\`\` +{ type: 'milestone', id, cue, title, note, colour } +\`\`\` + +## Time format +All time fields are **milliseconds from midnight (local)**. Examples: +- 09:00:00 = 32400000 +- 09:30:00 = 34200000 +- 14:15:00 = 51300000 +- Duration of 45 min = 2700000 + +## Custom fields +Custom fields are project-scoped definitions. Get definitions at \`ontime://project/custom-fields\`. +Each event stores values at \`event.custom[fieldKey]\`. + +## Playback states (runtime only) +\`'stop' | 'play' | 'pause' | 'armed' | 'roll'\` +When playback is not \`stop\`, mutating tools warn that changes are visible immediately. + +## Available resources +- \`ontime://schema\` — this document +- \`ontime://rundown/current\` — the currently loaded rundown (JSON) +- \`ontime://rundowns\` — all rundowns in the project (JSON) +- \`ontime://project/info\` — project metadata (JSON) +- \`ontime://project/custom-fields\` — custom field definitions (JSON) +- \`ontime://docs\` — index of Ontime documentation topics with URLs + +## Further reading +Full Ontime documentation: **https://docs.getontime.no** +`; + +// ---- Documentation index ---- +// Served at ontime://docs. Agents read this when they need to point users to official docs. + +export const ONTIME_DOCS_MARKDOWN = `# Ontime Documentation Index + +Main site: https://docs.getontime.no + +## Getting started +- Installation & setup: https://docs.getontime.no/installation/ + +## Core concepts +- Rundown: https://docs.getontime.no/concepts/rundown/ +- Timer types (count-down, count-up, clock, none): https://docs.getontime.no/concepts/timer/ +- Time entry format: https://docs.getontime.no/concepts/time-entry/ +- Event actions (end action, link start): https://docs.getontime.no/concepts/event-actions/ +- Delays and blocks: https://docs.getontime.no/concepts/delays-and-blocks/ + +## Features +- Custom fields: https://docs.getontime.no/features/custom-fields/ +- Automations (triggers, filters, outputs): https://docs.getontime.no/features/automations/ +- URL presets / shared views: https://docs.getontime.no/features/url-presets/ +- HTTP Integration: https://docs.getontime.no/api/http/ +- OSC Integration: https://docs.getontime.no/api/osc/ + +## API reference +- REST API overview: https://docs.getontime.no/api/ +- WebSocket events: https://docs.getontime.no/api/websocket/ +`; diff --git a/apps/server/src/api-mcp/mcp.tools.ts b/apps/server/src/api-mcp/mcp.tools.ts index f3651c0c2..e99ae7c83 100644 --- a/apps/server/src/api-mcp/mcp.tools.ts +++ b/apps/server/src/api-mcp/mcp.tools.ts @@ -25,45 +25,11 @@ import { getState } from '../stores/runtimeState.js'; import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { deleteRundown, renameRundown, rundownListResponse } from './mcp.service.js'; +import { EVENT_TIMER_FIELDS, EVENT_WRITABLE_FIELDS } from './mcp.schema.js'; // Graceful truncation to keep tool responses within typical MCP context windows const CHARACTER_LIMIT = 25_000; -// ---- Shared event field JSON schemas ---- -// Reused across create_event, update_event, create_events_batch, batch_update_events to avoid repetition. -const EVENT_TIMER_FIELDS = { - timerType: { - type: 'string', - enum: ['count-down', 'count-up', 'clock', 'none'], - description: 'count-down: countdown from duration; count-up: elapsed time; clock: wall clock; none: no timer shown', - }, - endAction: { - type: 'string', - enum: ['none', 'load-next', 'play-next'], - description: 'Action when event ends: none = stop, load-next = cue next event, play-next = auto-start next event', - }, - linkStart: { - type: 'boolean', - description: - "Chain this event's start time to the previous event's end time — changing the first linked event propagates schedule changes to all linked followers", - }, - countToEnd: { type: 'boolean', description: 'Timer counts toward the scheduled end time rather than elapsed time' }, - timeWarning: { type: 'number', description: 'ms before timeEnd to enter warning state (e.g. 300000 = 5 min)' }, - timeDanger: { type: 'number', description: 'ms before timeEnd to enter danger state (e.g. 60000 = 1 min)' }, -} as const; - -const EVENT_WRITABLE_FIELDS = { - cue: { type: 'string', description: 'Short free-form cue label — ask the user what naming convention they prefer' }, - title: { type: 'string', description: 'Event title shown in the rundown and views' }, - note: { type: 'string', description: 'Free-text note for production notes or references' }, - colour: { - type: 'string', - description: 'Hex colour (#RRGGBB) for visual grouping — ask the user what colour convention they use', - }, - skip: { type: 'boolean', description: 'If true, event is skipped during playback' }, - ...EVENT_TIMER_FIELDS, -} as const; - // ---- MCP tool annotation presets ---- // https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations const READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;