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

@assemble-inc/chat-widget

v0.3.1

Published

Embeddable AI chat widget powered by Anthropic and MCP

Readme

@assemble-inc/chat-widget

An embeddable, general-purpose AI chat widget. Drop it into any React app — or any plain HTML page — to give users a floating chat interface backed by Anthropic Claude and MCP (Model Context Protocol). Fully configurable at embed time: system prompt, model, knowledge base, UI copy, and more.


Installation

npm install @assemble-inc/chat-widget
# or
yarn add @assemble-inc/chat-widget

Peer dependencies — make sure these are already in your project:

npm install react react-dom

Quick Start

1. Mount the widget

import { Widget } from "@assemble-inc/chat-widget";
import "@assemble-inc/chat-widget/style.css";

export default function App() {
  return (
    <>
      {/* your app */}
      <Widget
        config={{
          systemPrompt: "You are a helpful assistant for Acme Corp.",
          title: "Ask Us Anything",
          placeholder: "Type your question...",
        }}
      />
    </>
  );
}

The widget renders as a fixed floating button in the bottom-right corner (configurable). Clicking it opens the chat panel.

2. Add the server-side handler

The simplest setup is to mount the handler on your existing app server so that the widget's default endpoint: '/api/chat' resolves correctly:

// your existing Express/Next/etc. server
import { createChatHandler } from "@assemble-inc/chat-widget/server";

app.use(express.json());
app.post(
  "/api/chat",
  createChatHandler({
    apiKey: process.env.ANTHROPIC_API_KEY!,
    mcpServerUrl: process.env.MCP_SERVER_URL!,
    mcpBearerToken: process.env.MCP_BEARER_TOKEN,
  }),
);

If you prefer to run a separate API server (e.g. on port 3006), you must tell the widget where to find it — otherwise it will POST to your frontend server and get a 404:

// Pass the full URL when the API server is on a different port
<Widget config={{ endpoint: "http://localhost:3006/api/chat" }} />

Or proxy /api to the API server in your frontend dev config (Vite example):

// vite.config.ts
server: {
  proxy: {
    "/api": "http://localhost:3006",
  },
},

3. Environment variables

ANTHROPIC_API_KEY=sk-ant-...
MCP_SERVER_URL=https://your-mcp-server.example.com/mcp
MCP_BEARER_TOKEN=your-bearer-token   # optional
SYSTEM_PROMPT="You are..."           # optional server-side override

Script-tag Embed (no React required)

For non-React apps, use the self-contained IIFE bundle:

<link rel="stylesheet" href="https://cdn.example.com/asm-widget/index.css" />
<script src="https://cdn.example.com/asm-widget/embed.js"></script>
<script>
  AsmWidget.init({
    endpoint: "https://my-server.com/api/chat",
    systemPrompt: "You are a helpful assistant.",
    title: "Help",
  });
</script>

Build the embed bundle:

npm run build:embed   # outputs dist/embed.js

Configuration Reference

WidgetConfig

All configuration is passed as a single config prop. Every field is optional.

| Field | Type | Default | Description | | ----------------- | --------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------- | | endpoint | string | '/api/chat' | Backend chat endpoint URL. | | systemPrompt | string | — | System prompt for this embed. Forwarded to the server; server-side value wins when both are set. | | context | string | — | Additional context appended after the system prompt (e.g. current page, user role). Forwarded to the server. | | mcpServerUrl | string | — | MCP server URL for this embed. Must appear in the server's mcpCredentials allowlist; falls back to default. | | model | string | — | Claude model ID (e.g. 'claude-opus-4-5'). Forwarded to the server; server-side value wins when both are set. | | temperature | number | — | Response creativity, 0–1. Forwarded to the server; server-side value wins when both are set. | | initialMessages | Array<{ role, content }> | — | Seed messages shown when the widget first opens. | | persist | boolean | false | Save the conversation to sessionStorage across page navigations. | | user | { id?, name?, role? } | — | Forwarded in every request body for server-side personalisation or logging. | | title | string | 'Ask ASMBL' | Header title. | | logo | string | Built-in logo | URL to a custom logo image. | | welcomeHeading | string | — | Heading shown in the empty state. | | placeholder | string | — | Textarea placeholder text. | | position | 'bottom-right' \| 'bottom-left' | 'bottom-right' | Widget anchor corner. |

