Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

API for other extensions

Most extensions do not need an API: add a command with a caption and describedBy arguments. Agents find it with list_all_commands and run it with execute_command, with the result protocol and blockedCommands. Users and admins can also give it a dedicated tool with a tools entry, which adds the input checks.

To register a tool directly, use the IWebMCPRegistry token.

IWebMCPRegistry

interface IWebMCPRegistry {
  readonly isAvailable: boolean;
  readonly toolNames: readonly string[];
  readonly changed: ISignal<IWebMCPRegistry, void>;
  registerTool(tool: IModelContextTool): Promise<IDisposable>;
}

The package also exports the WebMCP typings (IModelContextTool, IToolAnnotations, IToolExecuteOptions, IModelContext, getModelContext) and the ICommandToolSpec type of a tools entry.

Example

Use the token as an optional dependency, so that your extension still starts without WebMCP:

import {
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { IWebMCPRegistry } from 'jupyter-webmcp';

const plugin: JupyterFrontEndPlugin<void> = {
  id: 'my-extension:webmcp',
  description: 'Adds a WebMCP tool that counts the open notebooks.',
  autoStart: true,
  optional: [IWebMCPRegistry],
  activate: (app: JupyterFrontEnd, registry: IWebMCPRegistry | null) => {
    if (!registry?.isAvailable) {
      return;
    }
    registry
      .registerTool({
        name: 'count_notebooks',
        description: 'Count the notebooks open in the main area.',
        inputSchema: { type: 'object', properties: {} },
        annotations: { readOnlyHint: true },
        execute: () => ({
          success: true,
          count: Array.from(app.shell.widgets('main')).filter(widget =>
            widget.hasClass('jp-NotebookPanel')
          ).length
        })
      })
      .catch(reason => console.warn('count_notebooks', reason));
  }
};

export default plugin;

Tools registered this way get no input checks, no bounding and no pending results, and disabledTools and blockedCommands do not apply to them. So:

Shared package

Your extension must use the token object that jupyter-webmcp provides. Add jupyter-webmcp to the dependencies of your package, and share it as a singleton without bundling it, in package.json:

"jupyterlab": {
  "sharedPackages": {
    "jupyter-webmcp": { "bundled": false, "singleton": true }
  }
}

A bundled copy gives a different token, and the registry is then null. Without a bundled copy, jupyter-webmcp must be installed: add jupyter_webmcp to the Python dependencies of your extension.