Skip to content

Getting Started

One TypeScript file. Three interfaces. No boilerplate.

Photon turns a .photon.ts file into a CLI tool, a web dashboard, and an MCP server for AI agents. You write methods. Photon handles the rest.


Install

bash
bun add -g @portel/photon

Or run it without a global install:

bash
bunx @portel/photon --help

# pnpm alternative
pnpm dlx @portel/photon --help

Requires Node.js 20+. Bun is the preferred package manager; pnpm dlx is also supported for one-off runs. TypeScript is compiled internally — no tsconfig.json needed.


Where things live

Photon keeps a tight, predictable layout. Knowing it up front saves a lot of confusion later.

LocationWhat lives there
./*.photon.ts (anywhere)Your photon source. Run photon from this directory and it picks up every .photon.ts file alongside.
~/.photon/Global photons. Anything dropped here is auto-discovered by the daemon, regardless of cwd. Created by photon new --global.
~/.photon/state/<photon>/<instance>-settings.jsonPersisted user settings (the runtime values that win over your in-source defaults).
~/.photon/.data/Daemon-wide runtime data: log, socket, base directory registry, compile cache.
<project>/.data/<photon>/Per-photon state, memory, scheduled jobs. Lives next to your source file when you run from a project dir.

The full layout, including marketplaces and caches, is documented in How Photon Works.


The three-command path

If you just want an MCP server connected to Claude Desktop, these three commands are all you need:

bash
photon new my-tool                  # Scaffolds ./my-tool.photon.ts in the current directory
photon mcp install my-tool          # Registers it in Claude Desktop's config
# Restart Claude Desktop. Your tool is live.

photon new is a shortcut for photon maker new. The scaffold lives in your current directory so you can open it in your editor, commit it to git, or share it with teammates like any other source file. Use --global if you'd rather drop it into ~/.photon/ for the daemon to auto-discover.

The rest of this page is about writing a photon from scratch so you can see how the same TypeScript class becomes three different interfaces.


Your First Photon (from scratch)

Create a file called todo.photon.ts in any directory:

typescript
// todo.photon.ts
export default class Todo {
  private items: { task: string; done: boolean }[] = [];

  /**
   * Add a task to the list
   */
  add(params: { task: string }) {
    this.items.push({ task: params.task, done: false });
    return this.items;
  }

  /**
   * List all tasks
   * @format table
   */
  list() {
    return this.items;
  }
}

That's 18 lines. Now run it three ways:

Web UI (Beam)

bash
photon

Open the browser. Your todo photon appears in the sidebar. Click add, type a task, hit execute. Click list to see a formatted table.

Beam: auto-generated form and result

CLI

bash
photon cli todo add --task "Buy milk"
photon cli todo list

Same logic, terminal output. The @format table tag renders as an ASCII table in the CLI.

MCP Server (for AI)

bash
photon mcp todo

Paste the connection config into Claude Desktop, Cursor, or any MCP-compatible client. The AI sees your methods as tools, with descriptions from your JSDoc comments.


Make It Visual

Photon auto-renders return values based on their shape. Add @format tags to control how:

typescript
/**
 * Task completion stats
 * @format chart:pie
 */
stats() {
  const done = this.items.filter(i => i.done).length;
  const pending = this.items.length - done;
  return [
    { status: "Done", count: done },
    { status: "Pending", count: pending },
  ];
}

The same data renders as a pie chart in Beam, structured JSON for the AI, and a text table in the CLI.

Other formats you can try right now:

TagWhat it does
@format tableSortable table with expandable rows
@format chart:barBar chart
@format metricBig number with label and trend
@format ringCircular progress indicator
@format markdownRendered markdown
@format mermaidMermaid diagrams

See the full catalog in the Output Formats guide.


Add Persistence

Right now, restarting the server loses all data. Add @stateful to persist automatically:

typescript
/**
 * A persistent todo list
 * @stateful
 */
export default class Todo {
  private items: { task: string; done: boolean }[] = [];

  // ... same methods as before
}

That's it. The items array is now saved to disk after every method call and restored on startup. No database, no config — just a tag.


Add Comments, Get Superpowers

JSDoc comments aren't just documentation — they drive the AI's understanding, the CLI's help text, and the web form's labels and validation:

typescript
/**
 * Add a task to the list
 * @param task What needs to be done {@example Buy groceries}
 */
add(params: { task: string }) {
  // ...
}

Now:

  • Beam shows "What needs to be done" as the field label, with "Buy groceries" as placeholder
  • CLI shows it in --help output
  • AI reads it to understand when and how to call the tool

The more you express in comments and types, the more all three surfaces derive. One annotation, three consumers.


What's Next?

You've built a working photon with persistence and visual output. Here's where to go from here:

Want to...Read
Understand the mental modelCore Concepts
See all visual formatsOutput Formats
Build custom HTML interfacesCustom UI Guide
Add environment variables and configComplete Guide: Constructor Configuration
Add OAuth authenticationAuthentication Guide
Deploy to productionDeployment Guide
Connect to other MCP serversMCP Dependencies
Browse everythingComplete Developer Guide

Released under the MIT License.