@lotagate/agent-sdk
v0.1.13
Published
Provider-neutral LotaGate agent runtime with composable tool execution and MCP capabilities.
Maintainers
Readme
@lotagate/agent-sdk
@lotagate/agent-sdk is a provider-neutral host-side runtime for building
governed LotaGate agents. It owns the agent loop, streaming model lifecycle,
tool catalog, authorization policy, session state, context management, and
observability.
MCP is one built-in tool source. Applications can compose it with their own
tool executors, such as a CLI filesystem executor or a controlled shell
executor, without adding terminal or operating-system concerns to this
package. The SDK does not turn the LotaGate API server into an MCP client and
does not modify the separate @lotagate/sdk package.
Table of contents
- Requirements
- Installation
- Quick start
- Tool execution abstraction
- Multimodal input and tool media
- MCP server configuration
- Supported MCP operations
- Protocol negotiation
- Dynamic MCP lifecycle
- Timeouts, cancellation, retry, and reconnect
- Authentication
- Client-to-server handlers
- Tool policy and security
- Streaming and lifecycle events
- Model and tool loop
- Agent lifecycle and failure handling
- Session state
- Direct MCP client management
- MCP resource and prompt context
- Conversation context management
- Advanced API usage
- Errors
- Public exports and TypeScript types
- Known limitations
- Package and licensing
Requirements
- Node.js 22 or newer.
- An ESM-compatible application (
"type": "module"). - A LotaGate API key for the model client.
@lotagate/sdk0.1.7 or newer as the peer dependency used to call the LotaGate API and provide the Responses input/output contracts.
The agent SDK does not read credentials from the environment itself. The host
application should pass the key to @lotagate/sdk; when using the conventional
environment-based setup, use LOTA_GATE_API_KEY.
The package uses the official @modelcontextprotocol/client v2 internally.
The package dependency is installed automatically. Install it directly only
when application code needs to import its provider or protocol types.
Installation
npm install @lotagate/agent-sdkFor pnpm:
pnpm add @lotagate/agent-sdkFor yarn:
yarn add @lotagate/agent-sdkQuick start
The agent receives an existing LotaGate SDK client through modelClient. MCP
servers are connected and their tools are discovered lazily on the first run,
or explicitly through initialize().
import { LotaGate } from '@lotagate/sdk';
import { LotaGateAgent } from '@lotagate/agent-sdk';
const modelClient = new LotaGate({
apiKey: process.env.LOTA_GATE_API_KEY!,
});
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
mcpServers: {
filesystem: {
type: 'stdio',
command: 'your-mcp-server',
args: ['--safe-mode'],
},
remote: {
type: 'http',
url: 'https://mcp.example.com/mcp',
auth: {
type: 'bearer',
token: process.env.REMOTE_MCP_TOKEN!,
},
},
},
toolPolicy: {
allowedTools: ['mcp__filesystem__list_files'],
requireApproval: true,
approvalHandler: async (request) => request.toolInfo?.metadata?.['serverId'] === 'filesystem',
},
});
try {
for await (const event of agent.run({
input: 'List the available files.',
stream: true,
})) {
if (event.type === 'model.delta') process.stdout.write(event.content);
if (event.type === 'session.failed') console.error(event.error);
}
} catch (error) {
// run() emits session.failed and rethrows the terminal error.
console.error('Agent run failed:', error);
} finally {
// Close long-lived MCP connections when the host shuts down the agent.
await agent.close();
}The same agent can receive host-provided tools through toolExecutor. The
host remains responsible for implementing operating-system-specific behavior;
the agent runtime only coordinates discovery, policy, execution, and model
messages.
import type { ToolExecutor } from '@lotagate/agent-sdk';
const filesystemExecutor: ToolExecutor = {
listTools: () => [{
type: 'function',
function: {
name: 'filesystem_read',
description: 'Read a UTF-8 text file inside the approved workspace.',
parameters: {
type: 'object',
properties: { path: { type: 'string' } },
required: ['path'],
},
},
}],
execute: async (toolName, input, context) => {
// Implement and validate the host-specific filesystem operation here.
void toolName;
void context;
return { content: String(input.path), isError: false };
},
};
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
toolExecutor: filesystemExecutor,
});Tool execution abstraction
ToolExecutor is the integration boundary for model-callable tools:
interface ToolExecutor {
listTools(): readonly ToolDefinition[];
execute(
toolName: string,
input: Record<string, unknown>,
context: ToolExecutionContext,
): Promise<ToolResult>;
getToolInfo?(toolName: string): ToolInfo | undefined;
initialize?(): Promise<void>;
close?(): Promise<void>;
}ToolExecutionContext contains the session, run, iteration, cancellation,
workspace and result-limit information needed by a host executor. It never
stores API credentials. ToolInfo allows the host to expose a source and
safe metadata for policy and observability.
The package includes:
McpToolExecutor, which adapts the existing MCP client and discovery flow.CompositeToolExecutor, which routes unique model tool names to multiple executors and rejects name collisions.ToolCatalog, which maintains the model-facing definitions.
The package intentionally does not include a filesystem or shell executor. Those implementations belong to the host application because their security policy, working directory, trust model, process spawning and platform behavior are host-specific.
When toolExecutor is supplied together with mcpServers, the agent composes
the MCP executor and the supplied executor automatically. If no custom
executor is supplied, the MCP executor is used directly.
Security requirements:
- Keep API keys and MCP tokens on the server side.
- Do not place credentials in source control, URLs, browser bundles, or client-visible events.
- Use
allowedTools,deniedTools, and approval handlers for production tool authorization.
Multimodal input and tool media
AgentRunRequest.inputContent can replace the plain input string with the
structured text content accepted by @lotagate/sdk. This is useful when a host
already has an image or another supported content part and wants it included in
the user message sent to the model:
for await (const event of agent.run({
input: 'Describe the attached screenshot.',
inputContent: [
{ type: 'text', text: 'Describe the attached screenshot.' },
{
type: 'image_url',
image_url: {
url: 'data:image/png;base64,<bounded-base64-data>',
detail: 'auto',
},
},
],
})) {
void event;
}Host-provided executors can return bounded images with ToolResult.contentBlocks:
return {
content: JSON.stringify({ path: 'screenshot.png', mimeType: 'image/png' }),
isError: false,
contentBlocks: [{
type: 'image',
mimeType: 'image/png',
dataBase64: '<bounded-base64-data>',
width: 1280,
height: 720,
}],
};The SDK converts each image block to the model client's image_url content
part for the next model request. The human-readable content remains the
authoritative textual summary. Binary blocks are intentionally not persisted
in the session store; hosts should keep them bounded by the configured result
limit and apply their own sensitive-data redaction policy.
The model request is serialized by @lotagate/sdk and is subject to the
Gateway JSON body limit of 25 MB decimal on /v1 and /v1beta. This is
separate from the Agent SDK's maxToolResultBytes limit and context/token
limits.
MCP server configuration
mcpServers is a map keyed by a stable server ID. Server IDs must begin with a
letter or number and may contain letters, numbers, ., _, and -; the
maximum length is 64 characters.
Stdio server
const mcpServers = {
localTools: {
type: 'stdio' as const,
command: 'node',
args: ['/opt/mcp/local-server.mjs'],
cwd: '/opt/mcp',
env: {
NODE_ENV: 'production',
},
maxBufferSize: 1024 * 1024,
},
};The command is started by the host process. Use an absolute executable or a controlled runtime path in production, and pass only the environment variables the server requires.
Streamable HTTP server
const mcpServers = {
remoteTools: {
type: 'http' as const,
url: 'https://mcp.example.com/mcp',
headers: {
'x-application-name': 'my-agent-service',
},
auth: {
type: 'bearer' as const,
token: process.env.MCP_TOKEN!,
},
},
};Remote endpoints must use HTTPS. Plain HTTP is accepted only for local
development hosts (localhost, 127.0.0.1, or ::1). Credentials embedded in
the URL are rejected.
Legacy SSE compatibility
const legacyServer = {
type: 'sse' as const,
url: 'https://legacy-mcp.example.com/sse',
};SSE is retained as a compatibility transport behind the MCP transport factory.
New integrations should use type: 'http' whenever the server supports
Streamable HTTP.
Supported MCP operations
The official MCP client boundary currently exposes these server operations:
| Operation | Runtime method | Purpose |
| --- | --- | --- |
| Tools | listTools, callTool | Discover and execute model-callable tools |
| Resources | listResources, readResource | Discover and read contextual resources |
| Resource templates | listResourceTemplates | Discover parameterized resource URIs |
| Prompts | listPrompts, getPrompt | Discover and retrieve reusable prompt templates |
| Completion | complete | Request completion values for supported MCP references |
| Resource subscriptions | subscribeResource, unsubscribeResource | Subscribe or unsubscribe from resource updates |
| Notifications | listen | Listen to supported MCP notifications through a subscription filter |
Tools discovered from server serverId and tool name are exposed to the
model using the deterministic name:
mcp__<serverId>__<toolName>The original server ID and tool name remain available in
McpDiscoveredTool, so authorization and execution do not depend on parsing a
model-generated name.
Protocol negotiation
The default mode is modern-first automatic negotiation with legacy fallback:
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
protocol: {
mode: 'auto',
probeTimeoutMs: 1_000,
probeMaxRetries: 2,
},
});The mcpClient option forwards official MCP client settings that are not
specific to a transport:
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
mcpServers,
mcpClient: {
// Maximum number of pages fetched by paginated list operations.
listMaxPages: 20,
// Cache lifetime used by the official MCP client where supported.
defaultCacheTtlMs: 5 * 60 * 1000,
// Refresh the tools cache after tools/list_changed notifications.
listChanged: { autoRefresh: true, debounceMs: 300 },
},
});capabilities can also be supplied when the host application needs to
advertise additional official MCP client capabilities. Capabilities required
by configured mcpHandlers are derived and advertised automatically.
Supported policies:
{ mode: 'auto' } // Default: modern first, then fallback
{ mode: 'legacy' } // Force the legacy protocol era
{ mode: { pin: '2026-07-28' } } // Pin one protocol versionprobeTimeoutMs and probeMaxRetries must be non-negative integers. Use
legacy or pin only when required by a known server compatibility contract.
Dynamic MCP lifecycle
The official MCP client refreshes its tools cache when a connected server sends
notifications/tools/list_changed. The agent updates ToolRegistry and
reports mcp.tool.catalog.updated through observability. Servers must advertise
the corresponding listChanged capability for notification refresh to work.
MCP servers can also be managed after construction:
await agent.mcp.addServer('runtime', {
type: 'http',
url: 'https://mcp.example.com/mcp',
}, { connect: true });
await agent.mcp.updateServer('runtime', newConfig, { reconnect: true });
await agent.mcp.removeServer('runtime');addServer() and reconnecting updateServer() discover tools. Updating or
removing a server removes its old model-facing tools first.
Timeouts, cancellation, retry, and reconnect
MCP operations accept McpOperationOptions. signal, timeoutMs, and
maxTotalTimeoutMs are forwarded to the official MCP request. Retries are
disabled by default and can be configured per operation or as agent defaults:
const result = await agent.mcp.callTool('runtime', 'search', { query: 'MCP' }, {
signal: abortController.signal,
timeoutMs: 15_000,
maxTotalTimeoutMs: 45_000,
retry: { maxAttempts: 3, initialDelayMs: 250, jitterRatio: 0.2 },
});
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
mcpOperation: { timeoutMs: 30_000, maxTotalTimeoutMs: 90_000 },
mcpRetry: {
maxAttempts: 3,
initialDelayMs: 250,
retryableOperations: ['listTools', 'readResource', 'getPrompt'],
},
mcpReconnect: {
enabled: true,
maxAttempts: 5,
initialDelayMs: 500,
maxDelayMs: 10_000,
healthCheckIntervalMs: 30_000,
healthCheckTimeoutMs: 5_000,
},
});Read/list operations are eligible when retries are enabled. callTool and
other non-idempotent operations require retryNonIdempotent: true, preventing
accidental duplication of side effects. Cancellation is never retried.
Authentication
Bearer token
auth: {
type: 'bearer',
token: process.env.MCP_TOKEN!,
}Official authentication provider
For OAuth, token refresh, or a custom official provider, pass an
AuthProvider or OAuthClientProvider from the official MCP client:
import type { AuthProvider } from '@modelcontextprotocol/client';
const authProvider: AuthProvider = {
token: async () => getCurrentAccessToken(),
};
const mcpServers = {
remoteTools: {
type: 'http' as const,
url: 'https://mcp.example.com/mcp',
auth: {
type: 'provider' as const,
provider: authProvider,
},
},
};The agent SDK passes the provider to the official transport boundary. It does not persist, refresh, or log credentials itself.
Client-to-server handlers
MCP servers may call back into the client for sampling, elicitation, roots, or
logging. Configure handlers through mcpHandlers:
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
mcpServers,
mcpHandlers: {
roots: async () => ({
roots: [{ uri: 'file:///srv/workspace', name: 'workspace' }],
}),
elicitation: async () => ({ action: 'cancel' }),
sampling: async (request) => {
// Route this request to the host application's approved model service.
return await hostSamplingService.createMessage(request);
},
logging: async (notification) => {
applicationLogger.info({ notification }, 'MCP server notification');
},
},
});When a handler is configured, the required client capability is advertised automatically. Handler failures are returned through the official MCP request path and should be handled by the host application according to its policy.
Tool policy and security
Tool authorization is enforced before a configured executor is called. The same policy applies to MCP, filesystem, shell and custom host executors.
toolPolicy: {
// Optional allow-list. If set, all other tools are denied.
allowedTools: [
'mcp__filesystem__list_files',
],
// Explicit deny rules take precedence over an allow-list.
deniedTools: [
'mcp__filesystem__delete_file',
],
// Optional, host-defined rules evaluated after static policy and before
// approval. Rules stay independent of a particular UI or workflow.
authorizationRules: [{
id: 'tenant-boundary',
authorize: async (request) => {
const allowed = await tenantPolicy.canUseTool(request.context.sessionId, request.toolName);
return allowed
? { decision: 'allow', reason: 'tenant-match' }
: { decision: 'deny', reason: 'tenant-mismatch' };
},
}],
// Optional human or service approval gate.
requireApproval: true,
approvalHandler: async (request) => {
return await policyService.approveToolCall({
tool: request.toolName,
source: request.toolInfo?.source,
arguments: request.input,
sessionId: request.context.sessionId,
workspaceRoot: request.context.workspaceRoot,
});
},
},Policy evaluation is fail-closed and ordered: static deny/allow lists, then authorization rules, then the optional approval handler, then the executor. A denying rule prevents the approval handler and executor from running.
An authorization rule or approvalHandler may also return a redirect decision
with a non-empty message. The agent records the feedback as a safe tool
result, does not run the redirected tool, and continues the same agent run with
the message as a new user instruction. This is useful for interactive hosts
that let a user change the requested action without starting a second turn.
approvalHandler may return either a boolean (true/false) or a
ToolAuthorizationDecision with allow, deny, or redirect; boolean
results retain their existing allow/deny behavior.
authorizationObserver receives the final decision for each tool call. Its
event includes tool identity, execution IDs, stage, decision, reason, rule ID and any
rule-supplied safe metadata; it intentionally excludes tool arguments. The
observer is best-effort and cannot change the authorization outcome.
Default agent limits:
| Limit | Default | Purpose |
| --- | ---: | --- |
| maxIterations | 8 | Maximum model/tool loop iterations |
| maxToolCallsPerTurn | 8 | Maximum tool calls in one model turn |
| maxToolArgumentBytes | 262144 | Maximum UTF-8 bytes of model tool arguments |
| maxToolResultBytes | 2097152 | Maximum UTF-8 bytes returned by any tool executor |
Override them globally or per run:
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
limits: {
maxIterations: 12,
maxToolCallsPerTurn: 4,
maxToolArgumentBytes: 128 * 1024,
maxToolResultBytes: 4 * 1024 * 1024,
},
});
agent.run({
input: 'Use the approved tools only.',
limits: { maxIterations: 4 },
});Streaming and lifecycle events
run() always returns an AsyncIterable<AgentEvent>. Set stream: true to
receive model text deltas while the model response is being assembled. Provider
metadata attached to streamed tool calls is preserved in the assistant message
history so provider-specific multi-turn tool protocols remain intact.
for await (const event of agent.run({
input: 'Summarize the project status.',
system: 'Be concise and factual.',
stream: true,
signal: abortController.signal,
})) {
switch (event.type) {
case 'model.delta':
process.stdout.write(event.content);
break;
case 'tool.called':
console.log(`Calling ${event.toolName}`);
break;
case 'session.completed':
console.log('\nCompleted:', event.content);
break;
case 'session.failed':
console.error(event.error);
break;
}
}Events include an ISO timestamp and use a discriminated type field:
| Event | Meaning |
| --- | --- |
| session.started | A new agent session has started |
| mcp.server.status | MCP connection status or degraded initialization result |
| mcp.tool.discovered | A tool was discovered and registered |
| mcp.tool.catalog.updated | A server's tools catalog was refreshed |
| mcp.server.configuration.changed | A server was added, updated, or removed |
| context.compacted | Conversation context was trimmed or summarized |
| model.delta | A streamed model text fragment |
| model.completed | A model turn has been assembled |
| tool.approval.requested | A tool is waiting for approval |
| tool.called | An approved tool call is being sent |
| tool.completed | A tool call returned, including isError |
| session.completed | The agent produced a final answer |
| session.failed | The run failed and includes an Error |
Model and tool loop
The agent uses one model loop for both user-facing text and tool execution. A model response may contain text, tool calls, or both: text is streamed to the host, and tool calls are executed in response order. After each tool result the loop sends the updated transcript back to the model until it produces a final response without tool calls.
The model boundary uses modelClient.responses.create() for both buffered and
streamed turns. Agent requests use the Responses input contract, and the
session transcript retains response output items, native function_call items,
and matching function_call_output items with their call_id. Provider
metadata attached to streamed function calls is retained so providers that
require metadata on later tool turns can continue correctly.
Every assistant response and tool result is retained in the session transcript.
Observer callbacks are optional. Observer failures are isolated so telemetry cannot silently change the agent's business outcome:
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
observability: {
onEvent: async (event) => telemetry.publish(event),
onError: async (error) => telemetry.report(error),
},
});Do not send raw tool arguments or credentials to an external telemetry system without applying the application's data-classification and redaction policy.
Session state
Every run receives a generated session ID unless sessionId is supplied. A
session can be reused for multiple conversational turns; the SDK loads the
existing messages and appends the next user input instead of recreating the
session.
const events = agent.run({
sessionId: 'request-123',
input: 'Inspect the repository.',
});
for await (const event of agent.run({
sessionId: 'request-123',
model: 'another-model',
input: 'Now summarize the risks.',
})) {
console.log(event.type);
}model on AgentRunRequest overrides the agent default for that run. This is
useful for host applications that provide a model picker without mutating a
shared agent instance. Concurrent runs for the same session are rejected so a
host cannot silently interleave conversation history.
reasoningEffort on AgentRunRequest sets the reasoning effort for that run.
The agent forwards it to the Responses request as reasoning: { effort }:
for await (const event of agent.run({
input: 'Analyze the repository architecture.',
reasoningEffort: 'high',
})) {
console.log(event.type);
}The default InMemorySessionStore tracks status, messages, iteration, update
timestamps, and final content. It is appropriate for one process. For
multi-replica production deployments, inject a durable implementation:
import type {
AgentSessionState,
SessionStore,
} from '@lotagate/agent-sdk';
const sessionStore: SessionStore = {
async create(state: AgentSessionState) {
await database.insertSession(state);
},
async get(sessionId) {
return await database.getSession(sessionId);
},
async update(sessionId, patch) {
await database.updateSession(sessionId, patch);
},
async delete(sessionId) {
await database.deleteSession(sessionId);
},
};
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
sessionStore,
});
const state = await agent.sessions.get('request-123');The SDK does not prescribe database schema, retention, encryption, or cross-replica locking. The injected store owns those production concerns.
Agent lifecycle and failure handling
run() is an async iterable. It lazily initializes configured MCP servers on
the first run unless initialize() was called explicitly:
const initializationEvents = await agent.initialize();
for (const event of initializationEvents) {
console.log(event.type);
}
for await (const event of agent.run({ input: 'Use the discovered tools.' })) {
console.log(event.type);
}Initialization events include MCP connection status and discovered tools. A
failed connection is represented in the returned summary and does not prevent
other configured servers from being attempted by connectAll().
MCP startup and tool discovery use bounded concurrency with a default of four
servers at a time. Hosts that coordinate multiple MCP runtimes can reuse the
exported mapWithMcpConcurrency helper and provide a different positive limit
when their resource budget requires it.
During a run, the agent emits session.failed, updates the session state to
failed, and rethrows the terminal error. Consumers should therefore use
try/catch around the async iteration. When the host no longer needs the
connections, call:
await agent.close();The agent does not automatically close MCP connections after every run because the same agent instance may be reused for multiple requests.
Direct MCP client management
Use LotaGateAgent for the normal model/tool loop. Use McpClientManager
directly when the application needs MCP operations without an agent:
import { McpClientManager } from '@lotagate/agent-sdk/mcp';
const manager = new McpClientManager(mcpServers, {
protocol: { mode: 'auto' },
handlers: {
roots: async () => ({ roots: [] }),
},
});
try {
const summaries = await manager.connectAll();
const tools = await manager.listTools('filesystem');
const result = await manager.callTool('filesystem', 'list_files', {});
const resources = await manager.listResources('filesystem');
const templates = await manager.listResourceTemplates('filesystem');
const resource = await manager.readResource('filesystem', 'file:///srv/workspace/README.md');
const prompts = await manager.listPrompts('filesystem');
const prompt = await manager.getPrompt('filesystem', 'summarize', { language: 'en' });
const completion = await manager.complete('filesystem', {
ref: { type: 'ref/prompt', name: 'summarize' },
argument: { name: 'language', value: 'e' },
});
await manager.ping('filesystem');
await manager.subscribeResource('filesystem', 'file:///srv/workspace/README.md');
const subscription = await manager.listen('filesystem', {
resourcesListChanged: true,
resourceSubscriptions: ['file:///srv/workspace/README.md'],
});
await subscription.close();
await manager.unsubscribeResource('filesystem', 'file:///srv/workspace/README.md');
console.log({ summaries, tools, result, resources, templates, resource, prompts, prompt, completion });
} finally {
await manager.disconnectAll();
}complete() accepts a prompt or resource-template reference and returns the
completion values supported by the server. listen() uses the modern
subscription stream and is available only when the negotiated protocol
supports it. For a single-server lower-level boundary, retrieve the connected
connection and use the same operation methods directly:
const connection = manager.getConnection('filesystem');
console.log(connection.status, connection.protocolEra, connection.protocolVersion);
const tools = await connection.listTools();McpConnection is available for lower-level single-server control. The
manager is preferred because it centralizes connection lookup, partial failure
handling, and lifecycle cleanup.
MCP resource and prompt context
The agent loop can load selected MCP resources and prompts before its first model request. Selection is explicit and byte-limited so a server cannot cause an unbounded context dump:
for await (const event of agent.run({
input: 'Summarize the supplied context.',
mcpContext: {
maxBytes: 256 * 1024,
resources: [{ serverId: 'filesystem', uri: 'file:///srv/README.md' }],
prompts: [{ serverId: 'filesystem', name: 'summarize', arguments: { language: 'en' } }],
},
})) {
void event;
}Configure mcpContext on the agent options for a default selection. Loaded
content is marked as external and untrusted in the model context, and MCP
operation timeout/cancellation options apply to these reads.
Conversation context management
ContextManager runs before each model request. Its default safety limits are
32,000 estimated tokens and 100 messages. Token counts are deterministic
estimates based on serialized message bytes (approximately four bytes per
token), not model-specific tokenizer counts.
const agent = new LotaGateAgent({
modelClient,
model: 'your-text-model',
contextManagement: {
maxTokens: 12_000,
maxBytes: 48_000,
maxMessages: 40,
strategy: 'summarize',
summarize: async (messages, signal) => hostSummarizer.summarize(messages, signal),
},
});Without a summarizer, the SDK uses turn-aware truncation and preserves system
or developer messages plus recent conversation turns. The context.compacted
event exposes before/after message, token, and byte metrics.
Advanced API usage
Tool policy without the agent runner
Use ToolPolicy when an application needs to authorize a tool call before
passing it to another execution layer:
import { ToolPolicy } from '@lotagate/agent-sdk';
const policy = new ToolPolicy({
allowedTools: ['mcp__filesystem__read_file'],
deniedTools: ['mcp__filesystem__delete_file'],
authorizationRules: [{
id: 'read-only-window',
authorize: (request) => request.toolName.endsWith('__read_file')
? { decision: 'allow', reason: 'read-only-window' }
: { decision: 'deny', reason: 'outside-read-only-window' },
}],
requireApproval: true,
approvalHandler: async (request) => {
const approved = request.toolInfo?.metadata?.['serverId'] === 'filesystem';
return approved
? { decision: 'allow', reason: 'filesystem-approved' }
: { decision: 'deny', reason: 'filesystem-denied' };
// An interactive host can instead return:
// { decision: 'redirect', message: 'Please inspect the file first.' }
},
});
const decision = await policy.assertAllowed({
toolName: 'mcp__filesystem__read_file',
input: { path: 'README.md' },
context: {
sessionId: 'session-1',
runId: 'run-1',
iteration: 1,
maxResultBytes: 2 * 1024 * 1024,
},
});
if (decision.decision === 'redirect') {
console.log(`Continue the same run with: ${decision.message}`);
}Tool registry and model definitions
ToolRegistry stores discovered tools and converts them to the function-tool
shape accepted by the LotaGate SDK:
import { ToolRegistry } from '@lotagate/agent-sdk';
const registry = new ToolRegistry();
registry.replaceServerTools('filesystem', [
{
name: 'read_file',
description: 'Read a text file.',
inputSchema: {
type: 'object',
properties: { path: { type: 'string' } },
required: ['path'],
},
},
]);
const discovered = registry.all();
const modelTools = registry.toModelDefinitions();Use replaceServerTools() when refreshing one server's catalog; tools from
other servers remain registered. Duplicate model-facing names are rejected.
MCP extension support
Optional MCP extensions are isolated behind an explicit support check:
import {
assertMcpExtensionSupported,
getMcpExtensionSupport,
} from '@lotagate/agent-sdk';
const support = getMcpExtensionSupport('tasks');
if (!support.supported) console.log(support.reason);
// Throws McpExtensionError when the extension is unsupported.
assertMcpExtensionSupported('tasks');MCP Tasks currently report supported: false and are intentionally not part
of the agent execution path.
Errors
All SDK-owned errors are exported from the package root:
| Error/code | Meaning |
| --- | --- |
| AgentSdkError / INVALID_CONFIGURATION | Invalid agent, transport, server, or limit configuration |
| McpConnectionError / MCP_CONNECTION_FAILED | MCP transport or handshake failure |
| AgentSdkError / MCP_DISCONNECTED | An MCP operation was requested after the server connection was lost or closed |
| McpOperationError / MCP_OPERATION_FAILED | MCP operation failure after connection |
| McpExtensionError / MCP_EXTENSION_UNSUPPORTED | An isolated MCP extension is not supported by the official client boundary |
| ToolExecutionError / TOOL_NOT_FOUND | Model requested an unavailable tool |
| ToolExecutionError / TOOL_DENIED | Tool policy rejected the call |
| ToolExecutionError / TOOL_APPROVAL_REQUIRED | Approval is required but not configured |
| ToolExecutionError / TOOL_ARGUMENTS_INVALID | Model returned invalid or oversized arguments |
| ToolExecutionError / TOOL_RESULT_TOO_LARGE | Executor result exceeded the configured limit |
| ToolExecutionError / TOOL_EXECUTION_FAILED | A configured executor failed while running a tool |
| AgentSdkError / TOOL_NAME_CONFLICT | Multiple executors exposed the same model tool name |
| AgentSdkError / SESSION_ALREADY_RUNNING | A second run attempted to use an active session |
| AgentSdkError / AGENT_LIMIT_EXCEEDED | Agent iteration or tool-call limit was reached |
| AgentSdkError / MODEL_RESPONSE_INVALID | Model response did not contain a usable assistant response |
Example:
import {
AgentSdkError,
AgentSdkErrorCode,
ToolExecutionError,
} from '@lotagate/agent-sdk';
try {
for await (const event of agent.run({ input: 'Run the task.' })) {
console.log(event);
}
} catch (error) {
if (error instanceof ToolExecutionError && error.code === AgentSdkErrorCode.ToolDenied) {
console.error('The tool call was denied by policy.');
} else if (error instanceof AgentSdkError) {
console.error(error.code, error.message, error.details);
}
}Public exports and TypeScript types
The package provides typed exports for the public runtime API. Type-only exports are emitted as declaration files and can be imported with normal TypeScript syntax.
Main package: @lotagate/agent-sdk
Runtime exports:
LotaGate
LotaGateAgent
AgentRunner
McpClientManager
McpConnection
ToolPolicy
ToolRegistry
ToolCatalog
CompositeToolExecutor
McpToolExecutor
AgentSdkError
McpConnectionError
McpExtensionError
McpOperationError
ToolExecutionError
AgentSdkErrorCode
createInitialSessionState
InMemorySessionStore
assertMcpExtensionSupported
getMcpExtensionSupport
mergeHandlerCapabilities
normalizeMcpToolResult
normalizeMcpContentBlock
normalizeMcpContentBlocks
qualifyModelToolName
ContextManager
estimateMessageBytes
estimateMessageTokensType exports include:
// Re-exported from @lotagate/sdk
LotaGateClientOptions
RequestOptions
// Agent
AgentEvent
AgentEventBase
AgentObservability
AgentRunRequest
LotaGateAgentOptions
// Session
AgentSessionPatch
AgentSessionState
AgentSessionStatus
SessionStore
// MCP configuration and lifecycle
McpAuth
McpClientCapabilities
McpClientManagerOptions
McpOperationName
McpOperationOptions
McpReconnectPolicy
McpRetryPolicy
McpServerMutationOptions
McpClientHandlers
McpConnectionStatus
McpConnectionSummary
McpDiscoveredTool
McpHttpServerConfig
McpLoggingHandler
McpElicitationHandler
McpRootsHandler
McpSamplingHandler
McpProtocolMode
McpProtocolPolicy
McpServerConfig
McpServerConfigMap
McpStdioServerConfig
McpServerChangedEvent
McpToolsChangedEvent
McpContextSelection
McpPromptContextReference
McpResourceContextReference
ContextCompactionStrategy
ContextManagementOptions
ContextManagementResult
ContextMetrics
McpExtensionName
McpExtensionSupport
// Tool policy
ToolApprovalRequest
ToolAuthorizationDecision
ToolAuthorizationEvent
ToolAuthorizationRule
ToolPolicyOptions
// Tool execution
ToolExecutionContext
ToolExecutor
ToolInfo
ToolResult
ToolImageContentBlock
ToolResultContentBlock
ToolSource
// Error code union
AgentSdkErrorCodeValue@lotagate/agent-sdk/agent
Exports LotaGateAgent, AgentRunner, AgentEvent, AgentEventBase,
AgentObservability, AgentRunRequest, and LotaGateAgentOptions.
@lotagate/agent-sdk/mcp
Exports McpClientManager, McpConnection, MCP handler utilities, content
normalization, extension support utilities, tool-name qualification,
DEFAULT_MCP_STARTUP_CONCURRENCY, mapWithMcpConcurrency, and
DEFAULT_MCP_PROTOCOL_MODE. It also exports the MCP configuration/lifecycle
types and re-exports the official McpSubscription and SubscriptionFilter
types.
Use import type for types in application code when your TypeScript compiler
uses verbatimModuleSyntax:
import { LotaGateAgent } from '@lotagate/agent-sdk';
import type {
AgentEvent,
LotaGateAgentOptions,
McpServerConfigMap,
SessionStore,
} from '@lotagate/agent-sdk';Known limitations
- MCP Tasks are intentionally not implemented. The official MCP v2 client does
not expose stable runtime task methods; the extension is isolated behind
getMcpExtensionSupport('tasks')and reportssupported: false. - Legacy SSE remains available only for compatibility. New deployments should use Streamable HTTP.
InMemorySessionStoreis process-local and is not a distributed persistence solution.- The SDK does not provide a built-in OAuth credential store, telemetry backend, approval UI, or durable session database. Those responsibilities belong to the host application.
- The SDK does not provide built-in local filesystem or shell executors. Hosts
such as
@lotagate/climust implement those capabilities with explicit workspace, process, approval and platform policies. - The package currently targets Node.js server-side runtimes. It is not designed to expose MCP credentials or spawn stdio processes in a browser.
- Context token counts are estimates; applications that require exact model accounting should use the tokenizer for their selected model in a custom context policy or summarizer.
Package and licensing
- Package:
@lotagate/agent-sdk - License: LotaGate Proprietary Software License v1.0 (see
LICENSE.md) - Website: LotaGate
- Support and bug reports: Contact LotaGate
