slidevim
Provides a vim-like text interface for editing Google Slides presentations, allowing agents to create, modify, delete, and rearrange slides and elements, run substitutions, manage tables/images, and write changes to Google via the Slides API.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@slidevimDelete all slides containing 'TODO' and replace 2025 with 2026"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
slidevim
Vim for Google Slides — built so an agent can edit a deck instead of clicking one.
Computer-use agents are bad at Google Slides for a structural reason: a deck is a tree of objects, but the only interface is a canvas. So the model takes a screenshot, guesses at a pixel, clicks, screenshots again to see whether it worked, and repeats. Ten screenshots and a lot of tokens to delete three slides.
slidevim gives the deck a text interface and a command language instead:
$ slidevim exec $DECK ':g/TODO/d | :%s/2025/2026/g | :w'
deleted 1 slide
deleted 1 slide
7 substitutions on 4 slides
wrote 9 ops to googleNo screenshots. No pixels. One round trip, and a diff you can inspect before it is sent.
Zero runtime dependencies. Node ≥ 22.6. MIT.
The idea
Vim's grammar is operator + motion, applied to a buffer of lines. slidevim
keeps the grammar and rebinds the nouns:
vim | slidevim |
buffer | the deck |
line | a slide |
word | an element on the slide (title, body, image, table) |
| delete this slide |
| substitute across every slide |
| delete every slide matching a pattern |
marks, registers, counts, | all of them, unchanged |
That mapping is the whole product. An agent that knows vim already knows this, which means the command language costs roughly zero tokens to teach.
What this takes from Vimium
Vimium is the direct ancestor, and its best
idea is not the vim keybindings — it is f. Press f and every link on the page
grows a short label; type the label and you have clicked it. You never aim at a
pixel, never describe a target, never say "the third link in the nav". The
thing you are looking at is labelled with exactly what you type to act on it.
That is the property slidevim is trying to give a deck, and it is worth considerably more to an agent than to a human, because an agent's alternative is a screenshot and a guess at a coordinate.
So slidevim has hints, and they work the way Vimium's do. Every slide and every element carries a code, in the buffer and in the preview:
[l] @3 #s03 TITLE_AND_BODY
[e] title Revenue
[w] tbl1 [table 3x3]
Quarter | Revenue | GrowthType the code where a label goes and it acts on that exact thing, with no motion and no cursor:
:text e Revenue and margin
:cell w 2,2 $9.9M
:drop w
:hint l | dd # or put the cursor there first, for slide operatorsTwo differences from Vimium, both forced by the reader being a program:
Hints are always on. Vimium hides them behind
fbecause they are visual clutter. For an agent they are four characters that remove a round trip, so they are cheaper shown than hidden.Hints are assigned in document order, which makes them a pure function of the deck — the same deck yields the same codes in the next process, and every CLI and MCP call is a new process. They shift when slides are added or removed, exactly as Vimium's do when the page changes, so re-read the buffer after that.
For when you would rather name a thing than look one up, @3/tbl1 and
#s03/tbl1 also work anywhere a label goes and also ignore the cursor. A bare
tbl1 still means "in this command's range", as in vim.
What slidevim deliberately does not take from Vimium: modes and remappable keys.
Insert mode exists to get out of the way of a human typing prose, and map
exists because humans have muscle memory. Neither applies here.
Related MCP server: google-slides-mcp
Install
git clone https://github.com/jaisharmz/slidevim
cd slidevim && npm install && npm run build && npm linkTry it against the bundled fixture deck, no credentials needed:
slidevim demo # print the buffer
slidevim demo ':g/TODO/d | :%s/2025/2026/g' # run a program
slidevim demo ':g/TODO/d | :w' --dry-run # the batchUpdate it would send
slidevim keys # command referenceThat third line is the quickest way to judge this project: it prints the real Google Slides API payload for a program, with no credential and no network.
Just tell Claude to make you a deck
The repo is set up as a Claude Code project. .mcp.json registers the MCP
server; .claude/skills/presentation/ holds the workflow.
cd slidevim && npm install && npm run build
claudemake me a presentation about why residential streets should be 20mph
Claude creates a deck under decks/, opens a live preview, hands you the URL,
and builds the deck while you watch. On the first run it asks once whether to
trust the project's MCP server; say yes.

