> ## Documentation Index
> Fetch the complete documentation index at: https://mcpjam-mintlify-docs-update-pr-2772-1782277917413.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# HostRunner

> API reference for HostRunner

The `HostRunner` class runs prompts with MCP tools enabled. It handles the multi-step prompt loop and returns rich result objects.

## Import

```typescript theme={null}
import { HostRunner } from "@mcpjam/sdk";
```

## Constructor

```typescript theme={null}
new HostRunner(options: HostRunnerOptions)
```

### Parameters

<ParamField path="options" type="HostRunnerOptions" required>
  Configuration for the test agent.
</ParamField>

### HostRunnerOptions

| Property             | Type                             | Required | Default     | Description                                                                                                                                                                                                                                                                                                                                                            |
| -------------------- | -------------------------------- | -------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tools`              | `Record<string, Tool>`           | Yes      | -           | MCP tools from `manager.getTools()`                                                                                                                                                                                                                                                                                                                                    |
| `model`              | `string`                         | Yes      | -           | Model identifier in `provider/model` format                                                                                                                                                                                                                                                                                                                            |
| `apiKey`             | `string`                         | Yes      | -           | API key for the LLM provider                                                                                                                                                                                                                                                                                                                                           |
| `systemPrompt`       | `string`                         | No       | `undefined` | System prompt for the LLM                                                                                                                                                                                                                                                                                                                                              |
| `temperature`        | `number`                         | No       | `undefined` | Sampling temperature (0-2). If undefined, uses model default.                                                                                                                                                                                                                                                                                                          |
| `maxSteps`           | `number`                         | No       | `10`        | Maximum agentic loop iterations                                                                                                                                                                                                                                                                                                                                        |
| `customProviders`    | `Record<string, CustomProvider>` | No       | `undefined` | Custom LLM provider definitions                                                                                                                                                                                                                                                                                                                                        |
| `mcpClientManager`   | `MCPClientManager`               | No       | `undefined` | Same connected manager used for `connectToServer` / `getToolsForAiSdk`. **Required** to populate [`getWidgetSnapshots()`](/sdk/reference/prompt-result#mcp-app-widget-snapshots) on each [`PromptResult`](/sdk/reference/prompt-result) (see note below).                                                                                                              |
| `injectOpenAiCompat` | `boolean`                        | No       | `false`     | When `true`, injects the OpenAI Apps SDK `window.openai` shim into captured widget HTML snapshots. Enable this when testing widgets written against the OpenAI Apps SDK (ChatGPT / Copilot hosts). Leave it off for SEP-1865-native widgets (Claude, Cursor, Codex) — those hosts don't expose `window.openai` and snapshots should match what the live host produces. |

### Example

```typescript theme={null}
const agent = new HostRunner({
  tools: await manager.getTools(),
  model: "anthropic/claude-sonnet-4-20250514",
  apiKey: process.env.ANTHROPIC_API_KEY,
  systemPrompt: "You are a helpful assistant.",
  temperature: 0.3,
  maxSteps: 5,
});
```

<Info>
  **MCP Apps, traces, and `mcpClientManager`:** MCP App widgets are backed by HTML served from an MCP **resource** (`ui.resourceUri`), not by the tool’s JSON result alone. After each tool call, `HostRunner` uses the manager’s [`readResource`](/sdk/reference/mcp-client-manager#readresource) to fetch that HTML and attach **`widgetSnapshots`** to the [`PromptResult`](/sdk/reference/prompt-result). When you upload results to MCPJam, those snapshots are stored so the Evals trace viewer can **replay the widget offline**. If you omit `mcpClientManager`, prompts still run and tools still execute — but **`widgetSnapshots` stay empty** and uploaded traces will only contain messages and spans (no embedded app replay). Passing `manager` is also what enables **replay credential** capture for authenticated HTTP servers when reporting to MCPJam.
</Info>

***

## Methods

### run()

Sends a prompt to the LLM and returns the result.

```typescript theme={null}
run(
  message: string,
  options?: PromptOptions
): Promise<PromptResult>
```

Use [`getWidgetSnapshots()`](/sdk/reference/prompt-result#mcp-app-widget-snapshots) on the returned result (or `toEvalResult()` when reporting) to access MCP App HTML captured during the run when `mcpClientManager` was set.

#### Parameters

| Parameter | Type            | Required | Description        |
| --------- | --------------- | -------- | ------------------ |
| `message` | `string`        | Yes      | The user prompt    |
| `options` | `PromptOptions` | No       | Additional options |

#### PromptOptions

| Property            | Type                                                                | Description                                                                                                                                                                           |
| ------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context`           | `PromptResult \| PromptResult[]`                                    | Previous result(s) for multi-turn conversations                                                                                                                                       |
| `abortSignal`       | `AbortSignal`                                                       | Cancels the prompt runtime when aborted                                                                                                                                               |
| `stopWhen`          | `StopCondition<ToolSet> \| Array<StopCondition<ToolSet>>`           | Additional conditions for the multi-step prompt loop. Tools still execute normally. `HostRunner` always applies `stepCountIs(maxSteps)` as a safety guard.                            |
| `timeout`           | `number \| { totalMs?: number; stepMs?: number; chunkMs?: number }` | Bounds prompt runtime. `number` and `totalMs` cap the full prompt, `stepMs` caps each generation step, and `chunkMs` is accepted for parity but is mainly relevant to streaming APIs. |
| `timeoutMs`         | `number`                                                            | Shortcut for a total prompt timeout in milliseconds                                                                                                                                   |
| `stopAfterToolCall` | `string \| string[]`                                                | Short-circuits the named tool(s) with a stub result and stops after the step where they were called. Tool names and args are still captured in the `PromptResult`.                    |

