npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@portel/photon

v1.13.0

Published

You focus on the business logic. We'll enable the rest. Build MCP servers and CLI tools in a single TypeScript file.

Readme

npm version npm downloads License: MIT TypeScript Node MCP

Define intent once. Deliver everywhere.

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.


One definition. Multiple interfaces.


Example

// hello.photon.ts
export default class Hello {
  greet(name: string) {
    return `Hello, ${name}!`;
  }
}

That's a complete photon. From this single file you get:

$ photon cli hello greet --name Ada        # CLI
$ photon                                    # Web UI at localhost:3008
$ photon mcp hello                          # MCP server for Claude, Cursor, etc.

No decorators. No registration. No server boilerplate. Just define the intent. Photon handles the rest.


Why Photon Exists

Most 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.

Photon 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.

One definition. Multiple surfaces.


Quick Start

npm install -g @portel/photon
photon maker new my-tool      # Scaffold a new photon
photon                        # Open Beam, the web UI

Or try without installing:

npx @portel/photon maker new my-tool
npx @portel/photon

Requires Node.js 20+. TypeScript is compiled internally; no tsconfig.json needed.


How It Works

You 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.

analytics.photon.ts  →  Web UI (Beam)  ·  CLI  ·  MCP Server for AI

The more you express, the more Photon derives:

| What you write | What Photon derives | |---|---| | Method signatures | Tool definitions: names, inputs, outputs | | Type annotations | Input validation rules, UI field types | | JSDoc comments | Documentation for AI clients and human users | | Constructor parameters | Config UI, environment variable mapping | | @tags | Validation, formatting, scheduling, webhooks |

When 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.


Beam: Human Exploration

Beam is the web dashboard. Every photon becomes an interactive form automatically. Run photon. That's the whole command.

The 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.

When 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.

Custom UIs follow the MCP Apps Extension (SEP-1865) standard and work across compatible hosts. See the Custom UI Guide.


AI Agents: Machine Invocation

photon info analytics --mcp
{
  "mcpServers": {
    "analytics": {
      "command": "photon",
      "args": ["mcp", "analytics"]
    }
  }
}

Paste 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.

The 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.

The MCP tools themselves work with Claude Desktop, Claude Code, Cursor, and any MCP-compatible client.

When your photon has a custom UI, clients that support the MCP Apps Extension render it natively, no separate app needed. The photon below is running inside Claude Desktop, same UI, same data as Beam.


How a Photon Evolves

Here is how a photon grows. Each step adds one thing and gets multiple capabilities from it.

Add comments: AI understands your intent

/**
 * Weather - Check weather forecasts worldwide
 */
export default class Weather {
  /**
   * Get the weather forecast for a city
   * @param city City name (e.g., "London")
   */
  async forecast(params: { city: string }) { ... }
}

The 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.

Add a constructor: configuration appears

export default class Weather {
  constructor(
    private apiKey: string,
    private units: string = 'metric'
  ) {}

  async forecast(params: { city: string }) {
    const res = await fetch(`...?appid=${this.apiKey}&units=${this.units}`);
    return await res.json();
  }
}

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.

Add tags: behavior extends across all surfaces

/**
 * @dependencies node-fetch@^3.0.0
 */
export default class Weather {
  /**
   * @param city City name {@example London} {@pattern ^[a-zA-Z\s]+$}
   * @param days Number of days {@min 1} {@max 7}
   * @format table
   */
  async forecast(params: { city: string; days?: number }) { ... }
}

@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.

System CLI dependencies

If your photon wraps a command-line tool, declare it and Photon enforces it at load time:

/**
 * @cli ffmpeg - https://ffmpeg.org/download.html
 */
export default class VideoProcessor {
  async convert({ input, format }: { input: string; format: string }) {
    // ffmpeg is guaranteed to exist when this runs
  }
}

What Comes for Free

Things you don't build because Photon handles them:

