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

@lotagate/agent-sdk

v0.1.13

Published

Provider-neutral LotaGate agent runtime with composable tool execution and MCP capabilities.

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

  • Node.js 22 or newer.
  • An ESM-compatible application ("type": "module").
  • A LotaGate API key for the model client.
  • @lotagate/sdk 0.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-sdk

For pnpm:

pnpm add @lotagate/agent-sdk

For yarn:

yarn add @lotagate/agent-sdk

Quick 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 version

probeTimeoutMs 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
estimateMessageTokens

Type 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 reports supported: false.
  • Legacy SSE remains available only for compatibility. New deployments should use Streamable HTTP.
  • InMemorySessionStore is 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/cli must 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