#### Returns

`Promise<PromptResult>` - The result object with response and metadata.

#### Example

```typescript theme={null}
import { hasToolCall } from "@mcpjam/sdk";

// Simple prompt
const result = await agent.run("Add 2 and 3");

// Multi-turn conversation
const r1 = await agent.run("Create a task called 'Test'");
const r2 = await agent.run("Mark it complete", { context: r1 });

// Multiple context items
const r3 = await agent.run("Show summary", { context: [r1, r2] });

// Stop the loop after the step where a tool is called
const r4 = await agent.run("Search for tasks", {
  stopWhen: hasToolCall("search_tasks"),
});
console.log(r4.hasToolCall("search_tasks"));

// Bound prompt runtime
const r5 = await agent.run("Run a long workflow", {
  timeout: { totalMs: 10_000, stepMs: 2_500 },
});

if (r5.hasError()) {
  console.error(r5.getError());
}

// Exit early after selecting a tool, without waiting for the real MCP call
const r6 = await agent.run("Search for tasks", {
  stopAfterToolCall: "search_tasks",
  timeoutMs: 5_000,
});
console.log(r6.getToolArguments("search_tasks"));
```

<Note>
  `run()` never throws exceptions. Errors are captured in the `PromptResult`.
  Check `result.hasError()` to detect failures.
</Note>

<Note>
  `stopAfterToolCall` is intended for evals that only care about tool selection
  and arguments. If the model emits multiple tool calls in the same step,
  non-target sibling tools may still execute before the loop stops.
</Note>

<Info>
  `timeout` bounds prompt runtime. The runtime creates an internal abort signal,
  so tools can stop early if their implementation respects the provided
  `abortSignal`. If a tool ignores that signal, its underlying work may continue
  briefly after the prompt returns an error result.
</Info>

***

## Model String Format

Models are specified as `provider/model`:

```typescript theme={null}
// Anthropic
"anthropic/claude-sonnet-4-20250514";
"anthropic/claude-3-haiku-20240307";

// OpenAI
"openai/gpt-4o";
"openai/gpt-4o-mini";

// Google
"google/gemini-1.5-pro";
"google/gemini-1.5-flash";

// Azure
"azure/gpt-4o";

// Mistral
"mistral/mistral-large-latest";

// DeepSeek
"deepseek/deepseek-chat";

// Ollama (local)
"ollama/llama3";

// OpenRouter
"openrouter/anthropic/claude-3-opus";

// xAI
"xai/grok-beta";
```

***

## Custom Providers

Add custom OpenAI or Anthropic-compatible endpoints.

### CustomProvider Type

| Property             | Type                                            | Required | Description                      |
| -------------------- | ----------------------------------------------- | -------- | -------------------------------- |
| `name`               | `string`                                        | Yes      | Provider identifier              |
| `protocol`           | `"openai-compatible" \| "anthropic-compatible"` | Yes      | API protocol                     |
| `baseUrl`            | `string`                                        | Yes      | API endpoint URL                 |
| `modelIds`           | `string[]`                                      | Yes      | Available model IDs              |
| `useChatCompletions` | `boolean`                                       | No       | Use `/chat/completions` endpoint |
| `apiKeyEnvVar`       | `string`                                        | No       | Custom env var for API key       |

### Example

```typescript theme={null}
const agent = new HostRunner({
  tools,
  model: "my-litellm/gpt-4",
  apiKey: process.env.LITELLM_API_KEY,
  customProviders: {
    "my-litellm": {
      name: "my-litellm",
      protocol: "openai-compatible",
      baseUrl: "http://localhost:8000",
      modelIds: ["gpt-4", "gpt-3.5-turbo", "claude-3-sonnet"],
      useChatCompletions: true,
    },
  },
});
```

***

## Configuration Properties

### tools

The MCP tools available to the agent. Obtained from `MCPClientManager.getTools()`.

```typescript theme={null}
const tools = await manager.getTools();
const agent = new HostRunner({ tools, ... });
```

### model

The LLM model identifier. Format: `provider/model-id`.

```typescript theme={null}
model: "anthropic/claude-sonnet-4-20250514";
```

### apiKey

The API key for the LLM provider.

