Photon turns a single TypeScript file into: - MCP server for AI agents - CLI tool for automation - Web interface for humans Photon is free and open source software released under the MIT license. Interfaces are optional. Intent is mandatory. That's a complete photon. From this single file you get: No decorators. No registration. No server boilerplate. Just define the intent. Photon handles the res
Add this skill
npx mdskills install portel-dev/photonComprehensive MCP server framework with excellent documentation and rich feature set
12<div align="center">3<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/photon-logo.png" alt="Photon" width="500">4</div>56[](https://www.npmjs.com/package/@portel/photon)7[](https://www.npmjs.com/package/@portel/photon)8[](https://github.com/portel-dev/photon/blob/main/LICENSE)9[](https://www.typescriptlang.org)10[](https://nodejs.org)11[](https://modelcontextprotocol.io)1213### Define intent once. Deliver everywhere.1415Photon turns a single TypeScript file into:1617- **MCP server** for AI agents18- **CLI tool** for automation19- **Web interface** for humans2021Photon is free and open source software released under the [MIT license](./LICENSE).2223*Interfaces are optional. Intent is mandatory.*2425---2627## One definition. Multiple interfaces.2829<div align="center">30<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/photon-ecosystem.png" alt="Photon: one file, three surfaces" width="100%">31</div>3233---3435### Example3637```typescript38// hello.photon.ts39export default class Hello {40 greet(name: string) {41 return `Hello, ${name}!`;42 }43}44```4546That's a complete photon. From this single file you get:4748```49$ photon cli hello greet --name Ada # CLI50$ photon # Web UI at localhost:300851$ photon mcp hello # MCP server for Claude, Cursor, etc.52```5354No decorators. No registration. No server boilerplate.55Just define the intent. Photon handles the rest.5657<div align="center">58<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-1.png" alt="One file, three interfaces" width="100%">59</div>6061---6263## Why Photon Exists6465Most software is built around interfaces: web apps, CLI tools, APIs, and now MCP servers for AI agents. But the underlying logic is often the same.6667Photon starts from a different place: capture the intent once in a TypeScript file and let the system expose it through multiple interfaces — CLI tools, web interfaces, and MCP servers.6869One definition. Multiple surfaces.7071---7273### Quick Start7475From zero to an MCP server connected to Claude Desktop in three commands:7677```bash78npm install -g @portel/photon79photon new my-tool # Scaffolds ./my-tool.photon.ts in your CWD80photon mcp install my-tool # Registers it in Claude Desktop's config81# Restart Claude Desktop. Your tool is live.82```8384Prefer the web dashboard? Skip step 3 and run `photon` instead — it opens Beam, the auto-generated UI.8586Or try without installing globally:8788```bash89npx @portel/photon new my-tool90npx @portel/photon mcp install my-tool91```9293> Requires [Node.js 20+](https://nodejs.org). TypeScript is compiled internally; no `tsconfig.json` needed.9495<div align="center">9697<a href="https://www.youtube.com/watch?v=FI0M8s6ZKv4">98 <img src="https://img.youtube.com/vi/FI0M8s6ZKv4/maxresdefault.jpg" alt="Watch: Why Photon? (2 min)" width="100%">99</a>100101</div>102103---104105### How It Works106107You write a TypeScript class. Methods are your capabilities. Types describe what's valid. Comments explain the intent. Photon reads all of it and generates three interfaces from one file. Same logic. Same validation. Same data.108109```110analytics.photon.ts → Web UI (Beam) · CLI · MCP Server for AI111```112113The more you express, the more Photon derives:114115| What you write | What Photon derives |116|---|---|117| Method signatures | Tool definitions: names, inputs, outputs |118| Type annotations | Input validation rules, UI field types |119| JSDoc comments | Documentation for AI clients and human users |120| Constructor parameters | Config UI, environment variable mapping |121| `@tags` | Validation, formatting, scheduling, webhooks |122123When you add a `@param city {@pattern ^[a-zA-Z\s]+$}` annotation, Beam validates it in the form, the CLI validates it before running, and the MCP schema enforces it for the AI. One annotation. Three consumers.124125---126127## Beam: Human Exploration128129Beam is the web dashboard. Every photon becomes an interactive form automatically. Run `photon`. That's the whole command.130131<div align="center">132<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/beam-dashboard.png" alt="Beam Dashboard" width="100%">133</div>134135The UI is **fully auto-generated** from your method signatures: field types, validation, defaults, layouts. You never write frontend code. When you add a `{@choice a,b,c}` tag to a parameter, Beam renders a dropdown. When you mark a string as `{@format email}`, the field validates email format. The UI evolves as your code does.136137When forms aren't the right interface for what you're building, you can replace Beam's auto-generated view with your own HTML. A global named after your photon is auto-injected (e.g., `analytics.onResult(data => ...)`) — no framework required.138139> Custom UIs follow the [MCP Apps Extension (SEP-1865)](https://github.com/nicolo-ribaudo/modelcontextprotocol/blob/nicolo/sep-1865/docs/specification/draft/extensions/apps.mdx) standard and work across compatible hosts. See the [Custom UI Guide](./docs/guides/CUSTOM-UI.md).140141---142143## AI Agents: Machine Invocation144145```bash146photon info analytics --mcp147```148149```json150{151 "mcpServers": {152 "analytics": {153 "command": "photon",154 "args": ["mcp", "analytics"]155 }156 }157}158```159160Paste into your AI client's config. Your photon is now an MCP server. Claude can call your methods. Cursor can call your methods. Any MCP-compatible host can call your methods.161162The AI sees the same thing a human sees in Beam: the method names, the parameter descriptions from your JSDoc, the validation rules from your types. The JSDoc comment you wrote to document the tool for yourself is what Claude reads to decide when and how to call it.163164The MCP tools themselves work with [Claude Desktop](https://claude.ai/download), [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://cursor.com), and any MCP-compatible client.165166When your photon has a custom UI, clients that support the [MCP Apps Extension](https://github.com/nicolo-ribaudo/modelcontextprotocol/blob/nicolo/sep-1865/docs/specification/draft/extensions/apps.mdx) render it natively, no separate app needed. The photon below is running inside Claude Desktop, same UI, same data as Beam.167168<div align="center">169<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/claude-desktop.png" alt="Photon running as an MCP App with custom UI inside Claude Desktop" width="100%">170</div>171172---173174## How a Photon Evolves175176Here is how a photon grows. Each step adds one thing and gets multiple capabilities from it.177178### Add comments: AI understands your intent179180```typescript181/**182 * Weather - Check weather forecasts worldwide183 */184export default class Weather {185 /**186 * Get the weather forecast for a city187 * @param city City name (e.g., "London")188 */189 async forecast(params: { city: string }) { ... }190}191```192193The class description becomes how AI clients introduce the tool to users. The `@param` description is what the AI reads before deciding what value to pass. Same comments. Human help text and AI contract at once.194195<div align="center">196<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-2.png" alt="Step 2" width="100%">197</div>198199### Add a constructor: configuration appears200201```typescript202export default class Weather {203 constructor(204 private apiKey: string,205 private units: string = 'metric'206 ) {}207208 async forecast(params: { city: string }) {209 const res = await fetch(`...?appid=${this.apiKey}&units=${this.units}`);210 return await res.json();211 }212}213```214215`apiKey` becomes a password field in the Beam settings panel and maps to the `WEATHER_API_KEY` environment variable. `units` gets a text input with `'metric'` pre-filled. You declared what you need. Photon built the configuration surface.216217<div align="center">218<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-3.png" alt="Step 3" width="100%">219</div>220221### Add tags: behavior extends across all surfaces222223```typescript224/**225 * @dependencies node-fetch@^3.0.0226 */227export default class Weather {228 /**229 * @param city City name {@example London} {@pattern ^[a-zA-Z\s]+$}230 * @param days Number of days {@min 1} {@max 7}231 * @format table232 */233 async forecast(params: { city: string; days?: number }) { ... }234}235```236237`@dependencies` installs `node-fetch` automatically on first run, no `npm install` needed. The `{@pattern}` validates in the form, the CLI, and the MCP schema simultaneously. `days` becomes a number spinner with bounds. `@format table` renders the result as a table in Beam. One annotation, three surfaces.238239<div align="center">240<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-4.png" alt="Step 4" width="100%">241</div>242243### System CLI dependencies244245If your photon wraps a command-line tool, declare it and Photon enforces it at load time:246247```typescript248/**249 * @cli ffmpeg - https://ffmpeg.org/download.html250 */251export default class VideoProcessor {252 async convert({ input, format }: { input: string; format: string }) {253 // ffmpeg is guaranteed to exist when this runs254 }255}256```257258<div align="center">259<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/readme-step-5.png" alt="Step 5" width="100%">260</div>261262---263264## What Comes for Free265266Things you don't build because Photon handles them:267268| | |269|---|---|270| **Auto-UI** | Forms, field types, validation, layouts generated from your signatures |271| **Stateful instances** | Multiple named instances of the same photon, each with isolated state |272| **Persistent memory** | `this.memory` gives your photon per-instance key-value storage, no database needed |273| **Scheduled execution** | `@scheduled` runs any method on a cron schedule |274| **Webhooks** | `@webhook` exposes any method as an HTTP endpoint |275| **OAuth (client)** | Built-in OAuth 2.0 flows for Google, GitHub, Microsoft |276| **OAuth Authorization Server** | Issue tokens to MCP clients yourself: CIMD + DCR, PKCE, OIDC id_token, RFC 8693 token exchange |277| **SQLite persistence** | Audit log, execution history, and OAuth grants survive daemon restart (bun:sqlite or better-sqlite3) |278| **Daemon ops** | `photon ps` lists and controls scheduled jobs, webhooks, and live sessions |279| **Distributed locks** | `@locked` serializes access: one caller at a time, across processes |280| **Cross-photon calls** | `this.call()` invokes another photon's methods |281| **Real-time events** | `this.emit()` fires named events to the browser UI with zero wiring |282| **Live rendering** | `this.render()` pushes formatted output to CLI and Beam in real time |283| **Delegated LLM** | `this.sample()` asks the driving agent's model to generate text — no API key, agent pays |284| **Inline confirm / input** | `this.confirm()` and `this.elicit()` route through the client's native UI (Beam dialog, Claude prompt) |285| **Scoped remote access** | `photon claim` generates a short-lived code to scope a remote MCP session to one directory |286| **Standalone binaries** | `photon build` compiles any photon to a single executable via Bun |287| **Dependency management** | `@dependencies` auto-installs npm packages on first run |288289---290291## Coordination: Locks + Events292293Two primitives. Together they unlock a class of things that are surprisingly hard to build today.294295**Locks** serialize access. When a method is marked `@locked`, only one caller can execute at a time, whether that caller is a human in Beam, a CLI script, or an AI agent. Everyone else waits their turn.296297**Events** push state changes to any browser UI in real time. `this.emit({ event: 'boardUpdated', data: board })` on the server becomes `chess.onBoardUpdated(handler)` in your custom UI — named after your photon file. No WebSockets to configure. No polling. Events are delivered via SSE through the MCP Streamable HTTP transport.298299Together: **turn-based coordination with live state**.300301```typescript302export default class Chess {303 /** Make a move. Locks ensure human and AI alternate turns. */304 /** @locked */305 async move(params: { from: string; to: string }) {306 const result = await this.applyMove(params.from, params.to);307308 // Browser UI updates instantly, no polling needed309 this.emit({ event: 'boardUpdated', data: result.board });310 this.emit({ event: 'turnChanged', data: { next: result.nextPlayer } });311312 return result;313 }314}315```316317```javascript318// In your custom UI (ui/chess.html)319// The global `chess` is auto-injected, named after your photon file320chess.onBoardUpdated(board => renderBoard(board));321chess.onTurnChanged(({ next }) => showTurn(next));322323// Call server methods directly324chess.move({ from: 'e2', to: 'e4' });325```326327A human moves through Beam. Claude is configured with the MCP server. The lock ensures they truly alternate. Events keep the board live on both sides. That's a fully functional turn-based chess game, human vs AI, in about 50 lines of application logic.328329The same pattern applies beyond games: approval workflows where a human reviews before AI continues, collaborative tools where edits from any source appear instantly, simulations where steps must execute in strict sequence, any system where **who acts next matters**.330331---332333## MCP Primitives on `this`334335The MCP protocol's user-facing primitives are surfaced as plain methods336on every photon instance — no decorators, no capability flags, no SDK337imports. The runtime routes each call through whichever surface the338request arrived on (Beam, Claude Desktop, Cursor, CLI).339340```typescript341export default class Editor {342 async summarize(params: { text: string }) {343 // Ask the driving agent's LLM. No API key. Agent pays.344 return await this.sample({345 prompt: `Summarize in one sentence:\n\n${params.text}`,346 maxTokens: 128,347 });348 }349350 async deploy() {351 if (!(await this.confirm('Ship to production?'))) return;352 const env = await this.elicit<string>({353 ask: 'select',354 message: 'Which environment?',355 options: ['staging', 'prod'],356 });357 await this.run(env);358 }359}360```361362| Primitive | What it does |363|---|---|364| `await this.sample({ prompt })` | Delegates LLM generation to the caller's model via MCP sampling |365| `await this.confirm(question)` | Yes/no prompt — returns `boolean` |366| `await this.elicit(params)` | Arbitrary input (text, select, form, file, etc.) |367| `this.status(msg)` / `this.progress(v)` | Live feedback during long work |368369Full reference: [`docs/reference/MCP-PRIMITIVES.md`](docs/reference/MCP-PRIMITIVES.md).370371---372373## Remote Access: Claim Codes374375By default every installed photon is visible to every connected MCP376client. When you want to pair a *remote* agent with a *subset* of your377photons — your phone driving Beam, a teammate reviewing one project,378a CI agent scoped to a single directory — generate a claim code:379380```bash381$ photon claim --scope /workspace/proj --ttl 4h --label "phone"382✓ Claim code: R3K-9QZ383 Scope: /workspace/proj384 Expires in: 4h385```386387The remote client presents the code as the `Mcp-Claim-Code` header on388its MCP session. `tools/list` then only exposes photons whose source389lives under that directory. Sessions without a code keep full access —390the feature is strictly opt-in.391392Full reference: [`docs/reference/CLAIM-CODES.md`](docs/reference/CLAIM-CODES.md).393394---395396## Marketplace39739832 photons ready to install: databases, APIs, developer tools, and more.399400<div align="center">401<img src="https://raw.githubusercontent.com/portel-dev/photon/main/assets/beam-marketplace.png" alt="Marketplace" width="100%">402</div>403404```bash405photon search postgres406photon add postgres407```408409You can also install directly from any GitHub repository using qualified refs:410411```bash412photon add owner/repo/photon-name413```414415Browse the full catalog in the [official photons repository](https://github.com/portel-dev/photons). You can also host a private marketplace for your team: internal tools that stay off the public internet.416417---418419## Commands420421```bash422# Run423photon # Open Beam UI424photon mcp <name> # Run as MCP server425photon mcp <name> --dev # MCP server with hot reload426photon cli <name> [method] # Run as CLI tool427428# Install from GitHub429photon beam owner/repo/name # Install & open in Beam430photon cli owner/repo/name method # Install & run via CLI431432# Create433photon maker new <name> # Scaffold a new photon434435# Build436photon build <name> # Compile to standalone binary437photon build <name> --with-app # Include Beam UI in binary438439# Manage440photon info # List all photons441photon info <name> --mcp # Get MCP client config442photon maker validate <name> # Check for errors443444# Marketplace445photon add <name> # Install photon446photon search <query> # Search marketplace447photon upgrade # Upgrade all448449# Ops450photon doctor # Diagnose environment451photon test # Run tests452photon ps # Observe & control scheduled jobs, webhooks, sessions453```454455### `photon ps`: scheduled jobs, webhooks, and sessions456457`photon ps` is the operator surface for the daemon. Without arguments458it prints a four-section snapshot — ACTIVE schedules, DECLARED-but-459not-enrolled, WEBHOOKS, and ACTIVE SESSIONS.460461```bash462photon ps # full snapshot463photon ps --json # structured output for scripts464photon ps --type active # one section only465photon ps --base ~/Projects/kith # filter to one PHOTON_DIR466```467468**Two-step model.** A `@scheduled` annotation in source is **DECLARED**469until enrolled. Enrollment is per-machine, persistent, and explicit:470471```bash472photon ps enable newsletter:sendDigest # DECLARED → ACTIVE473photon ps disable newsletter:sendDigest # ACTIVE → suppressed (survives restart)474photon ps pause newsletter:sendDigest # stop firing without removing enrollment475photon ps resume newsletter:sendDigest # undo pause476photon ps history newsletter:sendDigest # last 20 firings: timestamp, status, error477```478479For manual cron schedules without a `@scheduled` tag, use the Beam Pulse480panel ("Add schedule") or call `this.schedule.create()` from photon code.481482`this.schedule.create()` (programmatic schedules) skips DECLARED and483goes straight to ACTIVE. See484[`docs/GUIDE.md#scheduling`](docs/GUIDE.md#scheduling-scheduled-thisschedule-photon-ps)485for the full reference, the daemon state layout, and `.photon-no-host`486for multi-host setups.487488### Install from GitHub489490Use qualified refs to install and run photons directly from any GitHub repository:491492```bash493photon beam Arul-/photons/claw # Install from GitHub, open in Beam494photon cli Arul-/photons/todo add # Install from GitHub, run method495```496497The format is `owner/repo/photon-name`. Transitive `@photon` dependencies from the same repo are resolved automatically.498499### Compile to Binary500501Build standalone executables from any photon — no Node.js required on the target machine:502503```bash504photon build my-tool # Binary for current platform505photon build my-tool -t bun-linux-x64 # Cross-compile for Linux506photon build my-tool --with-app # Embed Beam UI as a desktop app507```508509Uses Bun's compiler under the hood. The binary bundles the photon, its `@dependencies`, and transitive `@photon` deps into a single file.510511---512513## Tag Reference514515| Tag | Where | What it does |516|---|---|---|517| `@dependencies` | Class | Auto-install npm packages on first run |518| `@cli` | Class | Declare system CLI dependencies, checked at load time |519| `@format` | Method | Result rendering (table, list, markdown, code, etc.) |520| `@param ... {@choice a,b,c}` | Param | Dropdown selection in Beam |521| `@param ... {@choice-from method}` | Param | Dynamic dropdown populated from another method's return value |522| `@param ... {@format email}` | Param | Input validation and field type |523| `@param ... {@min N} {@max N}` | Param | Numeric range constraints |524| `@ui` | Class/Method | Link a custom HTML template |525| `@expose` | Method | Auto-bind to `POST /api/<kebab>` for SPA fetch (`public` skips the SameSite gate) |526| `@webhook` | Method | Expose as HTTP endpoint |527| `@scheduled` | Method | Run on a cron schedule |528| `@locked` | Method | Distributed lock across processes |529| `@autorun` | Method | Auto-execute when selected in Beam |530| `@mcp` | Class | Inject another MCP server as a dependency |531| `@icon` | Class/Method | Set emoji icon |532533> See the full [Tag Reference](./docs/reference/DOCBLOCK-TAGS.md) for all 30+ tags with examples.534535---536537## Documentation538539**Start here:**540541| Guide | |542|---|---|543| [Getting Started](./docs/getting-started.md) | Install, build, and run your first photon in 5 minutes |544| [Core Concepts](./docs/concepts.md) | The 5 ideas behind Photon |545| [Output Formats](./docs/formats.md) | Visual gallery of every `@format` type |546| [Troubleshooting](./docs/TROUBLESHOOTING.md) | Common issues and solutions |547548**Go deeper:**549550| Topic | |551|---|---|552| [Custom UI](./docs/guides/CUSTOM-UI.md) | Build rich interactive interfaces with the photon bridge API |553| [OAuth](./docs/guides/AUTH.md) | Built-in OAuth 2.0 with Google, GitHub, Microsoft |554| [MCP Client Registration](./docs/guides/mcp-client-registration.md) | Register MCP clients with Photon's AS via CIMD or DCR |555| [Observability](./docs/guides/observability.md) | OpenTelemetry traces, metrics, logs, and structured errors |556| [Protocol Features](./docs/guides/PROTOCOL-FEATURES.md) | Capability handshake, structured errors, trace correlation |557| [Daemon Pub/Sub](./docs/internals/DAEMON-PUBSUB.md) | Real-time cross-process messaging |558| [Webhooks](./docs/reference/WEBHOOKS.md) | HTTP endpoints for external services |559| [Locks](./docs/reference/LOCKS.md) | Distributed locks for exclusive access |560| [Advanced Patterns](./docs/guides/ADVANCED.md) | Lifecycle hooks, dependency injection, interactive workflows |561| [Deployment](./docs/guides/DEPLOYMENT.md) | Docker, Cloudflare Workers, AWS Lambda, Systemd |562563**Operate:**564565| Topic | |566|---|---|567| [Security](./SECURITY.md) | Best practices and audit checklist |568| [Marketplace Publishing](./docs/guides/MARKETPLACE-PUBLISHING.md) | Create and share team marketplaces |569| [Best Practices](./docs/guides/BEST-PRACTICES.md) | Patterns for production photons |570571**Reference:** [Complete Developer Guide](./docs/GUIDE.md) · [Tag Reference](./docs/reference/DOCBLOCK-TAGS.md) · [Naming Conventions](./docs/guides/NAMING-CONVENTIONS.md) · [Architecture](./docs/internals/ARCHITECTURE.md) · [OAuth Authorization Server](./docs/internals/OAUTH-AUTHORIZATION-SERVER.md) · [Lifecycle & Ingress](./docs/internals/LIFECYCLE-AND-INGRESS.md) · [PHOTON_DIR & Namespace](./docs/internals/PHOTON-DIR-AND-NAMESPACE.md) · [Changelog](./CHANGELOG.md) · [Contributing](./CONTRIBUTING.md)572573---574575## Open Source576577Photon is free and open source under the [MIT license](./LICENSE).578579The project is still evolving and contributions are welcome.580581- Star the repository if the idea resonates582- [Report issues](https://github.com/portel-dev/photon/issues)583- [Contribute improvements or examples](./CONTRIBUTING.md)584
Full transparency — inspect the skill content before installing.