Skip to content

Core library for parsing, loading, and managing .photon.ts files - runtime-agnostic foundation for building custom Photon runtimes

License

Notifications You must be signed in to change notification settings

portel-dev/photon-core

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

27 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@portel/photon-core

Core library for parsing, loading, and managing .photon.ts files

npm version License: MIT

🎯 What is Photon Core?

Photon Core is the runtime-agnostic foundation for working with .photon.ts files. It provides:

  • βœ… Base class with lifecycle hooks (onInitialize, onShutdown)
  • βœ… Dependency management - Extract and install @dependencies from JSDoc
  • βœ… Schema extraction - Parse TypeScript types and JSDoc into JSON schemas
  • ❌ NO runtime layers - No MCP server, no CLI, no transport

This allows you to build custom runtimes on top of the Photon format:

  • Multi-protocol API servers (REST, GraphQL, RPC, MCP) β†’ Lumina
  • Orchestrators β†’ NCP
  • MCP/CLI runners β†’ @portel/photon

πŸ“¦ Installation

npm install @portel/photon-core

πŸš€ Quick Start

1. Create a Photon class

// calculator.photon.ts

/**
 * Simple calculator with basic math operations
 * @dependencies lodash@^4.17.21
 */
export default class Calculator {
  /**
   * Add two numbers together
   * @param a First number
   * @param b Second number
   */
  async add(params: { a: number; b: number }) {
    return params.a + params.b;
  }

  /**
   * Multiply two numbers
   * @param a First number
   * @param b Second number
   */
  async multiply(params: { a: number; b: number }) {
    return params.a * params.b;
  }

  /**
   * Lifecycle hook called when photon is initialized
   */
  async onInitialize() {
    console.log('Calculator initialized!');
  }

  /**
   * Lifecycle hook called when photon is shut down
   */
  async onShutdown() {
    console.log('Calculator shutting down!');
  }
}

2. Use Photon Core to load and manage it

import { PhotonMCP, DependencyManager, SchemaExtractor } from '@portel/photon-core';

// 1. Extract and install dependencies
const depManager = new DependencyManager();
const deps = await depManager.extractDependencies('./calculator.photon.ts');
// Found: [{ name: 'lodash', version: '^4.17.21' }]

await depManager.ensureDependencies('calculator', deps);
// βœ… Dependencies installed

// 2. Load the class
const CalculatorClass = (await import('./calculator.photon.ts')).default;
const instance = new CalculatorClass();

// 3. Call lifecycle hook
if (instance.onInitialize) {
  await instance.onInitialize();
}

// 4. Extract schemas (optional)
const extractor = new SchemaExtractor();
const schemas = await extractor.extractFromFile('./calculator.photon.ts');
console.log(schemas);
// [
//   {
//     name: 'add',
//     description: 'Add two numbers together',
//     inputSchema: {
//       type: 'object',
//       properties: {
//         a: { type: 'number', description: 'First number' },
//         b: { type: 'number', description: 'Second number' }
//       },
//       required: ['a', 'b']
//     }
//   },
//   ...
// ]

// 5. Execute methods
const result = await instance.add({ a: 5, b: 3 });
console.log(result); // 8

// 6. Cleanup
if (instance.onShutdown) {
  await instance.onShutdown();
}

πŸ“š API Reference

PhotonMCP

Base class for creating Photon classes (optional - you can use plain classes too).

import { PhotonMCP } from '@portel/photon-core';

export default class MyPhoton extends PhotonMCP {
  async myMethod(params: { input: string }) {
    return `Hello ${params.input}`;
  }

  async onInitialize() {
    // Called when photon is loaded
  }

  async onShutdown() {
    // Called when photon is unloaded
  }
}

Static methods:

  • getMCPName() - Convert class name to kebab-case (e.g., MyAwesomeMCP β†’ my-awesome-mcp)
  • getToolMethods() - Get all public async methods (excludes lifecycle hooks and private methods)

Instance methods:

  • executeTool(name, params) - Execute a method by name

Lifecycle hooks:

  • onInitialize() - Called when photon is initialized
  • onShutdown() - Called when photon is shut down

DependencyManager

Extracts and installs npm dependencies declared in JSDoc @dependencies tags.

import { DependencyManager } from '@portel/photon-core';

const depManager = new DependencyManager();

// Extract dependencies from source file
const deps = await depManager.extractDependencies('./my-tool.photon.ts');
// [{ name: 'axios', version: '^1.0.0' }, { name: 'date-fns', version: '^2.0.0' }]

// Install dependencies to isolated cache
await depManager.ensureDependencies('my-tool', deps);
// Creates ~/.cache/photon-mcp/dependencies/my-tool/node_modules/

// Clear cache for a specific photon
await depManager.clearCache('my-tool');

// Clear all caches
await depManager.clearAllCache();

Supported JSDoc format:

/**
 * @dependencies axios@^1.0.0, date-fns@^2.0.0
 * @dependencies @octokit/rest@^19.0.0
 */

MCP SDK Transport

Connect to external MCPs using the official @modelcontextprotocol/sdk. Supports multiple transports:

import {
  SDKMCPClientFactory,
  SDKMCPTransport,
  loadMCPConfig,
  createSDKMCPClientFactory,
  resolveMCPSource
} from '@portel/photon-core';

// Create from config
const config = {
  mcpServers: {
    // stdio transport (local process)
    github: {
      command: 'npx',
      args: ['-y', '@modelcontextprotocol/server-github'],
      env: { GITHUB_TOKEN: 'your-token' }
    },
    // SSE transport (HTTP)
    remote: {
      url: 'http://localhost:3000/mcp',
      transport: 'sse'
    },
    // WebSocket transport
    realtime: {
      url: 'ws://localhost:8080/mcp',
      transport: 'websocket'
    },
    // Streamable HTTP transport
    streaming: {
      url: 'http://localhost:3000/mcp',
      transport: 'streamable-http'
    }
  }
};