Those yellow chips are the hints. They are the same codes the agent sees in its buffer, so what you are looking at and what it is typing are the same addresses.
Watching it work
A text interface is only convincing if you can see what it produced. slidevim serve opens a page that follows a local deck file, so you can put the preview
on one side of the screen and let an agent edit on the other:
slidevim new deck.json "Slower Roads"
slidevim serve --file deck.json # http://127.0.0.1:PORT
slidevim exec deck ':$new TITLE | :title Slower Roads | :w' --file deck.json./examples/build-a-deck.sh runs the whole thing — five slides, a table, a web
image, a substitution pass over the finished deck — with a pause between steps
so you can watch each one land. No credential needed.
The preview is an approximation, not a renderer: it knows structure and content, because that is all the model carries. It has no idea what your theme looks like. For pixels, open the real deck — Slides updates live while the API writes to it, which is the same demo with better fonts.
Appending: :$new adds a slide at the end. A plain :new inserts after the
cursor, and every new process starts with the cursor on slide 1, so a deck built
with bare :new calls comes out backwards. This bit me the first time I built a
deck with it.
The buffer
slidevim view $DECK renders the deck as text. Hints are in brackets, slides
are numbered, elements are labelled, and > marks the cursor:
deck:1AbC… "Northwind Q3 Review" 6 slides [+]
[ss] @1 #s01 TITLE
[sa] title Northwind Q3 Review
[sd] subtitle Prepared for the board, FY2025
> [sf] @2 #s02 TITLE_AND_BODY
[sj] title Agenda
[sk] body - Revenue
- Pricing changes
- Roadmap
notes Keep this to ninety seconds.
[sl] @3 #s03 TITLE_AND_BODY
[se] title Revenue
[sw] tbl1 [table 3x3]
Quarter | Revenue | Growth
Q1 2025 | $1.2M | 8%Every bracketed code is an address: :cell sw 2,2 $9.9M edits that table
wherever the cursor is. So is @3/tbl1, for when naming beats looking up. A
bare label means "in this command's range" — :text body … edits the body of
the current slide, :%text body … edits every slide's. Notes have no hint
because :notes addresses the slide, not an element on it.
The projection is one-way by design. Commands mutate the model and the view is re-rendered — nothing ever parses this format back into a deck, so there is no round trip to get subtly wrong.
Commands
motions j k gg G {n}G } { w b 0 $ /pat n N
edits dd yy dj dG 3dd x p P cc u <C-r> . "ayy
marks ma `a ranges: % 1,5 . $ .+2 'a,'b /pat/
:new [layout] insert a slide after the cursor (:new! before)
:title TEXT set the title (also :sub, :body, :notes)
:text LABEL TEXT set any element by label
:append LABEL TEXT append a paragraph
:image URL [alt] add an image
:table RxC add a table; :cell tbl1 2,3 TEXT
:drop LABEL delete an element
:s/old/new/g substitute (:%s for the whole deck)
:g/pat/CMD run CMD on every matching slide (:v inverts)
:m ADDR :t ADDR move / copy slides
:sort sort slides in range by title (:sort! reverses)
:skip :unskip toggle "skip in presentation"
:hint CODE put the cursor on what a hint code names (also :f)
:diff show pending ops without writing
:w :e :q :wq write, reload, quit:new inserts after the cursor; :$new appends at the end. Use :$new when
building a deck up from nothing.
Anywhere LABEL appears you may write a qualified address instead — @3/tbl1
by position, #s03/tbl1 by object id — which names its own slide and ignores
the cursor and the range. :3cell tbl1 2,2 X is the ex-range spelling of the
same thing.
Two deliberate deviations from vim:
Whitespace separates commands.
gg 3dd :wis three commands. Agents write programs as JSON strings, and unambiguous separators are worth more than<Space>as a synonym forl./patternand:exrun to a|or a newline, as in vim. A pattern may contain spaces, so terminate it before the next command:/q3 revenue | :s/2025/2026/g.
Nothing is written until :w. Until then :diff shows exactly what would be
sent.
Use from an agent (MCP)
{
"mcpServers": {
"slidevim": { "command": "node", "args": ["/path/to/slidevim/dist/mcp.js"] }
}
}Eight tools: slides_new, slides_preview, slides_view, slides_exec,
slides_diff, slides_write, slides_check_image, slides_keys. Only
slides_write touches the deck, so an agent can plan and self-check for free.
A typical exchange:
slides_view { deckId } -> the buffer
slides_exec { deckId, program: ":g/TODO/d" } -> the buffer, edited
slides_diff { deckId } -> "2 pending ops: delete slide s02, …"
slides_write { deckId } -> "wrote 2 ops to google"slidevim does not replace the agent's other tools
It is a text interface to one specific object, not a sandbox. The agent keeps its browser, its web search, its file system — and it should use them. Pictures are the obvious case: slidevim has no opinion about which image belongs on a slide, only about whether a given URL will work.
(the agent's own image search) -> some candidate URLs
slides_check_image { url } -> "ok image/jpeg 448 KB — Slides can fetch this"
slides_exec { program: ":image <url> a residential street with speed cushions" }That middle step earns its place. createImage is fetched by Google's
servers, not by yours, so a URL only works if it is publicly reachable,
PNG/JPEG/GIF, and under 50 MB. Image search results routinely fail all three —
they are pages about an image, or hotlink-protected, or behind a redirect that
wants a cookie — and Slides reports every one of those as a bare 400.
slides_check_image turns that into "content-type is text/html (this looks like
a page about the image, not the image file)" at the moment of choosing, which is
a thing an agent can actually act on. slidevim check-image <url> is the same
check from the shell.
Credentials
slidevim never stores a credential. Point it at a token you already have:
export SLIDEVIM_ACCESS_TOKEN="$(gcloud auth print-access-token)"
# or, refreshed on demand:
export SLIDEVIM_TOKEN_CMD="gcloud auth print-access-token"
# or a standard OAuth client:
export SLIDEVIM_CLIENT_ID=… SLIDEVIM_CLIENT_SECRET=… SLIDEVIM_REFRESH_TOKEN=…Scopes: presentations, drive.file.
Before pointing it at a deck you care about, look at the requests:
slidevim exec $DECK ':%s/2025/2026/g' --dry-run--dry-run compiles to Google Slides batchUpdate requests and prints them,
and it holds on every backend — the Google client sends nothing, the local
--file deck is not rewritten, and the demo fixture is not touched.
How it works
command text ──parse──▶ AST ──execute──▶ deck model (in memory)
│
diff vs last-synced snapshot
│
DeckOp[]
╱ ╲
memory backend Google backend
(batchUpdate)Two decisions carry the design:
Undo is a snapshot; sync is a diff. Commands mutate the model freely and
never journal an inverse. Undo restores a clone. :w diffs the working deck
against the last synced snapshot and emits the minimal op list. Correctness of
both falls out of the model rather than out of every command remembering to do
the right thing — which is why :g/pat/normal 3dd syncs correctly despite
nobody having thought about that combination.
The Google backend is a pure compiler plus a thin client. compileOps turns
ops into API requests with no I/O, so the entire write path is unit-tested
without a network or a credential.
Three places where the Slides API is sharper than it looks, and what this does about them:
createSlide.placeholderIdMappingslets the caller choose object ids, so model ids become the real Slides ids and stay stable across syncs.updateSlidesPosition.insertionIndexis indexed against the arrangement before the move, so a slide moving later in the deck must aim one past its destination. Reordering is computed by simulation, not by comparing final indices — an op list of "final index" moves is wrong the moment there is more than one of them.deleteTextis a 400 on a shape with no text, so the compiler tracks which shapes and table cells actually hold text and only clears those.
Tests
npm test # 126 tests, on Node 22 and Node 26
npm run typecheckThe suite's backbone is a round-trip property: for a corpus of programs, writing the deck and reading it back must reproduce the working deck exactly. On top of that, all 720 orderings of a six-slide deck are checked through diff-and-apply, which is what caught the multi-slide reordering bug that final-index moves produce.
The Google backend is covered by asserting the generated batchUpdate requests
against the documented API shapes, and by mapping a recorded presentations.get
payload into the model.
The write path against a live Google deck is the one thing the suite cannot reach: it needs an OAuth grant, which CI does not have. Everything up to the HTTP call is tested; the HTTP call itself is not.
Status
v0.1. Slide and element structure, text, tables, images, notes, ordering and
skip state. Not yet: text styling (fonts, colours, sizes), speaker-note
formatting, charts, and relayout of an existing slide — the Slides API has no
request for that last one, so :layout only affects slides created in the same
session, and says so.
License
MIT
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Build decks in your own brand, from the AI agent you already use. Then edit them yourself.
Create polished slide decks from text or YouTube links in seconds. Fetch video transcripts to tran…
Create and improve live, shared interactive documents with your agent.
1Connect AI assistants to Google Sheets through controlled tools for reading and updating rows.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceEnables AI assistants to control Google Workspace services (Slides, Sheets, Drive) through natural language.637ISC
- AlicenseNot gradedqualityBmaintenanceEnables creating, reading, and modifying Google Slides presentations programmatically via natural language through the Model Context Protocol.32185MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to control Google Workspace services (Slides, Docs, Sheets, Drive) with granular tools and provides a universal CLI escape hatch for all Workspace APIs.MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with PowerPoint presentations through natural language commands, supporting reading, editing, slide management, and automated speaker notes processing.MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/jaisharmz/slidevim'
If you have feedback or need assistance with the MCP directory API, please join our Discord server