@workingdevshero/olympia-ait-adapter
v1.0.0
Published
MCP adapter that converts between Olympia Fitness & Performance XLSX workbooks (programs and companion warm-ups) and strict JSON schemas.
Readme
olympia-ait-adapter
MCP adapter that converts Olympia Fitness & Performance XLSX workbooks — training programs and their companion warm-up documents — to and from strict JSON schemas. Built for the Automate It framework so AI agents can author new strength-and-conditioning paperwork from prior client data without ever touching XLSX directly.
Why this exists
Olympia's coaches build client programs in XLSX workbooks — one client per
file, one sheet per training day, with merged cells, custom borders, an
embedded logo, and a domain-specific notation (12E, :20E, 12EL,
x3 Rest :60, etc.). Asking an LLM to edit XLSX directly is fragile: it
corrupts merges, loses the logo, and can't reason about the implicit
6-week / 1A-1B / set-by-set structure encoded in the visual layout.
Each program also ships with a companion warm-up workbook — same client, same date, paired day-for-day (the "Day 4" warm-up column is performed before the "Day 4" program sheet). It's a completely different layout: one sheet where each day is a column of titled sections of freeform exercise lines.
This package gives the agent a clean structured view of both document types and guarantees well-formed output: the LLM reads JSON, reasons about it, writes JSON, and the adapter handles all the visual fidelity on the way back to XLSX.
What it does
Four MCP tools, exposed over stdio:
| Tool | Direction | Purpose |
| ------------------------------ | ----------- | ---------------------------------------------------------------- |
| olympia_program_xlsx_to_json | XLSX → JSON | Parse an Olympia program workbook into a typed Program. |
| olympia_program_json_to_xlsx | JSON → XLSX | Render a Program back to a styled .xlsx on disk. |
| olympia_warmup_xlsx_to_json | XLSX → JSON | Parse a companion warm-up workbook into a typed WarmUpDocument. |
| olympia_warmup_json_to_xlsx | JSON → XLSX | Render a WarmUpDocument back to a styled .xlsx on disk. |
The JSON schemas are defined once in src/schema/program.ts and
src/schema/warmup.ts using Zod and surface as:
- The TypeScript type used internally
- The runtime validator at each tool boundary
- The JSON Schema published as each tool's
inputSchemaso the LLM sees the full typed contract at discovery time
All object schemas are strict: unknown fields are rejected with an error rather than silently stripped, so callers authoring against a stale contract fail loudly.
JSON shape — programs
type Program = {
client: string; // "Bobby Galli"
programDate: string; // "4.26"
days: Day[];
};
type Day = {
name: string; // "Day 1"
sections: Section[];
supplementalNotes?: SupplementalNote[];
};
type Section = {
kind: "warmup" | "main" | "sportSpecific" | "supplemental" | "conditioning";
exercises: Exercise[];
};
type Exercise = {
name: string; // "2DB Alternating Bench Press"
groupLabel?: string; // "1A", "1B", "2A"… matches /^\d+[A-Z]$/
notes?: string;
sets: (string | null)[][]; // [setIdx][weekIdx]; inner length 6
};
type SupplementalNote = {
row: number; // offset from "Sport Specific/" label row
col: string; // "C" | "D" | … | "P"
value: string;
};Prescription cell grammar
Olympia does not prescribe weights. Each sets[setIdx][weekIdx] is a
single opaque string encoding reps, time, or breaths:
| Format | Meaning |
| --------------------- | ------------------------------------ |
| "12" / "10E" | reps (E = each side) |
| ":20" / ":20E" | seconds (E = each side) |
| "5 breaths" | breath count |
| "12EL" / "10ER" | reps each side, left or right lead |
| free-form strings | circuit-style instructions, OK too |
| null | empty cell |
The schema enforces shape (6 weeks per set, group label regex, allowed section kinds) but does not validate the grammar of each prescription string — Olympia's own files include free-form entries in some spots.
The Date: cells in each sheet's header row are intentionally left
blank — clients hand-write the week dates on the printed sheet, so the
schema has no field for them and the writer never fills them.
Supplemental area
The bottom of every day sheet is a trainer-managed "Sport Specific / Supplemental" area where coaches write athlete-specific instructions that don't fit the structured exercise table (conditioning circuits, sport drills, recovery work). The adapter:
- Reads that area as a sparse list of
{ row, col, value }entries (master cells only) so the signal is preserved for downstream agents. - Writes the fixed 10-row template (with "Sport Specific/" and
"Supplemental" labels) at the bottom of every day, then layers any
supplied
supplementalNoteson top.
JSON shape — warm-ups
type WarmUpDocument = {
client: string; // "Bobby Galli" — same as companion program
warmUpDate: string; // "4.26" — matches the companion programDate
days: WarmUpDay[];
};
type WarmUpDay = {
name: string; // "Day 4" — matches a program day sheet name
sections: WarmUpSection[];
};
type WarmUpSection = {
title: string; // "Foam Roll", "Prep \ Activation X2 Rounds"
items: WarmUpItem[];
};
type WarmUpItem = {
text: string; // "Pecs :45E" — prescription inline, never parsed
note?: string; // "(^R.F.E on SB, F-Roller for Balance^)"
};The warm-up layout is days-as-columns on a single sheet (A, C, E, … with
empty gutter columns between), sections row-aligned across days. Section
headers are bold; exercise lines are plain with the prescription shorthand
embedded in the text (:45E, X8br, X4ES — trainer conventions, kept
opaque); note lines render in italics under their exercise. When decoding,
italic or fully-parenthesized lines are treated as notes attached to the
item above — so never author an item whose entire text is parenthesized.
Skill
skills/olympia-authoring/SKILL.md (shipped in the npm package) is an
agent-facing technical reference covering both formats: the schemas, the
prescription notation, the program/warm-up pairing rules, and the
decode → author → encode workflow.
Installation
npm install
npm run buildThe built CLI lives at dist/server.js; the bin entry registers it as
olympia-ait-adapter.
Running the MCP server
# Dev (TypeScript via tsx)
npm run dev
# Built (after `npm run build`)
npm start
# or
npx olympia-ait-adapterThe server speaks the Model Context Protocol over stdio. To wire it into Claude or another MCP client, add an entry like:
{
"mcpServers": {
"olympia-ait-adapter": {
"command": "npx",
"args": ["olympia-ait-adapter"]
}
}
}All tools take absolute paths to .xlsx files on the local filesystem.
Repo layout
src/
schema/
program.ts # Zod schema for programs — single source of truth
warmup.ts # Zod schema for warm-up documents
reader/
cell-utils.ts # Shared ExcelJS cell-value helpers
parse-workbook.ts # Workbook → Program JSON
parse-day-sheet.ts # One sheet, section detection, supplemental capture
parse-warmup-workbook.ts # Warm-up workbook → WarmUpDocument JSON
writer/
build-workbook.ts # Program → ExcelJS workbook
build-warmup-workbook.ts # WarmUpDocument → ExcelJS workbook
layout.ts # Column widths, row positions, logo anchor
styles.ts # All borders/fonts/fills as constants
stamp-exercise.ts # One exercise block (variable sets, group borders)
supplemental-template.ts # Fixed bottom template + supplementalNotes overlay
tools/
program-xlsx-to-json.ts # MCP handler
program-json-to-xlsx.ts # MCP handler
warmup-xlsx-to-json.ts # MCP handler
warmup-json-to-xlsx.ts # MCP handler
server.ts # Bootstrap, registers all four tools
assets/
olympia-logo.png # Extracted from sample workbooks; embedded in output
skills/
olympia-authoring/SKILL.md # Agent-facing technical reference for both formats
samples/ # Real Olympia .xlsx files used as round-trip fixtures
scripts/ # Inspection helpers used during development
tests/ # vitest — schema, reader, round-trip, MCP smokeTests
npm test # one-shot
npm run test:watch # vitest watch mode
npm run typecheck # tsc --noEmitCoverage:
schema.test.ts— Zod rejects malformed program input (week count, group label regex, section kinds, missing fields).reader.test.ts— All three program samples parse into schema-validProgramdocuments; spot checks on Bobby's first exercise, Giulianna's variable set counts, exercise notes, supplemental notes.round-trip.test.ts— The primary determinism guarantee: for each program sample,parse → write → parseproduces JSON equal to the original.warmup-doc-schema.test.ts— Zod accept/reject tables for the warm-up document schema.warmup-doc-reader.test.ts— The warm-up sample parses into a schema-validWarmUpDocumentwith verbatim spot checks (including the note-attachment rule); warning paths for irregular layouts.warmup-doc-writer.test.ts— Day/section/item/note styling and cross-day section alignment, verified by re-reading the written file.warmup-doc-round-trip.test.ts— Warm-up determinism: the sample and an authored document both surviveparse → write → parse/write → parseunchanged.mcp-smoke.test.ts— End-to-end MCP server check via the in-memory transport: tool discovery (all four tools), round-trips viaclient.callToolfor both document types, schema rejection of malformed input at the SDK boundary.
Determinism
The writer is fully programmatic — no template .xlsx file. Every byte of
the output is determined by code plus the input JSON, so the same JSON
produces the same workbook. The Olympia logo is the only binary asset
(src/assets/olympia-logo.png).
This trade-off is intentional: templates introduce hidden state that ExcelJS occasionally drops (drawings, theme colors), whereas pure code → output gives byte-stable, snapshot-testable results.
License
Internal — Automate It / Olympia Fitness & Performance RI.