const factory = new SDKMCPClientFactory(config);
const github = factory.create('github');

// List tools
const tools = await github.list();

// Call a tool
const issues = await github.call('list_issues', { repo: 'owner/repo' });

// Or use the proxy for fluent API
import { createMCPProxy } from '@portel/photon-core';
const githubProxy = createMCPProxy(github);
const issues = await githubProxy.list_issues({ repo: 'owner/repo' });

Transport Types:

Transport Config Use Case
stdio command, args Local CLI-based MCPs
sse url, transport: 'sse' HTTP Server-Sent Events
streamable-http url, transport: 'streamable-http' HTTP streaming
websocket url, transport: 'websocket' WebSocket connections

Helper Functions:

// Load config from standard locations
// Checks: PHOTON_MCP_CONFIG env, ./photon.mcp.json, ~/.config/photon/mcp.json
const config = await loadMCPConfig();

// Create factory from default config
const factory = await createSDKMCPClientFactory();

// Resolve marketplace sources to config
const config = resolveMCPSource('github', 'anthropics/mcp-server-github', 'github');
// β†’ { command: 'npx', args: ['-y', '@anthropics/mcp-server-github'], transport: 'stdio' }

SchemaExtractor

Extracts JSON schemas from TypeScript method signatures and JSDoc comments.

import { SchemaExtractor } from '@portel/photon-core';

const extractor = new SchemaExtractor();

// Extract from file
const schemas = await extractor.extractFromFile('./my-tool.photon.ts');

// Extract from source string
const source = await fs.readFile('./my-tool.photon.ts', 'utf-8');
const schemas = extractor.extractFromSource(source);

// Extract constructor parameters
const constructorParams = extractor.extractConstructorParams(source);

Schema format:

interface ExtractedSchema {
  name: string;
  description: string;
  inputSchema: {
    type: 'object';
    properties: Record<string, any>;
    required?: string[];
  };
}

Supported JSDoc constraints:

/**
 * @param age User age {@min 0} {@max 120}
 * @param email Email address {@format email}
 * @param username Username {@pattern ^[a-z0-9_]+$}
 * @param count Count {@default 10}
 */

πŸ—οΈ Building Custom Runtimes

Photon Core is designed to be the foundation for custom runtimes. Here are examples:

Example 1: REST API Server

import express from 'express';
import { PhotonMCP, DependencyManager, SchemaExtractor } from '@portel/photon-core';

class PhotonRESTServer {
  async serve(photonPath: string) {
    const app = express();

    // Load photon
    const PhotonClass = (await import(photonPath)).default;
    const instance = new PhotonClass();
    await instance.onInitialize?.();

    // Extract schemas
    const extractor = new SchemaExtractor();
    const schemas = await extractor.extractFromFile(photonPath);

    // Create REST endpoints
    schemas.forEach(schema => {
      app.post(`/api/${schema.name}`, async (req, res) => {
        try {
          const result = await instance[schema.name](req.body);
          res.json({ result });
        } catch (error) {
          res.status(500).json({ error: error.message });
        }
      });
    });

    app.listen(3000);
  }
}

Example 2: GraphQL Server

import { buildSchema } from 'graphql';
import { PhotonMCP, SchemaExtractor } from '@portel/photon-core';

class PhotonGraphQLServer {
  async generateSchema(photonPath: string) {
    const extractor = new SchemaExtractor();
    const schemas = await extractor.extractFromFile(photonPath);

    const mutations = schemas.map(s =>
      `${s.name}(${this.paramsToGraphQL(s.inputSchema)}): JSON`
    ).join('\n');

    return buildSchema(`
      type Mutation {
        ${mutations}
      }
    `);
  }
}

Example 3: MCP Orchestrator

import { PhotonMCP, DependencyManager } from '@portel/photon-core';

class PhotonOrchestrator {
  private photons = new Map();

  async loadPhoton(path: string) {
    // Install dependencies
    const depManager = new DependencyManager();
    const deps = await depManager.extractDependencies(path);
    const name = basename(path, '.photon.ts');
    await depManager.ensureDependencies(name, deps);

    // Load class
    const PhotonClass = (await import(path)).default;
    const instance = new PhotonClass();
    await instance.onInitialize?.();

    this.photons.set(name, instance);
  }

  async execute(photonName: string, method: string, params: any) {
    const photon = this.photons.get(photonName);
    return await photon[method](params);
  }
}

🎯 Use Cases

Project Description Runtime Layer
@portel/photon CLI tool for running Photons as MCP servers or CLI tools MCP server (stdio), CLI runner
NCP Multi-MCP orchestrator with discovery and semantic search Orchestrator, unified MCP interface
Lumina Multi-protocol API server REST, GraphQL, RPC, HTTP streaming MCP

All three projects use @portel/photon-core for the shared format, but each adds its own runtime layer.


πŸ”§ Development

# Clone the repository
git clone https://github.com/portel-dev/photon-core.git
cd photon-core

# Install dependencies
npm install

# Build
npm run build

# Test
npm test

# Lint
npm run lint

πŸ“„ License

MIT Β© Portel


🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.


πŸ”— Related Projects


πŸ“– Documentation

For more information about the Photon format and ecosystem:

About

Core library for parsing, loading, and managing .photon.ts files - runtime-agnostic foundation for building custom Photon runtimes

Resources

License

Stars

Watchers

Forks

Packages

No packages published