Skip to main content
MCP Apps SDK
import { App } from "@modelcontextprotocol/ext-apps";

Overview

onlisttools is a request handler that returns the list of tools your app provides to the host. The host calls this to discover what tools are available, then invokes them through oncalltool. To use onlisttools, you must declare the tools capability when constructing the App. If you set listChanged: true, the host will re-query this handler whenever you send a tool list changed notification, allowing your app to dynamically add or remove tools at runtime. Because this is a request handler, the host waits for your response. Return an object with a tools array containing the tool names or definitions your app supports.

Signature

app.onlisttools = async (params, extra) => Promise<ListToolsResult>;

Parameters

params
ListToolsRequest['params']
Standard MCP list tools request parameters (includes optional cursor for pagination).

Returns

result
ListToolsResult
tools
Tool[]
required
Array of tool definitions. Each tool must have a name and inputSchema (with type: "object"). Optional fields: description, annotations.
Register handlers before calling connect() to avoid missing notifications during the initialization handshake.

Usage

Static tool list

const app = new App(
  { name: "MyApp", version: "1.0.0" },
  { tools: { listChanged: true } },
);

app.onlisttools = async () => {
  return {
    tools: [
      { name: "get-selection", inputSchema: { type: "object" } },
      { name: "format-text", inputSchema: { type: "object" } },
    ],
  };
};

await app.connect();

Dynamic tools based on app state

When listChanged: true is set, the host will re-query onlisttools after receiving a list-changed notification. This lets you add or remove tools based on the current state of your app:
let hasSelection = false;

app.onlisttools = async () => {
  const tools = [
    { name: "format-text", inputSchema: { type: "object" } },
  ];
  if (hasSelection) {
    tools.push({ name: "get-selection", inputSchema: { type: "object" } });
  }
  return { tools };
};

document.addEventListener("selectionchange", () => {
  const newHasSelection = !!window.getSelection()?.toString();
  if (newHasSelection !== hasSelection) {
    hasSelection = newHasSelection;
    // Notify the host to re-query the tool list
    app.sendToolListChanged();
  }
});

await app.connect();

Pair with oncalltool

The tools declared here must be handled in oncalltool:
app.onlisttools = async () => {
  return {
    tools: [
      { name: "get-selection", inputSchema: { type: "object" } },
      { name: "format-text", inputSchema: { type: "object" } },
    ],
  };
};

app.oncalltool = async (params) => {
  switch (params.name) {
    case "get-selection":
      return { content: [{ type: "text", text: getSelectedText() }] };
    case "format-text":
      return { content: [{ type: "text", text: formatText(params.arguments) }] };
    default:
      throw new Error(`Unknown tool: ${params.name}`);
  }
};

await app.connect();