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

zentis

v1.1.24

Published

A high-level agentic framework for Model Context Protocol (MCP) with memory and LLM integration.

Readme

Zentis

A high-level agentic framework for Model Context Protocol (MCP) with built-in memory management and LLM integration.

Features

  • 🚀 MCP Client: Connect to multiple MCP servers in parallel (SSE or HTTP).
  • 🧠 Smart Memory: Persistent history with Session ID support and automatic technical message skipping.
  • 🗄️ Storage Adapters: Support for SQLite and PostgreSQL (with SSL) with composite keys.
  • 🤖 Agentic Reasoning: Multi-turn tool calling with Regex-based Smart Routing or LLM-based Planning.
  • 🛠️ Universal Compatibility: Built-in normalization for Gemini, Groq, and OpenRouter.
  • 🛡️ Recursion Guard: Prevents infinite tool-calling loops by tracking and blocking redundant calls with identical arguments.
  • Batch Tool Execution: Execute multiple tools in parallel within a single reasoning turn for high-performance workflows.
  • �📋 Planning Phase: Optional dedicated LLM turn to pre-select tool sequences, improving accuracy for complex tasks.
  • 🎨 Smarter UI Rendering: Register custom UI components (Table, Charts, etc.) that automatically resolve large datasets from hidden data references.

Installation

npm install zentis

Note: If using SQLite or Postgres, install the respective drivers:

npm install better-sqlite3 # for SQLite
npm install pg             # for PostgreSQL

UI Component Integration (React)

Zentis allows you to register UI components that the LLM can trigger. These components automatically handle large datasets by swapping background "Data References" for actual props before the response is returned.

1. Register Components (Node.js/Agent side)

Zentis uses a central ZentisUI registry to define which components the LLM is allowed to use. By default, Table, Chart, and Graph are pre-registered.

import { ZentisAgent, ZentisUI } from 'zentis';

const ui = new ZentisUI();

// Register a custom component
ui.register({
  name: 'WeatherCard',
  description: 'Display current weather and forecast.',
  props: {
    location: { type: 'string', description: 'City name', required: true },
    temperature: { type: 'number', description: 'Current temp in Celsius' },
    isRainy: { type: 'boolean', description: 'Whether it is raining' },
    forecast: { 
      type: 'data_reference', 
      description: 'Reference to the forecast data list from a tool' 
    },
    filters: {
      type: 'object',
      description: 'Dynamic filters to apply to the data'
    }
  }
});

const agent = new ZentisAgent({ ui, ... });

Property Types

  • string, number, boolean, array, object: Standard JSON types.
  • data_reference: Crucial for performance. This tells the LLM to provide a Result ID (e.g., res_1_get_weather) instead of the raw data. Zentis will automatically swap this ID for the actual data before returning the response to your frontend.

2. Render in React

import React from 'react';
import { AgentResponse } from 'zentis';

