Files
ontime/apps/server/src/api-mcp/mcp.prompts.ts
T
2026-06-27 16:27:59 +02:00

189 lines
10 KiB
TypeScript

import type { GetPromptResult, ListPromptsResult } from '@modelcontextprotocol/sdk/types.js';
export const PROMPT_DEFINITIONS: ListPromptsResult['prompts'] = [
{
name: 'create_rundown_from_agenda',
description: 'Convert a plain-text agenda into an Ontime rundown using ontime_batch_create_entries',
arguments: [{ name: 'agenda', description: 'Plain-text agenda to convert', required: true }],
},
{
name: 'bulk_edit_rundown',
description: 'Apply a bulk change across the rundown (recolour, reschedule, skip events, etc.)',
arguments: [
{
name: 'instruction',
description: 'What to change, e.g. "colour all keynotes blue"',
required: true,
},
],
},
{
name: 'validate_rundown',
description: 'Check the current rundown for common issues: missing cues, overlaps, gaps, zero-duration events',
arguments: [],
},
{
name: 'restructure_rundown',
description: 'Reorder events in the rundown according to an instruction',
arguments: [
{
name: 'instruction',
description: 'How to restructure, e.g. "move all breaks to after keynotes"',
required: true,
},
],
},
];
function userPrompt(description: string, text: string): GetPromptResult {
return { description, messages: [{ role: 'user', content: { type: 'text', text } }] };
}
export function handleGetPrompt(name: string, args: Record<string, string>): GetPromptResult {
if (name === 'create_rundown_from_agenda') {
return userPrompt(
'Build an Ontime rundown from a plain-text agenda',
`Convert the following agenda into an Ontime rundown.
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.
2. Build an array of events in order and call ontime_batch_create_entries ONCE with all of them. This is much faster than calling ontime_create_entry per item.
3. If the rundown already has events, pass \`after: <last event id>\` on the batch call so new events chain from the end.
Entry type guidance:
- Use \`event\` for anything with a scheduled time and duration (talks, panels, breaks, meals).
- Use \`milestone\` for non-timed markers that don't advance playback (e.g. "Doors open", "Broadcast start").
- Use \`delay\` only when the user explicitly wants to model schedule drift that shifts all following events.
- Use \`group\` to collect related events into a named block. Groups are created with a title only — use ontime_update_entry afterwards to set \`colour\`, \`note\`, \`custom\`, or \`targetDuration\`.
Event timing:
- Provide a title plus enough timing data for Ontime to infer a timing strategy.
- \`timeStart\` + \`duration\`: keeps duration fixed and calculates \`timeEnd\`.
- \`timeStart\` + \`timeEnd\`: keeps end time fixed and calculates \`duration\`.
- \`timeEnd\` + \`duration\`: calculates \`timeStart\`.
- Avoid sending \`timeStart\`, \`timeEnd\`, and \`duration\` together unless you intentionally want Ontime to prioritise duration and recalculate \`timeEnd\`.
Timer type (timerType):
- \`count-down\` (default): counts down from duration. Use for most timed sessions.
- \`count-up\`: counts elapsed time. Use for open-ended items like Q&A or audience discussion.
- \`clock\`: shows wall-clock time. Use for broadcast-start or house-open markers.
- \`none\`: no timer shown. Use for purely informational or non-timed items.
End action (endAction):
- \`none\` (default): stops at end; operator must manually start the next event.
- \`load-next\`: pre-arms the next event; operator triggers start. Use when a human handoff is needed.
- \`play-next\`: automatically starts the next event. Use for seamless back-to-back segments with no gap.
Linking (linkStart):
- \`linkStart\` controls schedule-change propagation through the rundown.
- When an event is linked, it inherits the end time of the previous playable event as its start time.
- The event's \`timeStrategy\` decides how it adapts to the inherited start: lock duration updates the end time; lock end updates the duration.
- Ideal for segments within a block where only the anchor start time and individual durations are managed directly.
Flags (flag):
- Set \`flag: true\` on events that are critical operational markers (keynote starts, broadcast moments, VIP arrivals).
- The operator view shows a countdown to the next flagged event — use sparingly for maximum impact.
Colours:
- Ask the user what colour convention they use before applying any colours.
- Common pattern: one colour per event type (keynotes, panels, breaks, meals).
- Colours are hex strings: \`#RRGGBB\`.
Custom fields (custom):
- Call ontime_get_custom_fields for the project's field keys (cuesheet-style columns such as camera, graphics, speaker).
- Store values per entry at \`custom: { <fieldKey>: <value> }\` — only use keys that exist in the project.
- If the user's requested field is ambiguous, show the existing field list before choosing a key, so you avoid duplicate concepts such as \`Cam\`, \`camera\`, and \`Cameras\`.
Agenda:
${args.agenda}`,
);
}
if (name === 'bulk_edit_rundown') {
return userPrompt(
'Apply a bulk change across the rundown',
`Apply the following bulk edit to the current Ontime rundown: "${args.instruction}"
Strategy:
1. Call ontime_get_rundown to see the current events, their IDs, and field values.
2. Determine which event IDs are affected by the instruction.
3. If every affected entry receives the SAME field values (e.g. "colour all keynotes purple", "skip all breaks"): call ontime_batch_update_entries once with { ids, data }.
4. If each event needs DIFFERENT field values (e.g. "shift everything 30 minutes later"): check first if events use linkStart. If they do, changing the anchor event's timeStart or duration can cascade to linked followers — you may only need to update one event. Otherwise, compute the new values per event and call ontime_update_entry for each.
Time shift mechanics:
- All time fields are milliseconds from midnight; compute arithmetic before calling the tools.
- timeEnd - timeStart = duration. When shifting times, decide whether to keep duration fixed (timeEnd moves with timeStart) or keep timeEnd fixed (duration shrinks). Provide only the fields you intend to change — the server infers the strategy from which fields are present.
- For "shift everything N minutes later": update timeStart and timeEnd per event (or just timeStart on the anchor event of a linkStart chain). Do not use ontime_batch_update_entries for this unless every target event should receive the exact same timeStart/timeEnd values.
Automation risks:
- Setting \`endAction: 'play-next'\` on multiple events creates an automatic playback chain that removes operator control between those events. Confirm with the user before applying.
- Bulk-setting \`skip: true\` will hide events from playback. Confirm before applying to many events.`,
);
}
if (name === 'validate_rundown') {
return userPrompt(
'Check the current rundown for common issues',
`Validate the currently loaded Ontime rundown and report issues.
Steps:
1. Call ontime_get_rundown to read all events and their fields.
2. Call ontime_get_rundown_metadata for totals (totalDuration, totalDelay, totalDays, firstStart, lastEnd, flags).
Check and report:
Schedule integrity:
- Events with missing \`cue\` or \`title\`: these are usually worth checking, but not necessarily errors
- Events with \`duration\` of 0 or negative
- Events with \`gap < 0\`: overlaps the previous timed event and is a conflict
- Large unexplained positive gaps between consecutive timed events (> 30 min): check whether these are intentional
- Events where \`timeEnd < timeStart\`: these cross midnight; confirm this is intentional
- Events whose \`timeStart\` is the same as or earlier than the previous playable event's \`timeStart\`: Ontime schedules these on the next day; confirm this is intentional
Timing and linking:
- \`metadata.totalDays > 0\`: show spans midnight — confirm this is intentional
- \`metadata.totalDelay !== 0\`: active delay entries are shifting the schedule by this many ms; report the net shift
- Events with \`linkStart: true\` that are first in the rundown (no predecessor to link to)
Automation:
- Events with \`endAction: 'play-next'\`: these form automatic playback chains. List each chain so the user can confirm the automation is intentional.
- Events with \`flag: true\`: these are the operator's critical markers. List them so the user can verify they are correct and complete.
Skipped events:
- Events with \`skip: true\` — confirm with the user these are intentional
Totals:
- Total rundown duration and whether it matches the user's expected show length (ask if unknown)
Present issues grouped by severity: ERROR (breaks playback), WARNING (likely mistake), INFO (worth confirming).`,
);
}
if (name === 'restructure_rundown') {
return userPrompt(
'Reorder events in the rundown',
`Restructure the current Ontime rundown: "${args.instruction}"
Steps:
1. Call ontime_get_rundown to see the current order, groups, and event fields.
2. Note which events are inside groups (check each group's \`entries\` array vs the top-level \`order\` array).
3. Compute the target arrangement as a sequence of moves.
4. For each event that needs to move, call ontime_reorder_entry:
- \`order: 'before'\` or \`'after'\` — places the event as a sibling next to destinationId
- \`order: 'insert'\` — places the event inside a group (destinationId must be the group's ID)
5. Call ontime_get_rundown again to confirm the new order.
Group awareness:
- Events inside a group appear in the group's \`entries\` array, not in the top-level \`order\`.
- To move an event out of a group, reorder it before/after a top-level entry.
- To move an event into a group, use \`order: 'insert'\` with the group as destinationId.
- A group's \`targetDuration\` is a planning hint only — moving events in or out does not break anything.
Efficiency tip: plan moves in the direction of the target position to minimise reorder calls. Avoid moving the same event twice — compute the full target sequence before issuing any calls.`,
);
}
throw new Error(`Unknown prompt: ${name}`);
}