Your next power user can’t see the screen
Accessibility lets people with disabilities use an app that was designed for a mouse and a pair of eyes. MCP lets an agent use it for a power user who wants a thousand photos done at once. Both ask the app the same questions.
Two users the design leaves out
When I design a screen in LightPhotos, I picture one person. They sit in front of a laptop, they move a mouse, and they look at the photo. Almost every app is designed for that person, and that is correct. Most users are that person.
Accessibility is the work for the people who are not. A blind photographer cannot see the grid, so a screen reader must read it to them. A person with a tremor cannot drag a slider to an exact value, so the keyboard must do it. Accessibility removes the requirement for a particular body. The app still does the same things, and more people can tell it to.
The agentic experience is for a different person that the design also leaves out: the power user. After a wedding, I have 1,200 photos. I want to say “reject every photo where the eyes are closed, rate the sharpest of each burst three stars, and give the rest the warm preset,” and then go and eat dinner. No screen layout makes that fast, because the limit is my hand. One person cannot click 1,200 times without mistakes. An agent can.
So accessibility lets the app do everything for people who cannot use the default input. MCP lets the app do more than one person could do with that input. These two goals look opposite, but they need the same changes to the app. A blind photographer and an agent have one thing in common. Neither of them can see the screen.
The same three questions
A screen reader cannot see pixels. It asks the operating system about the app, through the accessibility tree, and it asks three questions:
- What is on the screen? Each control has a name, a role and a value.
- What can I do here? Each control lists its actions, such as press or increment.
- What happened? The app announces a change, so the user knows the action worked.
LightPhotos answers these questions badly. Its interface is drawn with egui, which paints every button, slider and thumbnail into one GPU frame, 60 times a second. The operating system sees a window full of pixels. egui can describe some widgets through AccessKit, but a photo grid and a develop panel give it very little to say. For a screen reader, most of LightPhotos is one large image. The only way to read it is to capture the screen and guess what the pixels mean.
An agent has the same problem. It can take a screenshot and look at
it, and the MCP server has a screenshot tool for that.
But a screenshot is a slow, lossy way to learn that photo 412 has
three stars. An agent that uses MCP asks the same three questions
as a screen reader, and it needs the answers as text. In
LightPhotos, they are four tools. list_commands answers “what can I
do?” get_state and list_photos answer
“what is on the screen?” run_command does
one thing, and its reply says what happened, or why it failed.
An app that answers these questions well for a screen reader is most of the way to an app that answers them for an agent. An app that answers them for neither is a set of pixels that only one kind of person can use.
The screen reader asks the window for its controls. egui gives one image. The reader cannot find photo 3, so it cannot rate it.
What I gave up, and what I got back
In Why Rust, I wrote that I accepted weaker accessibility. Browsers give screen reader support for free. The Rust UI libraries, egui among them, are far behind. In my plan, I wrote that keyboard support is the part of that problem that I can fix myself. So every feature will work without the mouse.
To make every feature work from the keyboard, I had to give every
feature a name. That became src/command.rs: one Rust
enum, Command, with a variant for each action. Each
variant has a fixed id, such as photo.rate, and a params
struct, such as {"stars": 4}. The keyboard and the menu
bar do not call the editor directly. They make a
Command and dispatch it.
When I added the MCP server, most of the work was already done.
lightphotos-mcp reads the same enum.
list_commands prints each id with an example of its
params. run_command parses the JSON into a
Command and runs it. I did not write a second API for
agents. The agent uses the list of actions that I wrote for the
keyboard.
The server runs in two modes. With --folder, it needs
no app. It writes ratings, flags, labels and edits into the
sidecars next to the photos, and render returns a photo
with its edits as a JPEG, so the agent can look at its own work.
With --attach, it drives a running app, and
screenshot returns the window. In the browser, the web
app registers the same tools with WebMCP, so an agent in the browser
uses the open tab.
A feature that only the mouse can do
The rule I follow now is this: if a feature has no command, it does not exist for a keyboard user or for an agent. Brush masks showed me where I had broken the rule. A brush stroke was a drag with the mouse, and nothing else. A screen reader user could not paint one. An agent could not paint one either.
To give brush masks to agents, I had to describe a stroke in words:
mask.add now takes a list of points. That is also the
description an assistive tool needs. When I fixed the feature for
the power user, I fixed it for the user with a disability too. The
reverse is also true. When you make a feature accessible, you have
usually also made it available to agents.
The same is true for errors. A sighted user sees a disabled button
and understands. A screen reader user needs the reason in words. An
agent needs it in words too, and it also needs the next step. When a
LightPhotos tool fails, its reply says what to do next. An unknown
id gets “Call list_commands to see every
id.” A
good error for an agent is a good error for a person.
Where the comparison breaks
The two are not equal. Accessibility is about whether a person can use the app at all. In many countries it is the law. MCP is about speed for people who can already use the app. I am not saying that an MCP server is a substitute for screen reader support. It is not. A blind user of LightPhotos today still gets a window of egui pixels, and MCP does not change that.
An agent can also do damage at a scale a person cannot. One wrong
instruction can change 1,200 sidecars. So the agent path has guards
that a screen reader does not need. In headless mode, the server
checks every photo in a batch before it writes any of them. It
refuses a symlink that leads out of the folder. In attached mode,
the app requires a token, and the server reads the token only from
the environment, so ps does not show it to other users.
What this changes
A feature in LightPhotos is now finished when it has three things:
a control on the screen, a Command with a name and
params, and state that get_state or
list_photos can report. The second and third are what
the keyboard, the menu bar and the agent use. They are also the
text a screen reader would need. When egui can expose more than
pixels, the names and the state will already exist.
I used to think of accessibility as work for a small group of users. Now I think of it as the work that makes the app readable by anything other than a pair of eyes. A person with a disability was the first user who needed that. An agent is the second. The README has the steps to connect an agent. If a feature has no command yet, open an issue.