```typescript theme={null}
apiKey: process.env.ANTHROPIC_API_KEY;
```

### systemPrompt

Optional system prompt to guide the LLM's behavior.

```typescript theme={null}
systemPrompt: "You are a task management assistant. Be concise.";
```

### temperature

Controls response randomness. Range: 0.0 (deterministic) to 1.0 (creative).

```typescript theme={null}
temperature: 0.1; // More deterministic, better for testing
temperature: 0.9; // More creative
```

### maxSteps

Maximum iterations of the agentic loop (prompt → tool → result → continue).

```typescript theme={null}
maxSteps: 5; // Stop after 5 tool calls
```

<Warning>
  Setting `maxSteps` too low may prevent complex tasks from completing. Setting
  it too high may allow runaway loops.
</Warning>

### injectOpenAiCompat

Controls whether the OpenAI Apps SDK `window.openai` shim is injected into captured widget HTML snapshots. Defaults to `false`.

Set this to `true` when your widget is written against the OpenAI Apps SDK (i.e. it targets ChatGPT or Copilot). Leave it `false` for SEP-1865-native widgets targeting Claude, Cursor, Codex, or other hosts that don't expose `window.openai` — snapshots should match what the live host would have produced.

```typescript theme={null}
// Testing an OpenAI Apps SDK widget (ChatGPT / Copilot host)
const agent = new HostRunner({
  tools,
  model: "openai/gpt-4o",
  apiKey: process.env.OPENAI_API_KEY,
  mcpClientManager: manager,
  injectOpenAiCompat: true,
});

// Testing a SEP-1865 widget (Claude / Cursor / Codex host) — default
const agent = new HostRunner({
  tools,
  model: "anthropic/claude-sonnet-4-20250514",
  apiKey: process.env.ANTHROPIC_API_KEY,
  mcpClientManager: manager,
  // injectOpenAiCompat defaults to false
});
```

<Note>
  This flag is stamped onto each widget snapshot (`injectedOpenAiCompat`) so the
  MCPJam trace viewer can replay the widget faithfully under a different host
  config without rewriting the cached bytes.
</Note>

***

## Control Multi-Step Loops with stopWhen

Use `stopWhen` to control whether the agent starts another step after the current step completes.

```typescript theme={null}
import { hasToolCall } from "@mcpjam/sdk";

// Stop after the step where "search_tasks" is called
const result = await agent.run("Find my open tasks", {
  stopWhen: hasToolCall("search_tasks"),
});

expect(result.hasToolCall("search_tasks")).toBe(true);

// Stop after any of multiple conditions
const result2 = await agent.run("Do something", {
  stopWhen: [hasToolCall("tool_a"), hasToolCall("tool_b")],
});
```

<Info>
  `stopWhen` does not skip tool execution. It controls whether the prompt loop
  continues after the current step completes, and `HostRunner` also applies
  `stepCountIs(maxSteps)` as a safety guard.
</Info>

***

## Bound Prompt Runtime with timeout

Use `timeout` when you want to bound how long `HostRunner.run()` can run:

```typescript theme={null}
const result = await agent.run("Run a long workflow", {
  timeout: 10_000,
});

const result2 = await agent.run("Run a long workflow", {
  timeout: { totalMs: 10_000, stepMs: 2_500, chunkMs: 1_000 },
});
```

<Info>
  `chunkMs` is accepted for parity, but it is mainly useful for streaming APIs.
  For `HostRunner.run()`, `number`, `totalMs`, and `stepMs` are the main
  settings to focus on.
</Info>

***

## Complete Example

```typescript theme={null}
import { MCPClientManager, HostRunner } from "@mcpjam/sdk";

async function main() {
  // Setup
  const manager = new MCPClientManager({
    everything: {
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-everything"],
    },
  });
  await manager.connectToServer("everything");

  // Create agent (pass manager when you need widget snapshots or MCPJam replay configs)
  const agent = new HostRunner({
    tools: await manager.getTools(),
    model: "anthropic/claude-sonnet-4-20250514",
    apiKey: process.env.ANTHROPIC_API_KEY,
    systemPrompt: "You are a helpful assistant.",
    temperature: 0.3,
    maxSteps: 10,
    mcpClientManager: manager,
  });

  // Single prompt
  const r1 = await agent.run("What is 15 + 27?");
  console.log(r1.getText());
  console.log("Tools:", r1.toolsCalled());

  // Multi-turn
  const r2 = await agent.run("Now multiply that by 2", { context: r1 });
  console.log(r2.getText());

  // Error handling
  if (r2.hasError()) {
    console.error("Error:", r2.getError());
  }

  // Cleanup
  await manager.disconnectServer("everything");
}
```

***

## Related

* [Testing with LLMs](/sdk/concepts/testing-with-llms) - Conceptual guide
* [PromptResult Reference](/sdk/reference/prompt-result) - Result object API
* [LLM Providers Reference](/sdk/reference/llm-providers) - All providers