// Example UI Component Map
const ComponentMap = {
  Table: ({ title, data, filters }: any) => {
    // Apply dynamic filters generated by the LLM
    const filteredData = React.useMemo(() => {
      if (!filters) return data;
      return data.filter((row: any) => 
        Object.entries(filters).every(([key, val]) => String(row[key]) === String(val))
      );
    }, [data, filters]);

    return (
      <div>
        <h3>{title}</h3>
        <table className="min-w-full border">
          <thead>
            <tr className="bg-gray-100">
              {Object.keys(filteredData[0] || {}).map(k => <th key={k} className="border p-2">{k}</th>)}
            </tr>
          </thead>
          <tbody>
            {filteredData.map((row: any, i: number) => (
              <tr key={i}>
                {Object.values(row).map((v: any, j) => <td key={j} className="border p-2">{String(v)}</td>)}
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  },
  Chart: (props: any) => <MyChartLibrary {...props} />
};

export const ZentisChat = () => {
  const [response, setResponse] = React.useState<AgentResponse | null>(null);

  const handleQuery = async (text: string) => {
    // res.components[0].props.data is already populated with the full dataset
    const res = await agent.query(text);
    setResponse(res);
  };

  return (
    <div className="p-4">
      {/* 1. Render the Conversational Text */}
      <p className="mb-4">{response?.text}</p>

      {/* 2. Render Auto-populated UI Components */}
      {response?.components.map((comp, i) => {
        const Renderer = ComponentMap[comp.name];
        return Renderer ? <Renderer key={i} {...comp.props} /> : null;
      })}
    </div>
  );
};

Quick Start

import { ZentisAgent, ZentisMcpClient } from 'zentis';

// 1. Initialize Agent with LLM, Storage, and MCP configs
const agent = new ZentisAgent({
  llm: { 
    apiKey: process.env.ZEN_API_KEY || process.env.GEMINI_API_KEY,
    baseURL: process.env.ZEN_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta/openai/',
    model: process.env.ZEN_MODEL || 'gemini-3-flash-preview'
  },
  storage: { 
    type: 'sqlite', 
    connectionString: process.env.DB_CONNECTION_URL || 'zentis.db',
    userId: 'user_123',
    sessionId: 'session_abc' // Optional: Scope history to a specific session
  },
  mcp: { 
    name: 'primary', 
    url: 'http://localhost:8001/sse'
  },
  tool_router: true,         // Enable internal tool routing to avoid prompt bloat
  maxHistoryMessages: 10     // Set a custom sliding window for history
});

// 2. Query the agent with real-time lifecycle hooks
// ALWAYS call waitReady() before querying to ensure all MCP servers are connected
await agent.waitReady();

const response = await agent.query("Check the status of the front yard camera", {
  onStep: (step) => {
    console.log(`[${step.type}] ${step.message}`);
    if (step.data) console.log('Data:', step.data);
  },
  onAction: (action) => {
    console.log(`Executing ${action.tool} on ${action.server}`);
  },
  extraArgs: {
    API_auth: 'your-frontend-token-here'
  }
});

// Optional: Override model for a specific query
const flashResponse = await agent.query("Quick summary", { model: "llama-3.1-8b-instant" });

Core Concepts

1. Multi-Server Connectivity

Zentis uses a singleton ZentisMcpClient to manage connections. You can connect to multiple servers in parallel, each with its own transport and authentication settings.

const client = ZentisMcpClient.getInstance();

await client.connectMany([
  { 
    name: 'analytics', 
    url: 'https://api.analytics.com/mcp',
    options: { 
      transportType: 'sse', 
      headers: { 'Authorization': process.env.MCP_TOKEN } 
    }
  },
  { 
    name: 'local-tools', 
    url: 'http://localhost:8080/mcp',
    options: { transportType: 'http' }
  }
]);

// Tools from ALL servers are automatically available to the agent
const tools = await agent.listAvailableTools();

2. Autonomous Reasoning

Zentis implements a multi-turn reasoning loop with optional perception and planning layers. You can control how the agent perceives and plans its actions:

const agent = new ZentisAgent({
  // ...
  tool_router: true,  // Perception: keyword-based tool filtering (saves tokens)
  planner: true       // Planning: dedicated LLM turn to pre-select tools
});
  • Smart Tool Routing: If tool_router is enabled, Zentis uses regex heuristics to inject only the most relevant tools into the prompt, preventing "tool noise" and saving context window.
  • Batch & Recursive Execution: Zentis supports executing multiple tools in a single turn. If an LLM emits multiple [CALL:...] tags, Zentis runs them all and returns the results as separate entries, enabling complex tasks like "Turn off all 5 smart lights" to happen in one step.
  • Experimental Planner: When planner is true, Zentis runs a hidden initial LLM turn to identify the exact sequence of tools needed. This significantly improves accuracy for complex, multi-step goals.
  • Observability: Use the onStep callback for real-time lifecycle tracking.

Recursion Guard: To ensure stability, Zentis tracks every tool call made in a reasoning loop. If the agent attempts to call the exact same tool with the exact same arguments twice, Zentis will block the call and notify the agent to prevent an infinite loop.

Tip: Zentis automatically sanitizes tool call IDs and patches schemas to ensure compatibility with strict providers like Gemini/Google.

Tip: Use the onAction option to listen for tool executions in real-time.

3. Smart Memory & Custom Personas (Zero-Identity)

Zentis avoids "library bloat" by not injecting any hardcoded identity, instructions, or UI help. This Zero-Identity Architecture gives you full control. Use the "notes stack" to define your agent's persona and rules.

// Define Identity
await agent.note("You are a helpful Security Assistant named Sentinel."); 
await agent.note("You have access to the Sherlock MCP server.");

// Define Operational Rules
await agent.note("If multiple cameras are found, always list their status in a [UI:Table].");
await agent.note("ALWAYS respond in a professional tone.");

// The agent's core 'system prompt' is built from these notes.
const currentInstructions = agent.getNotes();

Multi-Turn Planning

You can use notes to guide the planning phase as well:

await agent.note("Before using any tools, explain your plan to the user briefly.");

4. Extra Arguments (Sensitive Tokens)

Zentis allows you to pass custom arguments (like auth tokens or session IDs) directly to your tools without exposing them to the LLM. These are passed via the extraArgs option in the query method.

const response = await agent.query("Get my cameras", {
  extraArgs: {
    API_auth: "eyJhbGciOi...", // Hidden from LLM
    nodeId: 101
  }
});

How it works:

  1. Schema Scrubbing: Zentis removes API_auth and nodeId from the tool definition before sending it to the LLM. The LLM doesn't even know these parameters exist.
  2. Automatic Injection: When the LLM calls the tool, Zentis automatically injects your extraArgs into the arguments before execution.
  3. Security: Prevents the LLM from hallucinating or attempting to manipulate sensitive session/auth parameters.
  • Strict Overriding: extraArgs always take precedence.
  • Flexibility: You can name the keys anything and set their types to any valid JSON value.

Memory & Storage Backends

Zentis supports multiple storage adapters. Configure them via the storage option in the ZentisAgent constructor. All backends use a composite key (userId + sessionId) to ensure strict session isolation across parallel conversations.

| Type | Environment | Isolation | Key Features | | :--- | :--- | :--- | :--- | | sqlite | Node.js | Composite PK | Fast, file-based persistence | | postgres | Node.js | Composite PK | Scalable, SSL-ready storage |

Examples

SQLite (Node.js)

const agent = new ZentisAgent({
  storage: {
    type: 'sqlite',
    connectionString: './data.db', // default: 'zentis.db'
    userId: 'user_789'
  }
});

PostgreSQL (Node.js)

// Option 1: Connection String
const agent = new ZentisAgent({
  storage: {
    type: 'postgres',
    connectionString: 'postgresql://user:pass@localhost:5432/db',
    ssl: { rejectUnauthorized: false }, 
    userId: 'user_999'
  }
});

// Option 2: Existing PG Pool Instance
import { Pool } from 'pg';
const myPool = new Pool({ ... });

const agent = new ZentisAgent({
  storage: {
    type: 'postgres',
    pool: myPool, // Pass your own pre-configured pool
    userId: 'user_999'
  }
});

UI Components (Browser Integration)

Agents can "trigger" UI components in the frontend. Zentis automatically parses these into a structured components array.

Syntax

The agent should respond with: [UI:ComponentName]{"props": "here"}[/UI].

Usage

const response = await agent.query("Show me the map of London");

// response.text -> "Here is the map you requested:"
// response.components -> [{ name: "Map", props: { lat: 51.5, lng: -0.12 } }]

Frontend Integration (React Example)

You can easily map Zentis components to your own UI library:

const ChatMessage = ({ response }) => {
  return (
    <div>
      <p>{response.text}</p>
      
      {response.components.map((comp, i) => {
        switch(comp.name) {
          case 'Map': return <MyMap key={i} {...comp.props} />;
          case 'VideoPlayer': return <MyVideo key={i} {...comp.props} />;
          case 'Table': return <MyTable key={i} {...comp.props} />;
          default: return null;
        }
      })}
    </div>
  );
};

UI Actions (Web API)

Zentis allows the agent to interact with the frontend by triggering specific actions like highlighting, clicking, or focusing elements.

Syntax

[ACTION:Type]{"targetId": "element-id", "metadata": {}}[/ACTION]

Example

const response = await agent.query("Highlight the submit button");

// response.actions -> [{ type: "highlight", targetId: "submit-btn" }]

Supported Actions

  • highlight: Focus attention on a specific element.
  • click: Trigger a programmatic click.
  • focus: Set focus to an input field.
  • scroll: Scroll an element into view.
  • custom: Pass arbitrary events to your frontend.

Token & Tool Optimization

Zentis is built for production efficiency.

1. Smart History Optimization

To keep storage clean and token usage low, Zentis only saves final responses and user queries to permanent storage. Intermediate tool calls and raw results are kept in memory only for the duration of the current reasoning loop.

Supported Default Components

  • DetectionGallery: { "title": string, "data": any[] }
  • VideoPlayer: { "url": string, "title": string, "className": string }
  • Map: { "lat": number, "lng": number, "zoom": number, "className": string }
  • Chart: { "type": "bar" | "line", "data": any[], "className": string }
  • Table: { "headers": string[], "rows": any[][], "title": string, "className": string }

License

ISC