loading…
Search for a command to run...
loading…
A high-level agentic framework for Model Context Protocol (MCP) with memory and LLM integration.
A high-level agentic framework for Model Context Protocol (MCP) with memory and LLM integration.
A high-level agentic framework for Model Context Protocol (MCP) with built-in memory management and LLM integration.
search_tools virtual tool when managing 20+ tools to keep LLM context small and fast.npm install zentis
Note: If using SQLite or Postgres, install the respective drivers:
npm install better-sqlite3 # for SQLite
npm install pg # for PostgreSQL
The ZentisAgent is highly configurable to balance between reasoning depth and performance.
const agent = new ZentisAgent({
// Tool Routing & Planning
tool_router: true, // Filters tools sent to LLM based on query keywords
planner: true, // Uses a separate LLM turn to pre-plan the required tools
// Performance & Loops
maxTurns: 15, // Max reasoning loops (default 10, range 1-50)
maxHistoryMessages: 30, // Number of history messages to keep in context
// LLM Config
llm: {
apiKey: '...',
model: 'gemini-3-flash-preview'
},
// MCP Servers
mcp: [
{ name: 'Analytics', url: 'https://mcp-analytics.example.com/sse' },
{ name: 'Database', url: 'http://localhost:3001/mcp' }
]
});
When connecting to many MCP servers, the tool definitions can easily exceed the LLM's context window or make it slow/expensive.
Zentis automatically optimizes this:
search_tools virtual tool. The LLM can use this to find the exact tools it needs, which are then injected into the context dynamically for subsequent turns.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.
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: 'SalesChart',
description: 'Display sales trends and forecasts.',
props: {
region: { type: 'string', description: 'Region name', required: true },
revenue: { type: 'number', description: 'Current revenue' },
isProfitable: { type: 'boolean', description: 'Whether the region is profitable' },
forecast: {
type: 'data_reference',
description: 'Reference to the sales forecast data list from a tool'
},
filters: {
type: 'object',
description: 'Dynamic filters to apply to the data'
}
}
});
const agent = new ZentisAgent({ ui, ... });
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_sales) instead of the raw data. Zentis will automatically swap this ID for the actual data before returning the response to your frontend.In your React application, you can map the registered components to your actual UI library. Zentis automatically resolves data_reference IDs into actual data arrays before returning the response.
import React from 'react';
import { AgentResponse } from 'zentis';
// Example UI Component Map
const ComponentMap = {
SalesChart: ({ region, revenue, forecast }: any) => (
<div className="p-4 border rounded shadow">
<h2>Sales in {region}</h2>
<p className="text-2xl font-bold">${revenue}</p>
{forecast && (
<ul className="mt-2 text-sm">
{forecast.map((f: any, i: number) => (
<li key={i}>{f.period}: ${f.amount}</li>
))}
</ul>
)}
</div>
),
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 className="overflow-x-auto">
<h3 className="mb-2 font-semibold">{title}</h3>
<table className="min-w-full border text-sm">
<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>
);
}
};
export const ZentisChatView = () => {
const [response, setResponse] = React.useState<AgentResponse | null>(null);
const handleQuery = async (text: string) => {
// res.components[0].props.forecast is already populated with the full dataset
// thanks to Zentis' automatic resolution of data_reference types.
const res = await agent.query(text);
setResponse(res);
};
return (
<div className="p-4 space-y-4">
{/* 1. Render the Conversational Text */}
{response && <p className="text-gray-800">{response.text}</p>}
{/* 2. Render Auto-populated UI Components */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{response?.components.map((comp, i) => {
const Renderer = ComponentMap[comp.name as keyof typeof ComponentMap];
return Renderer ? <Renderer key={i} {...comp.props} /> : null;
})}
</div>
</div>
);
};
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 inventory levels", {
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" });
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();
Zentis implements a multi-turn reasoning loop with optional perception and planning layers.
When connecting to many MCP servers, the tool definitions can easily exceed the LLM's context window. Zentis automatically optimizes this:
search_tools virtual tool. The LLM uses this to find the exact tools it needs, which are then injected into the context dynamically for subsequent turns.const agent = new ZentisAgent({
// ...
tool_router: true, // Perception: keyword-based tool filtering
planner: true // Planning: dedicated LLM turn to pre-select tools
});
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.[CALL:...] tags, Zentis runs them all in parallel.Zentis provides deep visibility into the agent's reasoning loop. You can use onStep to build real-time "Thinking..." UIs or audit logs.
const response = await agent.query("Analyze the sales for Q1", {
onStep: (step) => {
switch (step.type) {
case 'thinking':
console.log("🤔 Agent is thinking:", step.message);
break;
case 'tool_call':
console.log(`🛠️ Calling tool: ${step.data.tool}`);
break;
case 'tool_result':
console.log("✅ Tool returned data");
break;
case 'complete':
console.log("🏁 Agent finished reasoning");
break;
}
},
onAction: (action) => {
// Fired just before a tool is executed
console.log(`Executing ${action.tool} with args:`, action.args);
}
});
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 Personal Assistant named Zen.");
await agent.note("You have access to the primary MCP server.");
// Define Operational Rules
await agent.note("If multiple records 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();
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.");
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 records", {
extraArgs: {
API_auth: "eyJhbGciOi...", // Hidden from LLM
nodeId: 101
}
});
How it works:
API_auth and nodeId from the tool definition before sending it to the LLM. The LLM doesn't even know these parameters exist.extraArgs into the arguments before execution.Zentis uses a composite key (userId + sessionId) to ensure strict isolation across parallel conversations. This allows you to maintain multiple independent chat sessions for the same user.
// Session A
const agentA = new ZentisAgent({
storage: { type: 'sqlite', userId: 'user_1', sessionId: 'session_A' }
});
await agentA.query("My name is Alice");
// Session B
const agentB = new ZentisAgent({
storage: { type: 'sqlite', userId: 'user_1', sessionId: 'session_B' }
});
const res = await agentB.query("What is my name?");
// res.text -> "I don't know your name yet."
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 |
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'
}
});
Agents can "trigger" UI components in the frontend. Zentis automatically parses these into a structured components array.
The agent should respond with: [UI:ComponentName]{"props": "here"}[/UI].
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 } }]
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>
);
};
Zentis is built for production efficiency.
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.
{ "url": string, "title": string, "className": string, "fullWidth": boolean }{ "lat": number, "lng": number, "zoom": number, "className": string, "fullWidth": boolean }{ "type": "bar" | "line", "data": any[], "className": string, "fullWidth": boolean }{ "headers": string[], "rows": any[][], "title": string, "className": string, "fullWidth": boolean }ISC
Выполни в терминале:
claude mcp add zentis -- npx -y zentisWeb content fetching and conversion for efficient LLM usage.
Retrieval from AWS Knowledge Base using Bedrock Agent Runtime.
автор: modelcontextprotocolProvides auto-configuration for setting up an MCP server in Spring Boot applications.
A very streamlined mcp client that supports calling and monitoring stdio/sse/streamableHttp, and can also view request responses through the /logs page. It also
автор: xuzexin-hzНе уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории ai