| | | |---|---| | Auto-UI | Forms, field types, validation, layouts generated from your signatures | | Stateful instances | Multiple named instances of the same photon, each with isolated state | | Persistent memory | this.memory gives your photon per-instance key-value storage, no database needed | | Scheduled execution | @scheduled runs any method on a cron schedule | | Webhooks | @webhook exposes any method as an HTTP endpoint | | OAuth | Built-in OAuth 2.0 flows for Google, GitHub, Microsoft | | Distributed locks | @locked serializes access: one caller at a time, across processes | | Cross-photon calls | this.call() invokes another photon's methods | | Real-time events | this.emit() fires named events to the browser UI with zero wiring | | Dependency management | @dependencies auto-installs npm packages on first run |


Coordination: Locks + Events

Two primitives. Together they unlock a class of things that are surprisingly hard to build today.

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.

Events push state changes to any browser UI in real time. this.emit('boardUpdated', data) 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.

Together: turn-based coordination with live state.

export default class Chess {
  /** Make a move. Locks ensure human and AI alternate turns. */
  /** @locked */
  async move(params: { from: string; to: string }) {
    const result = await this.applyMove(params.from, params.to);

    // Browser UI updates instantly, no polling needed
    this.emit('boardUpdated', result.board);
    this.emit('turnChanged', { next: result.nextPlayer });

    return result;
  }
}
// In your custom UI (ui/chess.html)
// The global `chess` is auto-injected, named after your photon file
chess.onBoardUpdated(board => renderBoard(board));
chess.onTurnChanged(({ next }) => showTurn(next));

// Call server methods directly
chess.move({ from: 'e2', to: 'e4' });

A 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.

The 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.


Marketplace

32 photons ready to install: databases, APIs, developer tools, and more.

photon search postgres
photon add postgres

Browse the full catalog in the official photons repository. You can also host a private marketplace for your team: internal tools that stay off the public internet.


Commands

# Run
photon                            # Open Beam UI
photon mcp <name>                 # Run as MCP server
photon mcp <name> --dev           # MCP server with hot reload
photon cli <name> [method]        # Run as CLI tool

# Create
photon maker new <name>           # Scaffold a new photon

# Manage
photon info                       # List all photons
photon info <name> --mcp          # Get MCP client config
photon maker validate <name>      # Check for errors

# Marketplace
photon add <name>                 # Install photon
photon search <query>             # Search marketplace
photon upgrade                    # Upgrade all

# Ops
photon doctor                     # Diagnose environment
photon test                       # Run tests

Tag Reference

| Tag | Where | What it does | |---|---|---| | @dependencies | Class | Auto-install npm packages on first run | | @cli | Class | Declare system CLI dependencies, checked at load time | | @format | Method | Result rendering (table, list, markdown, code, etc.) | | @param ... {@choice a,b,c} | Param | Dropdown selection in Beam | | @param ... {@format email} | Param | Input validation and field type | | @param ... {@min N} {@max N} | Param | Numeric range constraints | | @ui | Class/Method | Link a custom HTML template | | @webhook | Method | Expose as HTTP endpoint | | @scheduled | Method | Run on a cron schedule | | @locked | Method | Distributed lock across processes | | @autorun | Method | Auto-execute when selected in Beam | | @mcp | Class | Inject another MCP server as a dependency | | @icon | Class/Method | Set emoji icon |

See the full Tag Reference for all 30+ tags with examples.


Documentation

Start here:

| Guide | | |---|---| | Getting Started | Create your first photon, step by step | | Tag Reference | Complete JSDoc tag reference with examples | | Naming Conventions | How to name methods so they read naturally as CLI commands | | Troubleshooting | Common issues and solutions |

Build more:

| Topic | | |---|---| | Custom UI | Build rich interactive interfaces with the photon bridge API | | OAuth | Built-in OAuth 2.0 with Google, GitHub, Microsoft | | Daemon Pub/Sub | Real-time cross-process messaging | | Webhooks | HTTP endpoints for external services | | Locks | Distributed locks for exclusive access | | Advanced Patterns | Lifecycle hooks, dependency injection, interactive workflows | | Deployment | Docker, Cloudflare Workers, AWS Lambda, Systemd |

Operate:

| Topic | | |---|---| | Security | Best practices and audit checklist | | Marketplace Publishing | Create and share team marketplaces | | Best Practices | Patterns for production photons | | Comparison | Benchmarks vs official MCP implementations |

Reference: Architecture · Changelog · Contributing


Open Source

Photon is free and open source under the MIT license.

The project is still evolving and contributions are welcome.