विषय पर बढ़ें

Jarvis Embed SDK

Embed the Jarvis AI Assistant in any web application. Works with bundlers and script tags.


Installation

npm install @ascending-inc/jarvis-embed

Browser (no bundler)

A pre-built UMD bundle is served via GitHub Pages. Load the latest version directly with a <script> tag and access the class via window.JarvisSDK:

<script src="https://ascending-llc.github.io/jarvis-embed/latest/jarvis-embed.js"></script>
<script>
  const { JarvisEmbed } = window.JarvisSDK;

  const jarvis = new JarvisEmbed({
    provider:    'google',
    token:       googleIdToken,
    apiUrl:      'https://jarvis.host.com',
    containerId: 'chat-container',
  });
</script>

To pin to a specific version, replace latest with the version number:

<script src="https://ascending-llc.github.io/jarvis-embed/0.1.3/jarvis-embed.js"></script>

The bundle is also included in the npm package at dist/index.global.js if you prefer to self-host it.


Usage

import { JarvisEmbed } from '@ascending-inc/jarvis-embed';

const jarvis = new JarvisEmbed({
  provider:    'google',
  token:       googleIdToken,
  containerId: 'chat-container',
  apiUrl:       'https://jarvis.host.com',
  spec:        'my-spec',
  agentId:     'agent_123',
  artifactsButton: false,
  onReady:     (jarvisToken) => jarvis.setMcpServers(['my-mcp-server']),
});

Options

