@workingdevshero/olympia-ait-adapter
v0.1.1
Published
MCP adapter that converts between Olympia Fitness & Performance program XLSX workbooks and a strict JSON schema.
Readme
olympia-ait-adapter
MCP adapter that converts Olympia Fitness & Performance program XLSX workbooks to and from a strict JSON schema. Built for the Automate It framework so AI agents can author new strength-and-conditioning programs 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.
This package gives the agent a clean structured view of an Olympia program 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
Two MCP tools, exposed over stdio:
| Tool | Direction | Purpose |
| ------------------------ | --------------- | ------------------------------------------------------------------ |
| olympia_xlsx_to_json | XLSX → JSON | Parse an Olympia workbook into a typed Program document. |
| olympia_json_to_xlsx | JSON → XLSX | Render a Program document back to a styled .xlsx on disk. |
The JSON schema is defined once in src/schema/program.ts using Zod and
surfaces 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
JSON shape
type Program = {
client: string; // "Bobby Galli"
programDate: string; // "4.26"
weekDates?: (string | null)[]; // optional, length 6
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.
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.
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"]
}
}
}Both tools take absolute paths to .xlsx files on the local filesystem.
Repo layout
src/
schema/program.ts # Zod schema — single source of truth
reader/
parse-workbook.ts # Workbook → Program JSON
parse-day-sheet.ts # One sheet, section detection, supplemental capture
writer/
build-workbook.ts # Program → 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/
xlsx-to-json.ts # MCP handler
json-to-xlsx.ts # MCP handler
server.ts # Bootstrap, registers both tools
assets/
olympia-logo.png # Extracted from sample workbooks; embedded in output
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 input (week count, group label regex, section kinds, missing fields).reader.test.ts— All three sample workbooks 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 sample,parse → write → parseproduces JSON equal to the original.mcp-smoke.test.ts— End-to-end MCP server check via the in-memory transport: tool discovery, round-trip viaclient.callTool, 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.