WidgetProps

| Prop | Type | Default | Description | | -------------- | -------------------------------------------------- | ------- | ------------------------------------------------------------------------------------ | | config | WidgetConfig | — | All widget configuration (see above). | | open | boolean | — | Controls open/close state from outside (controlled mode). | | defaultOpen | boolean | false | Initial open state when uncontrolled. | | onOpenChange | (open: boolean) => void | — | Called whenever the widget opens or closes. | | onMessage | (msg: { role: string; content: string }) => void | — | Called after each new assistant message is received. | | onError | (error: Error) => void | — | Called when a stream error occurs. | | children | ReactNode | — | Custom content shown in the empty state. Replaces the built-in empty state entirely. |


Server Reference

createChatHandler(config)

Returns an Express-compatible (req, res) => Promise<void> request handler that streams Claude responses via your MCP server.

| Option | Type | Required | Description | | ---------------- | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------- | | apiKey | string | Yes | Anthropic API key. | | mcpServerUrl | string | Yes | Default MCP server URL. Used when no per-embed URL is provided or the requested URL is not in the allowlist. | | mcpBearerToken | string | No | Bearer token for the default MCP server. | | model | string | No | Claude model ID. Takes precedence over any model sent by the widget. Defaults to claude-sonnet-4-5. | | systemPrompt | string | No | Operator system prompt. Takes precedence over any system prompt sent by the widget. | | temperature | number | No | Model temperature (0–1). Takes precedence over any temperature sent by the widget. | | mcpCredentials | Record<string, string> | No | Allowlist of additional MCP server URLs → bearer tokens. Enables per-embed MCP routing. Credentials never leave the server. |

Precedence rules

For systemPrompt, model, temperature, and mcpServerUrl, the server-side value always takes precedence over what the widget sends. The resolution order is:

ChatHandlerConfig value  →  widget config value  →  built-in default

Multi-context deployments

A single server can serve multiple embeds pointing at different MCP servers:

createChatHandler({
  apiKey: process.env.ANTHROPIC_API_KEY!,
  mcpServerUrl: process.env.MCP_SERVER_URL!, // default fallback
  mcpBearerToken: process.env.MCP_BEARER_TOKEN,
  mcpCredentials: {
    "https://mcp.acme.com/hr": process.env.MCP_HR_TOKEN,
    "https://mcp.acme.com/it": process.env.MCP_IT_TOKEN,
  },
});

Each embed specifies which server it wants via config.mcpServerUrl. Bearer tokens are looked up server-side — they are never exposed to the browser.


Examples

Minimal embed

<Widget config={{ systemPrompt: "You are a helpful assistant." }} />

Fully configured embed

<Widget
  config={{
    endpoint: "https://api.acme.com/chat",
    systemPrompt: "You are an HR assistant for Acme Corp.",
    mcpServerUrl: "https://mcp.acme.com/hr",
    model: "claude-opus-4-5",
    temperature: 0.3,
    title: "Ask HR",
    welcomeHeading: "How can we help?",
    placeholder: "Ask an HR question...",
    position: "bottom-left",
    persist: true,
    user: { id: currentUser.id, name: currentUser.name },
  }}
  onMessage={({ content }) => analytics.track("chat_message", { content })}
  onOpenChange={(open) => setHelpOpen(open)}
/>

Custom empty state (children)

<Widget config={{ title: "Support", placeholder: "Describe your issue..." }}>
  <div>
    <h3>How can we help?</h3>
    <button onClick={() => sendMessage("Reset my password")}>
      Reset my password
    </button>
    <button onClick={() => sendMessage("Billing question")}>
      Billing question
    </button>
  </div>
</Widget>

Local Development

# Install dependencies
npm install

# Copy and fill in environment variables
cp .env.example .env

# Start the Vite dev server (port 3003) + Express API server (port 3006)
npm run dev

The Vite dev server proxies /api/* to http://localhost:3006.

npm run lint          # type-check without emitting
npm run build         # build the React library (dist/)
npm run build:embed   # build the standalone IIFE script (dist/embed.js)

Publishing

npm version patch   # bug fixes (0.1.x)
npm version minor   # new features (0.x.0)
npm version major   # breaking changes (x.0.0)

npm publish