Option Type Default Description
provider AuthProvider Required Auth provider — see Authentication.
apiUrl string Required Jarvis API endpoint (e.g. https://jarvis.host.com).
token string Required (except hmac) OAuth / JWT token. Omit for hmac.
containerId string ID of the DOM element to mount the iframe into.
container HTMLElement Direct element reference (alternative to containerId).
width string '100%' CSS width of the iframe.
height string '600px' CSS height of the iframe.
iframeUrl string {apiUrl}/v1/chat Override just the embedded chat page URL. Useful for local iframe testing while keeping auth/API calls pointed at apiUrl.
spec string Spec identifier to use for the conversation (sent as ?spec= to the API). Retrieve available values from GET {apiUrl}/api/config.
agentId string Agent identifier to use for the conversation (sent as ?agent_id= to the embedded chat).
artifactsButton boolean false Initial visibility state of the artifacts button in the embedded chat UI.
debug boolean false Log SDK activity to the console.
onReady (jarvisToken: string) => void Fires when the iframe is authenticated and ready. Receives the Jarvis session token — use it to call Jarvis APIs (e.g. GET {apiUrl}/api/mcp/servers) on behalf of the user.
onError (err: Error) => void Fires on failure.
onMessage (data: unknown) => void Fires when the iframe posts a message to the host page.

If neither containerId nor container is provided the iframe appends to document.body.

Local iframe testing

If you want to use production auth/API endpoints but load the chat UI from a local dev server, set iframeUrl separately:

new JarvisEmbed({
  provider: 'google',
  token: googleIdToken,
  containerId: 'chat-container',
  apiUrl: 'https://jarvis.host.com',
  iframeUrl: 'http://localhost:3090/c/new',
});

When iframeUrl is provided, the SDK will:

  • keep token exchange and API calls on apiUrl
  • load the iframe from iframeUrl
  • use the iframeUrl origin for postMessage

Getting a spec

Available specs can be retrieved from the Jarvis config endpoint:

GET {apiUrl}/api/config

Using an agent

To start the embedded chat in agent mode, pass agentId in the constructor config:

new JarvisEmbed({
  provider:    'google',
  token:       googleIdToken,
  containerId: 'chat-container',
  apiUrl: 'https://jarvis.host.com',
  agentId:     'agent_123',
});

Authentication

For google, s_jwt, a_jwt, and hmac, the SDK sends your auth payload to POST {apiUrl}/api/auth/exchange, and Jarvis returns a short-lived session token for the embedded chat. With direct, the SDK skips the exchange call and uses your JWT as-is.

google

Pass the Google id_token you receive from OAuth2 directly — no server-side token signing is required.

Provider Token
google Google id_token from OAuth2

s_jwt / a_jwt

Provider Token
s_jwt JWT signed with a shared secret (HS256)
a_jwt JWT signed with a private key (RS256 / ES256)

Generating the token on your server

Your server signs a JWT that Jarvis verifies. The two providers differ only in the signing algorithm and key material:

Provider Algorithm Signing key Jarvis verifies with
s_jwt HS256 A shared secret (CUSTOM_JWT_SECRET) The same shared secret
a_jwt RS256 (or ES256) Your RSA/EC private key (CUSTOM_JWT_PRIVATE_KEY) The matching public key

The iss, aud, and (for a_jwt) kid values must match the configured values in the Jarvis deployment.

Required claims

These are the claims Jarvis validates on every token.

Claim Type Description
sub string Username — a user's login ID. Must be a stable, unique identifier (e.g. username@domain.com)
iss string Issuer — the base URL of the Jarvis deployment.
aud string Audience — (e.g. "jarvis-services").
iat number Issued-at Unix timestamp (seconds).
exp number Expiry Unix timestamp (seconds). Maximum 24 hours from iat.

JWT header

Field Value
alg HS256 for s_jwt, RS256 / ES256 for a_jwt
kid Key ID (a_jwt only)

Node.js example

The payload is identical for both providers — only the signing options change. Swap the commented block to switch providers:

import jwt from 'jsonwebtoken';

const JARVIS_ISS = 'https://jarvis.host.com';
const JARVIS_AUD = 'jarvis-services';

function generateJarvisToken(username, { expiresInHours = 1 } = {}) {
  const now = Math.floor(Date.now() / 1000);

  const payload = {
    sub: username,
    iss: JARVIS_ISS,
    aud: JARVIS_AUD,
    iat: now,
    exp: now + expiresInHours * 3600,
  };

  // a_jwt — sign with an RSA/EC private key
  return jwt.sign(payload, process.env.CUSTOM_JWT_PRIVATE_KEY, {
    algorithm: 'RS256',
    keyid: 'self-signed-key-v1',
  });

  // s_jwt — sign with a shared secret
  // return jwt.sign(payload, process.env.CUSTOM_JWT_SECRET, {
  //   algorithm: 'HS256',
  // });
}

Environment variables

Your server

Variable Description
CUSTOM_JWT_PRIVATE_KEY PEM-encoded RSA/EC private key used to sign a_jwt tokens.
CUSTOM_JWT_SECRET Shared secret used to sign s_jwt tokens.

The iss, aud, and kid values are deployment-specific constants that can be hardcoded or stored as environment variables. They must match exactly what Jarvis is configured to expect.

Jarvis

Variable Description
CUSTOM_JWT_PUBLIC_KEY PEM-encoded RSA/EC public key. Jarvis uses this to verify a_jwt tokens signed by your server.
CUSTOM_JWT_SECRET Shared secret. Jarvis uses this to verify s_jwt tokens.
CUSTOM_JWT_ISSUER Expected issuer claim value (e.g., "https://jarvis.host.com").
CUSTOM_JWT_AUDIENCE Expected audience claim value (e.g., "jarvis-services").
ALLOW_EMBED Set to true to enable the embed token flow (/api/auth/exchange and direct tokens).

direct

With direct, your server mints the Jarvis-trusted JWT itself and the SDK uses it as-is — the /api/auth/exchange round-trip is skipped.

new JarvisEmbed({
  provider:    'direct',
  token:       jarvisToken, // generated by your server
  apiUrl: 'https://jarvis.host.com',
  containerId: 'chat-container',
});

How the token is authenticated

The direct token is delivered to the embedded chat via SDK_AUTH and presented as a Bearer token on every request to Jarvis. Jarvis validates it through its Passport strategies, choosing one per request:

Condition Strategy
OPENID_REUSE_TOKENS=true and the request carries a token_provider=openid cookie with a valid signed session openidJwt
Default jwt

The openidJwt strategy loads the federated tokens from the server-side session, which is resolved using the Session ID cookie the browser sends with each request. If that cookie is missing, expired, or the session store has been cleared, req.session cannot be hydrated and Jarvis will fall back to the standard jwt strategy or return a 401 error code.

hmac

new JarvisEmbed({
  provider:  'hmac',
  userId:    'user_123',
  timestamp: Math.floor(Date.now() / 1000),
  signature: hmacHex, // HMAC-SHA256(userId + timestamp)
  containerId: 'chat-container',
  apiUrl: 'https://jarvis.host.com',
});

Requests older than 5 minutes are rejected server-side.


Methods

Method Signature Description
destroy () => void Removes the iframe and cleans up the window message listener. Call this on unmount — essential for React.
setMcpServers (servers: string[]) => void Activates one or more MCP servers by name. Safe to call before the iframe is ready — servers are queued and flushed automatically on SDK_READY.
setArtifactsButton (enabled: boolean) => void Shows or hides the artifacts button at runtime. Safe to call before the iframe is ready — the latest value is queued and applied automatically on SDK_READY.
setAgentId (agentId: string) => void Switches the embedded chat to a specific agent at runtime. Safe to call before the iframe is ready — the latest value is queued and applied automatically on SDK_READY. Empty or whitespace-only values are ignored.

MCP (Model Context Protocol)

Pass one or more MCP server names to give Jarvis access to external tools and data sources during a session.

Discovering available servers

Call GET {apiUrl}/api/mcp/servers with the Jarvis token as a Bearer header to retrieve the names of all servers available to the authenticated user. The response is an object keyed by server name:

const jarvis = new JarvisEmbed({
  provider:    'google',
  token:       googleIdToken,
  containerId: 'chat-container',
  apiUrl: 'https://jarvis.host.com',
  onReady: async (jarvisToken) => {
    const res = await fetch(`${apiUrl}/api/mcp/servers`, {
      headers: { Authorization: `Bearer ${jarvisToken}` },
    });
    const servers = await res.json(); // { "posthog": {...}, "github": {...}, ... }
    const names = Object.keys(servers);

    // Activate all of them, or let the user pick from `names`
    jarvis.setMcpServers(names);
  },
});

Activating servers

The safest place to call setMcpServers is inside onReady, which fires once the iframe has authenticated and is listening:

const jarvis = new JarvisEmbed({
  provider:    'google',
  token:       googleIdToken,
  containerId: 'chat-container',
  apiUrl: 'https://jarvis.host.com',
  onReady: () => {
    jarvis.setMcpServers(['posthog', 'aws-knowledge']);
  },
});

You can also call it at any time after instantiation — if the iframe isn't ready yet the servers are queued internally and sent as soon as SDK_READY fires:

const jarvis = new JarvisEmbed({
  provider:    's_jwt',
  token:       myJwt,
  containerId: 'chat-container',
  apiUrl: 'https://jarvis.host.com',
});

// Called immediately — queued until SDK_READY
jarvis.setMcpServers(['github', 'jira']);

To swap the active server set later (e.g. after a user action), call setMcpServers again with the new list:

document.getElementById('enable-analytics')?.addEventListener('click', () => {
  jarvis.setMcpServers(['posthog']);
});

setArtifactsButton(enabled: boolean)

Shows or hides the artifacts button at runtime. If called before the iframe is ready, the value is queued and applied once the SDK is ready.

jarvis.setArtifactsButton(true);
jarvis.setArtifactsButton(false);

setAgentId(agentId: string)

Switches the embedded chat to a specific agent at runtime. If called before the iframe is ready, the value is queued and applied once the SDK is ready.

jarvis.setAgentId('agent_123');

This is useful when the host app lets the user choose an agent after the widget has already been mounted:

document.getElementById('agent-picker')?.addEventListener('change', (event) => {
  const nextAgentId = (event.target as HTMLSelectElement).value;
  jarvis.setAgentId(nextAgentId);
});

React

useJarvis is not exported from the package — copy examples/react/src/useJarvis.ts into your project. It wraps JarvisEmbed in a useEffect and calls destroy() on unmount automatically:

import { useEffect, useRef } from 'react';
import { JarvisEmbed } from '@ascending-inc/jarvis-embed';
import type { JarvisConfig } from '@ascending-inc/jarvis-embed';

export function useJarvis(config: JarvisConfig | null) {
  const jarvisRef = useRef<JarvisEmbed | null>(null);

  useEffect(() => {
    if (!config) return;
    jarvisRef.current = new JarvisEmbed(config);
    return () => {
      jarvisRef.current?.destroy();
      jarvisRef.current = null;
    };
  }, [config]);

  return jarvisRef;
}

Pass null to defer initialization until the user is authenticated. The hook calls destroy() automatically on unmount, so there are no memory leaks or stale event listeners.

Using the container prop in React

When mounting into a React-managed DOM node, use a callback ref so initialization only happens once the element actually exists. Wrap config in useMemo with the container as a dependency — this ensures the SDK sees a real HTMLElement, not null:

import { useCallback, useMemo, useState } from 'react';
import { useJarvis } from './useJarvis';

function ChatWidget({ googleToken }: { googleToken: string }) {
  const [container, setContainer] = useState<HTMLDivElement | null>(null);

  const config = useMemo(() => {
    if (!container || !googleToken) return null;
    return {
      provider: 'google' as const,
      token:    googleToken,
      container,
      width:    '100%',
      height:   '100%',
      onReady:  (jarvisToken: string) => {
        // fetch available servers and activate them
      },
    };
  }, [container, googleToken]);

  const jarvisRef = useJarvis(config);

  return <div ref={setContainer} style={{ flex: 1 }} />;
}

Using containerId instead avoids this entirely — the SDK does the getElementById lookup itself after the iframe loads — but the container prop approach above is required when the element is managed by React state.


Examples

Both examples demonstrate Google OAuth, MCP tool selection, and embedding the chat widget. They share the same Express backend for token exchange.

1. Clone and set up

git clone https://github.com/ascending-llc/jarvis-embed.git
cd jarvis-embed
npm run setup

setup installs root dependencies, builds the SDK, and installs dependencies for both examples.

2. Configure environment variables

Each example has its own .env. Copy and fill in both:

cp examples/vanilla/.env.example examples/vanilla/.env
cp examples/react/.env.example   examples/react/.env
Variable Description
GOOGLE_CLIENT_ID OAuth client ID from Google Cloud Console
GOOGLE_CLIENT_SECRET OAuth client secret (never sent to the browser)
REDIRECT_URI Must match what's registered in Google Cloud Console
JARVIS_URL Required — Jarvis API endpoint (e.g. https://jarvis.host.com)
JARVIS_SPEC Optional spec override
PORT Express port (default 5500)

3. Run an example

Vanilla JS — floating chat widget, served at http://localhost:5500

npm run example:vanilla

ReactuseJarvis hook demo with proper cleanup, served at http://localhost:5501

npm run example:react