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

@casys/mcp-view

v0.7.1

Published

View-side SDK for MCP Apps with in-iframe routing and host tool calls

Readme

@casys/mcp-view

Browser-side primitives for MCP Apps. The package creates the ext-apps connection, applies host styles, provides memory-only view routing, and capability-gates calls back to the originating MCP server.

import { createMcpApp, defineView } from "@casys/mcp-view";

const app = await createMcpApp({
  info: { name: "Engineering Viewer", version: "0.1.0" },
  root: document.getElementById("root")!,
  views: {
    home: defineView({ render: () => "<h1>Ready</h1>" }),
  },
  initialView: "home",
});

The core package does not depend on a rendering framework. Existing React component trees can use the optional adapter without duplicating the MCP Apps lifecycle:

import { readStructuredContent } from "@casys/mcp-view";
import { defineReactView } from "@casys/mcp-view/react";

const detail = defineReactView<State, { id: string }, Invoice>({
  async onEnter(ctx, { id }) {
    return readStructuredContent<Invoice>(await ctx.callTool("invoice_get", { id }));
  },
  component: ({ ctx, data }) => (
    <InvoiceViewer
      invoice={data}
      onBack={() => ctx.navigate("list")}
    />
  ),
});

React and ReactDOM are optional peers of the npm package. Component-only Preact Apps can use the official @casys/mcp-view/preact adapter; Preact is also an optional peer and is never loaded by the renderer-neutral main export. Direct use of the official ext-apps SDK remains conformant. See the adoption guide and authoring-framework decision.

Reusable components and composable surfaces

An MCP App exposes small domain components and may also expose a default standalone composition. A component-only App deliberately omits defaultSurface: it renders only when a compatible Compose host selects a surface. Compose never inspects the child DOM and never asks for a size mode:

import {
  advertisedComponentCatalog,
  createMcpApp,
  defineComponentRegistry,
  defineMetricGridComponent,
  defineStatusComponent,
  mountComponentSurface,
} from "@casys/mcp-view";

const components = defineComponentRegistry<Simulation>({
  components: {
    "thermal.status": defineStatusComponent({
      title: "Simulation status",
      select: (data) => ({ label: data.status }),
    }),
    "thermal.metrics": defineMetricGridComponent({
      title: "Thermal metrics",
      select: (data) => [{
        id: "max-temperature",
        label: "Max temperature",
        value: data.maxTemperature,
        unit: "°C",
      }],
    }),
  },
  defaultSurface: {
    layout: { type: "stack", gap: "sm" },
    components: [
      { id: "status", component: "thermal.status" },
      { id: "metrics", component: "thermal.metrics" },
    ],
  },
});

await createMcpApp({
  // ...info, root, views, initialView
  componentCatalog: advertisedComponentCatalog(components),
});

Use mountComponentSurface() from a view renderer. It mounts the negotiated surface when present, otherwise defaultSurface, and returns one deterministic cleanup handle. With neither selection nor default it reports surface-required; it never invents a dashboard. Components receive the same domain data and may be implemented with the supplied status/metric/key-value primitives, custom DOM, React, Preact, Three.js, or any other renderer. The public contract stays JSON-only: component keys, layout, instance IDs, safe props, and event routes.

When present, the default surface is the standalone viewer, not a second implementation. A composed dashboard can select, order, and repeat advertised components in YAML while the owning MCP App keeps domain rendering, local state, and actions.

Preact surface runtime and shared visual language

startPreactSurfaceApp() owns the result-driven MCP Apps handshake, advertises the component catalog, mounts the host-selected surface, and remounts when the host changes composition. It also installs the shared mcp-view theme by default:

import { defineComponentRegistry } from "@casys/mcp-view";
import { definePreactComponent, startPreactSurfaceApp } from "@casys/mcp-view/preact";

const registry = defineComponentRegistry({
  components: {
    "bom.metrics": definePreactComponent({ title: "BOM metrics" }, BomMetrics),
  },
  // No defaultSurface: this App is a palette for Compose.
});

await startPreactSurfaceApp({ root, info, registry });

Domain components should import the shared presentation kit instead of recreating its cards and tables:

import {
  Badge,
  Card,
  DataTable,
  KeyValueList,
  MetricGrid,
  type PreactSurfaceComponentProps,
} from "@casys/mcp-view/preact";

function SolveMetrics({ data }: PreactSurfaceComponentProps<StaticSolveResult>) {
  return (
    <Card title="Static solve" actions={<Badge tone="success">Solved</Badge>}>
      <MetricGrid items={toMetricItems(data)} />
    </Card>
  );
}

Native Preact applications that do not run inside an MCP Apps iframe should use the presentation-only entry point. It includes the same components and theme, but no lifecycle, surface registry, ext-apps, window, or postMessage bridge:

import { Card, installMcpViewTheme, MetricGrid } from "@casys/mcp-view/preact/components";

installMcpViewTheme();

@casys/mcp-view/preact remains backwards-compatible and continues to export both this presentation kit and the MCP Apps surface runtime.

This is deliberately closer to an imported design-system core than copied application CSS. A future mcp-view add workflow may provide source-owned domain recipes, but the foundational presentation components stay versioned here so every MCP receives the same fixes.

The theme is the compact, container-friendly language first proven by the ERPNext BOM components. It provides tokens and stable classes for cards, uppercase section titles, metric grids, badges, dense tables, selected rows, cross-view state, empty states, stacks, and rows. Import installMcpViewTheme() for custom renderers or set theme: false in the Preact runtime when an App must supply a complete alternative theme. Domain-specific visuals remain owned by their MCP.

Viewer-to-viewer interactions use the separate optional ctx.events channel. Its messages are restricted to ui/compose/event, validated at the iframe boundary, and removed automatically on teardown. Domain viewers still decide which selections, filters, or highlights are meaningful.

Host tool notifications

Pass onToolInput, onToolInputPartial, or onToolResult directly to createMcpApp when a view needs to react to notifications sent by its MCP Apps host. Each callback receives the notification payload and a complete AppHandle.

const app = await createMcpApp({
  // ...info, root, views, initialView
  async onToolResult(result, app) {
    if (result.isError) return;
    await app.navigate("summary", result.structuredContent);
  },
});

@casys/mcp-view installs those ext-apps handlers before App.connect(). That matters: ext-apps treats them as one-shot events and warns (or throws when strict: true) when they are registered after the initialize handshake. Events that arrive during the handshake or first route are buffered, then replayed in host arrival order once the AppHandle exists. Async callbacks are serialized; an error is logged and does not stop later notifications.

Use readStructuredContent() for the normal data path or readResultData() when a legacy JSON text fallback is explicitly required. Neither helper silently prefers model-facing text over structuredContent.

For an executable two-view bundle, see examples/basic.

Scaffold a vanilla result viewer

mcp-view also ships one narrow generator for the repeated MCP Apps pattern: receive an initiating structured result and render a readable evidence-style view. It is a starting point for a componentized viewer, not a server generator.

deno run -A jsr:@casys/[email protected]/scaffold result-viewer ./result-viewer
cd ./result-viewer
deno task test
deno task build

The generated project is standalone and vanilla: index.html, main.ts, a generic model/parser, renderer, host-aware accessible styles, build script, and parser/render test. Its onToolResult callback is declared in createMcpApp configuration, so mcp-view registers it before connect() and preserves the initiating result during the Apps handshake. It renders loading, empty, error, metrics, scalar details, and URI-based artifacts without assuming an ERP, CAD, or other domain schema.

The generator refuses a non-empty target directory. Pass --force only when overwriting its named scaffold files is intentional; unrelated files are not removed.