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

@adobe/llm-apps-runtime

v0.12.1

Published

Server runtime for Adobe LLM Apps — a platform for building AI tools and interactive widgets on Adobe I/O Runtime

Readme

@adobe/llm-apps-runtime

Server runtime for Adobe LLM Apps — a platform for building AI tools and interactive widgets that plug into Claude, Cursor, ChatGPT, and other LLM hosts.

This package powers every Adobe LLM App deployed on Adobe I/O Runtime. It handles the full request lifecycle — action loading, tool registration, widget resources, CORS, and transport — so app developers only need to write their action handlers. Under the hood it speaks the Model Context Protocol via the official MCP TypeScript SDK.

Install

npm install @adobe/llm-apps-runtime

Quick start

Wire the runtime into your Adobe I/O Runtime action:

// entry.js — webpack entry point of your Adobe I/O Runtime action
const { createMain } = require('@adobe/llm-apps-runtime')

const moduleContext = require.context('./actions', true, /index\.js$/)
const htmlContext   = require.context('./actions', true, /widget\.html$/)
let actionsConfig = {}
try { actionsConfig = require('./actions.json') } catch (e) { /* optional */ }

module.exports = createMain({ moduleContext, htmlContext, actionsConfig })

Write your handlers as plain async functions:

// actions/echo/index.js
module.exports = async ({ message }) => ({
    content: [{ type: 'text', text: `Echo: ${message}` }]
})

That's it. Deploy with aio app deploy and your handlers are reachable as tools in any LLM host that speaks MCP.

Features

  • Zero-config action discovery — drop a folder under actions/, it becomes a tool
  • Config-driven metadata — descriptions, schemas, annotations, and widget config come from actions.json
  • Interactive widgets — ship widget.html alongside your handler for rich UIs in Claude, Cursor, ChatGPT, etc.
  • EDS widget support — auto-generate widget markup for Adobe Edge Delivery Services content
  • Local development — run your app as a plain HTTP server with no Adobe credentials
  • Test-friendly — handlers are plain functions; a filesystem loader lets you write full-stack integration tests without webpack

API

createMain(options)

Creates the Adobe I/O Runtime main(params) function that serves your app.

const { main } = createMain(options)

Options

| Option | Type | Default | Description | |-----------------|-----------------|----------------------|-------------| | moduleContext | webpack context | — | require.context for actions/**/index.js. Webpack path. | | htmlContext | webpack context | — | require.context for actions/**/widget.html. Webpack path. | | actionsConfig | object | {} | Parsed actions.json keyed by action name. Webpack path. | | actionsDir | string | <cwd>/actions | Absolute path to the actions directory. Fs path. | | serverName | string | 'adobe-llm-apps' | App name reported to MCP clients. | | serverVersion | string | '1.0.0' | App version reported to MCP clients. |

When moduleContext is provided, the webpack path is used. Otherwise the filesystem path is used (via actionsDir).

Returns { main } — an async function compatible with Adobe I/O Runtime's action signature.

createLocalServer(main, port, extraParams)

Starts a plain Node.js HTTP server that wraps a main(params) function. Useful for npm run dev:local in consumer apps — no Adobe credentials required.

const { createLocalServer, parseParamArgs } = require('@adobe/llm-apps-runtime/local')
const { main } = require('./dist/index.js') // your webpack bundle
const extraParams = parseParamArgs(process.argv) // --param KEY=VALUE flags
createLocalServer(main, process.env.PORT || 9080, extraParams)

Parameters

| Parameter | Type | Default | Description | |---------------|----------|---------|-------------| | main | Function | — | The main(params) function returned by createMain. | | port | number | 9080 | Port to listen on. | | extraParams | object | {} | Key-value pairs merged into every request's params, simulating Adobe I/O Runtime action params. |

Returns an http.Server instance.

Passing action params locally

In production, Adobe I/O Runtime merges action params (set via aio app deploy --param) into the params object that main() receives alongside the request. Locally there is no runtime, so you pass them on the command line using the same --param KEY=VALUE syntax:

node server/local.js \
  --param MY_API_URL=http://localhost:3000 \
  --param MY_API_KEY=dev-secret

parseParamArgs(process.argv) reads these flags and passes them to createLocalServer as extraParams. Every inbound request then receives them as top-level params fields, so any param-reading code in your action works identically to the deployed runtime.

Action loading primitives

Exported from the package root for advanced use cases (custom loaders, integration tests):

  • loadActionsFromContexts(server, { moduleContext, htmlContext, actionsConfig }) — webpack-based loader
  • loadActionsFromFs(server, actionsDir, actionsConfig) — filesystem-based loader
  • loadActionsConfig(actionsDir) — read and key actions.json by name

Most users won't need these — createMain calls them internally based on which options you pass.

Handler contract

A handler is an async function exported from actions/<name>/index.js:

module.exports = async function handler(args) {
    return {
        // Shown to the LLM and rendered as text by clients
        content: [{ type: 'text', text: 'Result' }],

        // Optional. Passed to the widget iframe via window.openai.toolOutput.
        // Never sent to the LLM.
        structuredContent: { key: 'value' }
    }
}

args is the tool's arguments object, already validated against the inputSchema in actions.json.

See the MCP spec for the full response schema.

actions.json

actions.json is the source of truth for tool metadata. The runtime reads it from one level above actionsDir (i.e. the app root). Each entry drives tool registration:

{
  "actions": [
    {
      "name": "my-action",
      "title": "My Action",
      "description": "Does something useful",
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": { "type": "string", "description": "The query" }
        },
        "required": ["query"]
      },
      "annotations": { "readOnlyHint": true },
      "widget_type": "EDS",
      "eds_widget": {
        "script_url": "https://.../aem-embed.js",
        "widget_embed_url": "https://.../embed"
      },
      "resource_meta": {
        "ui": {
          "csp": { "connect_domains": ["https://..."] },
          "prefersBorder": true
        }
      },
      "tool_meta": {
        "openai/widgetAccessible": true
      }
    }
  ]
}

Widget resolution priority

  1. widget.html file in the action directory
  2. EDS config in actions.json (auto-generates <aem-embed> markup)
  3. Tool-only (no widget)

Testing your handlers

Handlers are plain async functions — test them directly:

const handler = require('./index.js')

test('echoes the message', async () => {
    const out = await handler({ message: 'hello' })
    expect(out.content[0].text).toBe('Echo: hello')
})

For full-stack integration tests, use the filesystem loader path:

const path = require('path')
const { createMain } = require('@adobe/llm-apps-runtime')

const { main } = createMain({
    actionsDir: path.join(__dirname, '..', 'actions')
})

test('tool call returns expected content', async () => {
    const res = await main({
        __ow_method: 'post',
        __ow_body: JSON.stringify({
            jsonrpc: '2.0',
            id: 1,
            method: 'tools/call',
            params: { name: 'echo', arguments: { message: 'hi' } }
        }),
        __ow_headers: {
            'content-type': 'application/json',
            accept: 'application/json;q=1.0, text/event-stream;q=0.5'
        }
    })
    const body = JSON.parse(res.body)
    expect(body.result.content[0].text).toBe('Echo: hi')
})

Requirements

  • Node.js >=24.0.0 (matches Adobe I/O Runtime nodejs:24)
  • Webpack 5 (for bundling when deploying to I/O Runtime)

Related

Releasing

Releases are published to npm automatically via GitHub Actions when a GitHub Release is created.

Steps to release a new version:

  1. Bump the version in package.json and commit to main:
    npm version patch   # or minor / major
    git push
  2. Create a GitHub Release with a tag that matches the version (e.g. v0.2.0):
    • Go to Releases → Draft a new release on GitHub
    • Set the tag to v<version> (must match package.json)
    • Click Publish release
  3. The publish workflow runs npm test then npm publish automatically.

Required secret: Add NPM_TOKEN to the repository's Settings → Secrets and variables → Actions. The token must have the publish permission scoped to the @adobe org (or be a classic Automation token).

License

See LICENSE.