iwork-mcp
An MCP server that drives Pages, Numbers and Keynote from Claude Code (or any MCP client): it generates documents, spreadsheets and presentations, and exports them to PDF or Word.
README
iwork-mcp
An MCP server that drives Pages, Numbers and Keynote from Claude Code (or any MCP client): it generates documents, spreadsheets and presentations, and exports them to PDF, Word, Excel, CSV, PowerPoint, RTF, plain text or PNG slide images.
The model writes the content; the Apple apps do the layout, the arithmetic and the
exporting. There is no proprietary format to reconstruct — .pages, .numbers and
.key are compressed Protobuf inside a ZIP, and reading them by hand is a dead end.
Here they are written by the people who invented them.
Output is laid out, not merely filled in: fitted column widths, real header rows, merged spanning headers, a heading hierarchy in documents, and any of the 53 Keynote themes or 111 Pages templates installed on the machine. A generated file should look like someone made it, not like a data dump that happens to open in Numbers.
Requirements
macOS with iWork installed, and Python 3.11+.
Install
git clone https://github.com/atipicguy/iwork-mcp.git
cd iwork-mcp
uv sync
uv run pytest # 96 tests, no app windows opened
Register it in ~/.claude.json, under mcpServers:
"iwork": {
"command": "/absolute/path/to/iwork-mcp/.venv/bin/python",
"args": ["-m", "iwork.server_mcp"],
"cwd": "/absolute/path/to/iwork-mcp"
}
On first use macOS asks for automation permission for each app: grant it, or every
call fails. iwork_status is the tool to call first when something is wrong — it
separates a missing app from a denied permission from a broken script.
Tools
| Tool | What it does |
|---|---|
iwork_status |
which apps respond, and with what version |
pages_create / pages_read |
documents, with a real heading hierarchy |
pages_templates |
the 111 installed templates, by name |
pages_export |
PDF, Word, EPUB, plain text, RTF |
pages_replace / pages_append |
edit an existing document without wrecking its styles |
numbers_create / numbers_read |
spreadsheets, with real formulas |
numbers_set |
write specific cells in an existing sheet; the app recalculates |
numbers_sort |
sort by a column, header left in place |
numbers_export |
PDF, Excel, CSV |
keynote_themes / keynote_layouts |
the 53 installed themes and their layouts |
keynote_create |
presentations, with presenter notes |
keynote_add_image / keynote_add_chart |
put an image or one of 6 chart types on a slide |
keynote_slide_size |
the theme's slide dimensions, before choosing coordinates |
keynote_export / keynote_export_pdf |
PDF (optionally with notes), PowerPoint, PNG slide images |
Filling in a document you already have — a contract, a letter — is a sequence of
pages_replace calls on its placeholders. Updating a quote is numbers_set on three
cells: the formulas that depend on them recalculate by themselves, because Numbers does
it, not us.
Formulas are written as in the app: =SUM(B2:B3) is inserted and computed. Reading
the cell back gives the result, not the formula text. That is the difference between
driving a spreadsheet and producing a CSV that looks like one.
Design comes from the app, not from us
The apps ship a lot of design, and none of it is reachable by writing a file by hand.
keynote_themes and pages_templates list what is installed — 53 and 111 respectively
on a stock Mac — and keynote_create(theme=...) / pages_create(template=...) build on
one. This is the single biggest difference in how the result looks: the default blank
theme is what makes a generated deck read as generated.
With a Pages template the heading face is left alone deliberately. Forcing Helvetica headings onto a serif letterhead looks like two documents glued together, so only the sizes are imposed and the typeface stays the template's.
Spreadsheets that look like the one you were copying
header_rows=2 plus merge=["H1:I1"] gives the two-tier header real bookkeeping uses:
one CASSA spanning ENTRATE and USCITE. Column widths are fitted to the content and
wrapping is switched off wherever the content already fits, so a notes column stops
dragging every row to three lines tall.
numbers_sort leaves the header in place — and takes footer_rows, which matters more
than it sounds: a TOTAL row is by value the largest in its column, so a descending sort
lifts it to the top and its formula then points at the wrong rows.
numbers_create(
rows=[["Guest", "Nights", "CASH", "", "TOTAL"],
["", "", "IN", "OUT", ""],
["Maja Miletic", "7", "1.360,00", "", "=C3-D3"],
["Martin Richardson", "5", "1.220,00", "", "=C4-D4"],
["TOTAL", "=SUM(B3:B4)", "=SUM(C3:C4)", "", "=SUM(E3:E4)"]],
save_in="~/Desktop/bookings.numbers",
header_rows=2, # two-tier header
merge=["C1:D1"], # one CASH spanning IN and OUT
column_formats={"C": "currency", "D": "currency", "E": "currency"},
)
Slides that carry more than bullets
Each slide accepts notes, which become the presenter notes;
keynote_export(fmt="pdf", notes=True) then produces the handout with the notes printed
under each slide, which is the form people actually rehearse from. keynote_add_chart
places one of six chart types, keynote_add_image an image. Both take coordinates —
worth passing, because left to itself Keynote centres the object on top of the bullets —
and anything that would hang off the edge is nudged back inside.
What it deliberately does not do
- It does not close documents it did not open. Launching an iWork app reopens the
ones you had on screen: two real ones came back during development. An
indiscriminate
closewould throw away someone else's work. - It does not overwrite existing files. That is irreversible and invisible in the reply: whoever reads "done" has no way to know what was there before.
- It never concatenates text into the script. See below: that would be injection.
The traps, all paid for
AppleScript injection. Interpolating user text into the script source is the same
vulnerability as in SQL: a quote breaks it, a carefully crafted line executes. Here
everything goes through osascript - arg1 arg2 and on run argv. Verified with text
containing tell application "Finder" to beep: it landed in the document as text and
was not executed.
text items of inside a tell application block. It is sent to the app instead
of being evaluated by AppleScript, and the app answers -1728 can't get. Cost one
debugging session on Numbers and a second one avoided on Keynote. All text splitting
stays in Python; the apps receive values already separated, one per argument.
Numbers parses what it is handed according to the system locale. Measured on an
Italian Mac: "1360.5" lands in the cell as text, "1360,5" becomes the number
1360.5. A canonical decimal therefore produces a column that cannot be summed — and
nothing in the reply says so. Values are parsed in Python (accepting 1360.5,
1360,5, 1.360,00 and 1,360.00) and re-emitted with this Mac's separator.
set bold of paragraph 1 of body text to true replaces the paragraph's text with the
word "true". No error. Pages exposes only size, font and color as usable text
properties — paragraph style, alignment, space before and line spacing are not
settable at all. Weight comes from a bold face instead. The lesson generalises: with
these apps, checking that a command did not raise is not evidence that it did what you
meant. Read the value back.
Rewriting body text wholesale flattens the formatting. The worst of the lot,
because the first test hides it: replace one word and rewrite the whole body, and the
text comes out right and looks done — but a 9pt paragraph came back at 28pt, having
inherited the first one's style. On a contract template that means shipping a file that
reads correctly and is laid out wrong. Edits are therefore targeted: only the
affected paragraph N of body text is reassigned, and the others stay intact (verified:
26 stays 26, 8 stays 8 after a replacement of a different length).
A closed iWork app is not launched by tell application. The script fails with a
flat -600, the application is not running, and so does the first call of every
session. AppleScript's own launch and activate do not fix it — measured on Keynote,
both still returned -600. Only LaunchServices does: open -g -a, with -g so the app
does not steal focus mid-task.
Writing outside the grid does not widen the table. In Numbers a cell beyond
row count is not created: it is a flat -10006. The table has to be widened first,
which means translating AA12 into row 12, column 27.
Empty cells are missing value, which coerced to a string becomes the text
"missing value" — and would land in the data as if someone had typed it.
Numbers infers types, and gets it wrong silently. Writing Giugno into a cell reads
back lunedì 1 giugno 2026 alle ore 00:00:00. No error: just wrong data. The format
must be imposed before the value (set format of cell … to text), and only on what
is neither a number nor a formula — forcing numbers too would make them unsummable and
break the formulas using them.
Keynote layout names are localized. "Title & Bullets" does not exist on an Italian
Mac: it is called "Titolo ed elenco". keynote_layouts asks the app instead of listing
them hard-coded, and the missing-layout error hands back the real ones. One of them even
contains an invisible soft hyphen (Dichiarazione) — one more reason to copy them from
the app rather than type them.
Numbers read back are localized. 1250.5 comes back as 1250,5. Anyone converting
to float needs to know.
ref and descending cannot be used as variable names. ref is short for
a reference to, so a script using it does not compile — and the parse error
points at the following statement, not the guilty one. descending is worse:
it is also the sort-direction enumerator, so the script compiles fine and fails
at run time trying to coerce a constant to a boolean. mod is a third one, the
modulo operator. The test suite runs osacompile over every script in the
package, which catches the whole first family in milliseconds without opening an
app; the second kind only surfaces when the script actually runs.
Formulas come back localized. Write =SUM(B2:B4), read the cell's formula
property, and you get =SOMMA(B2:B4) on an Italian Mac. A round-trip that
rewrites what it reads will produce formulas that only work in one language.
A CSV export is not comma-separated. Numbers writes it with the system list
separator — ; here — and with the formatted values, so a currency column
comes out as 100,00 €.
Pages cannot insert images. make new image fails on the document
(Non so come creare TMAScriptImageInfoProxy) and on its images element (an
AppleEvent handler error). Keynote accepts them without complaint. Similarly
Numbers cannot create sheets: make new sheet fails, though a second
table inside an existing sheet works.
The slide size comes from the theme, not from Keynote. "Bianco di base" is
1920x1080 and "Bianco" is 1024x768, so coordinates that centre an image in one
put it off the edge of the other. keynote_slide_size asks, and anything placed
too close to an edge is nudged back inside — the object's own width is only
knowable after it exists.
There is no decimal-places property. Column format (number, currency, percent,
text) is settable; the number of decimals is not. A column left on auto shows 1360
next to 2349,5. column_formats={"C": "currency"} is the way to get consistent
decimals.
The apps are not named what they seem. On this machine they are
Pages Creator Studio.app, bundle id com.apple.Pages — not Pages.app nor
com.apple.iWork.Pages. Searching for the historical names returns nothing and leads
to the wrong conclusion that iWork is not installed.
Boundaries
- No writing outside the paths given explicitly in the calls.
- No commits, no pushes.
License
MIT — see LICENSE.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。