Skip to content

Connecting AI assistants

SMSD lets an AI assistant run molecular comparisons on your machine: maximum common substructure, substructure and SMARTS matching, fingerprints, similarity, screening, standardisation and MCS depiction. Each search result reports its status and whether it is optimal and complete, so the assistant can tell a proven answer from a search that stopped at its limit.

You can use a hosted assistant or an open model that runs on your own machine. Connect SMSD through its local MCP server, smsd-mcp, or pass its tool definitions to a model API from your own Python code. SMSD itself sends nothing over the network. A hosted assistant sends your prompts, tool arguments and results, including any structures, to its model provider.

The eight tools and their results are described in Molecular tools and MCP.

Install

python -m pip install "smsd[mcp]>=8.1.0"
which smsd-mcp    # Windows: where.exe smsd-mcp

The MCP server and the tool definitions need SMSD 8.1.0 or later and Python 3.12 or later. The [mcp] extra adds the MCP SDK that smsd-mcp needs. The Python loops below need smsd without the extra, plus the client package that each section installs.

smsd-mcp (or python -m smsd.mcp_server) starts the server over standard input and output. It takes no arguments, opens no network port and writes diagnostics to standard error. Assistants start it themselves when they need the tools. Use the path that which or where.exe prints wherever a client needs an absolute path.

To check the server without an assistant, run python/examples/mcp_stdio_client.py; it lists the tools and runs one search over standard input and output.

Connect the MCP server

Clients are listed in alphabetical order. Each block follows the syntax in that product's documentation as of 29 October 2026. Each result carries engine_version; use it to confirm the SMSD version, because some clients show the MCP SDK's version for the server.

Claude Code

claude mcp add --transport stdio smsd -- smsd-mcp
claude mcp get smsd

The server is added to the current project for you only. Add --scope user before -- to use it in all your projects, or --scope project to share it with your team through .mcp.json. Inside a session, /mcp shows its status. Reference: Claude Code MCP.

Claude Desktop

Add the server to claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "smsd": {
      "command": "/absolute/path/to/smsd-mcp",
      "args": []
    }
  }
}

Replace the command with the absolute path printed by which smsd-mcp or where.exe smsd-mcp, because Claude Desktop may not find commands on your shell's PATH. In JSON, write Windows paths with doubled backslashes or forward slashes. Quit Claude Desktop fully and restart it. The server then appears under Connectors in the chat + menu. Server output is logged to mcp-server-smsd.log in ~/Library/Logs/Claude (macOS) or %APPDATA%\Claude\logs (Windows). Reference: Connect to local MCP servers.

Codex

codex mcp add smsd -- smsd-mcp
codex mcp list

The command writes an [mcp_servers.smsd] table to ~/.codex/config.toml. The Codex CLI, the Codex IDE extension and the ChatGPT desktop app share this file, so the server appears in all three. To add it from the IDE extension instead, open the gear menu, choose MCP servers, then Add server; enter smsd, choose STDIO, enter smsd-mcp as the command, save and select Restart extension. In the terminal interface, /mcp lists active servers.

Codex waits a short time for optional servers when it builds the first tool list. To wait for each server's startup_timeout_sec instead, set mcp_optional_startup_grace_ms = 0 at the top level of config.toml. To make start-up fail when the SMSD server cannot start, add required = true to the [mcp_servers.smsd] table. References: Codex MCP, configuration reference.

Gemini CLI and Antigravity CLI

gemini mcp add --scope user smsd smsd-mcp
gemini mcp list

Without --scope user, the server is added to .gemini/settings.json in the current folder. Gemini CLI connects stdio servers only in trusted folders; run gemini trust in a folder to trust it. Google moved some account types from Gemini CLI to Antigravity CLI (agy) on 18 June 2026; Google's announcement lists which. Antigravity CLI reads servers from ~/.gemini/config/mcp_config.json, or you can add them with its /mcp manager:

{
  "mcpServers": {
    "smsd": {
      "command": "smsd-mcp",
      "args": []
    }
  }
}

References: Gemini CLI MCP, Antigravity MCP.

Goose with a local model

Goose is an open-source agent that can use a model served by Ollama. Run goose configure, choose Ollama as the provider, confirm the API host http://localhost:11434 and choose a model with tool support, such as qwen3 (models with tools). On machines with less than 24 GiB of VRAM, Ollama's default context is 4k, which is small for eight tool definitions; start Ollama with OLLAMA_CONTEXT_LENGTH=8192 ollama serve. Then add SMSD under extensions: in ~/.config/goose/config.yaml (Windows: %APPDATA%\Block\goose\config\config.yaml):

extensions:
  smsd:
    type: stdio
    name: smsd
    enabled: true
    cmd: smsd-mcp
    args: []
    env_keys: []
    envs: {}
    timeout: 300

References: Goose configuration files, Ollama context length. Other local MCP clients are listed under Open models.

Use the tool definitions from Python

smsd.tool_definitions.get_tool_definitions(provider) returns the eight tools in the shape a model API expects. Pass one of the four values below as provider; the default is mcp. call_tool(name, arguments) runs a tool locally and returns its JSON-compatible result. It rejects unknown tools, unknown or missing arguments, wrong JSON types and out-of-range values with a ValueError; the loops below return that message to the model so that it can correct the call. python/examples/agent_workflow.py runs the same tools in a fixed sequence (screening, MCS and depiction) without a model.

Value Shape Use with
mcp name, description, inputSchema MCP tool lists
input-schema name, description, input_schema Claude Messages API
function flat: type: "function", name, description, parameters OpenAI Responses API; Gemini API after the change shown below
chat-function nested: type: "function", function: {name, description, parameters} Chat Completions API, DeepSeek API, llama.cpp, LM Studio, Ollama, vLLM

The function and chat-function values set strict: false, so the model can leave out optional arguments and SMSD applies its defaults. Each loop below reads any API key it needs from the environment and sends every tool result back to the model in full, including status, optimal and complete. The APIs are listed in alphabetical order of vendor.

Claude Messages API

python -m pip install anthropic
export ANTHROPIC_API_KEY=...    # your own key
import json

import anthropic
from smsd.tool_definitions import call_tool, get_tool_definitions


def run_tool(name, arguments):
    """Run one SMSD tool locally and return (text, is_error)."""
    try:
        return json.dumps(call_tool(name, arguments)), False
    except (ValueError, ImportError) as error:
        return str(error), True


client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY
tools = get_tool_definitions("input-schema")
messages = [{"role": "user", "content": "Find the maximum common substructure of "
             "c1ccccc1O and c1ccccc1N. Is the result proven optimal?"}]

while True:
    response = client.messages.create(
        model="claude-opus-5-5", max_tokens=4096, tools=tools, messages=messages)
    messages.append({"role": "assistant", "content": response.content})
    if response.stop_reason != "tool_use":
        break
    results = []
    for block in response.content:
        if block.type == "tool_use":
            text, is_error = run_tool(block.name, block.input)
            results.append({"type": "tool_result", "tool_use_id": block.id,
                            "content": text, "is_error": is_error})
    messages.append({"role": "user", "content": results})

print("".join(block.text for block in response.content if block.type == "text"))

claude-opus-5-5 was a current model on 29 October 2026; the models overview lists current IDs. Reference: Claude tool use.

Gemini API

python -m pip install google-genai
export GEMINI_API_KEY=...    # your own key
from google import genai
from google.genai import types
from smsd.tool_definitions import call_tool, get_tool_definitions


def run_tool(name, arguments):
    """Run one SMSD tool locally; errors go back to the model."""
    try:
        return {"result": call_tool(name, arguments)}
    except (ValueError, ImportError) as error:
        return {"error": str(error)}


# Gemini's parameters schema has no additionalProperties field, so drop it.
# call_tool still rejects unknown arguments.
declarations = [
    {"name": tool["name"], "description": tool["description"],
     "parameters": {key: value for key, value in tool["parameters"].items()
                    if key != "additionalProperties"}}
    for tool in get_tool_definitions("function")
]
client = genai.Client()  # reads GEMINI_API_KEY or GOOGLE_API_KEY
config = types.GenerateContentConfig(
    tools=[types.Tool(function_declarations=declarations)], store=False)
contents = [types.Content(role="user", parts=[types.Part(
    text="Find the maximum common substructure of c1ccccc1O and c1ccccc1N. "
         "Is the result proven optimal?")])]

while True:
    response = client.models.generate_content(
        model="gemini-3.8-flash", contents=contents, config=config)
    if not response.function_calls:
        break
    contents.append(response.candidates[0].content)
    parts = [types.Part(function_response=types.FunctionResponse(
        id=call.id, name=call.name, response=run_tool(call.name, call.args or {})))
        for call in response.function_calls]
    contents.append(types.Content(role="user", parts=parts))

print(response.text)

Gemini accepts default without applying it and may not enforce every limit in the schema; call_tool checks every argument itself. store=False opts the request out of storage, which is on by default for new paid projects from 26 October 2026; it needs google-genai 2.33.0 or later. gemini-3.8-flash was a current model on 29 October 2026; see Gemini models. Google recommends its Interactions API for new projects (Generate Content); its function tools use the flat type, name, description and parameters shape of the function value. References: function calling with generateContent, function calling with Interactions.

OpenAI Responses API

python -m pip install openai
export OPENAI_API_KEY=...    # your own key
import json

from openai import OpenAI
from smsd.tool_definitions import call_tool, get_tool_definitions


def run_tool(name, arguments):
    """Run one SMSD tool locally; errors go back to the model as text."""
    try:
        return json.dumps(call_tool(name, json.loads(arguments)))
    except (ValueError, ImportError) as error:
        return f"Error: {error}"


client = OpenAI()  # reads OPENAI_API_KEY
tools = get_tool_definitions("function")
input_list = [{"role": "user", "content": "Find the maximum common substructure of "
               "c1ccccc1O and c1ccccc1N. Is the result proven optimal?"}]

while True:
    response = client.responses.create(model="gpt-6-astra", tools=tools, input=input_list)
    input_list += response.output
    calls = [item for item in response.output if item.type == "function_call"]
    if not calls:
        break
    for item in calls:
        input_list.append({"type": "function_call_output", "call_id": item.call_id,
                           "output": run_tool(item.name, item.arguments)})

print(response.output_text)

gpt-6-astra was a current model on 29 October 2026; see OpenAI models. OpenAI recommends the Responses API for new projects (migration guide). Reference: Function calling.

Chat Completions API

import json

from openai import OpenAI
from smsd.tool_definitions import call_tool, get_tool_definitions


def run_tool(name, arguments):
    """Run one SMSD tool locally; errors go back to the model as text."""
    try:
        return json.dumps(call_tool(name, json.loads(arguments)))
    except (ValueError, ImportError) as error:
        return f"Error: {error}"


client = OpenAI()  # reads OPENAI_API_KEY
tools = get_tool_definitions("chat-function")
messages = [{"role": "user", "content": "Find the maximum common substructure of "
             "c1ccccc1O and c1ccccc1N. Is the result proven optimal?"}]

while True:
    response = client.chat.completions.create(
        model="gpt-5.6-terra", reasoning_effort="none", messages=messages, tools=tools)
    message = response.choices[0].message
    messages.append(message)
    if not message.tool_calls:
        break
    for tool_call in message.tool_calls:
        messages.append({"role": "tool", "tool_call_id": tool_call.id,
                         "content": run_tool(tool_call.function.name,
                                             tool_call.function.arguments)})

print(message.content)

On 29 October 2026, OpenAI's migration guide stated that, from GPT-5.4 onwards, Chat Completions supports tool calls only with reasoning_effort="none". gpt-5.6-terra was available on that date and supports this setting. GPT-6 Astra, GPT-6.1 Sol and GPT-6.1 Terra need the Responses API for tool calls; see OpenAI models. The same loop works with other Chat Completions services; see Open models.

Open models

One loop for DeepSeek and local runtimes

The hosted DeepSeek API and the local runtimes below accept tools in the chat-function shape through Chat Completions-compatible endpoints. This loop works with each of them; change only BASE_URL and MODEL. Runtimes are listed in alphabetical order.

python -m pip install openai
export DEEPSEEK_API_KEY=...    # DeepSeek only; local runtimes need no key
import json
import os
from urllib.parse import urlparse

from openai import OpenAI
from smsd.tool_definitions import call_tool, get_tool_definitions

# Change only these two values; the table below lists each runtime.
BASE_URL = "https://api.deepseek.com"
MODEL = "deepseek-flash"


def run_tool(name, arguments):
    """Run one SMSD tool locally; errors go back to the model as text."""
    try:
        return json.dumps(call_tool(name, json.loads(arguments)))
    except (ValueError, ImportError) as error:
        return f"Error: {error}"


# Local runtimes need no key, but the client needs a value.
# The DeepSeek key is sent only to the DeepSeek API.
if urlparse(BASE_URL).hostname == "api.deepseek.com":
    api_key = os.environ["DEEPSEEK_API_KEY"]
else:
    api_key = "local"
client = OpenAI(base_url=BASE_URL, api_key=api_key)
tools = get_tool_definitions("chat-function")
messages = [{"role": "user", "content": "Find the maximum common substructure of "
             "c1ccccc1O and c1ccccc1N. Is the result proven optimal?"}]

while True:
    response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
    message = response.choices[0].message
    messages.append(message)
    if not message.tool_calls:
        break
    for tool_call in message.tool_calls:
        messages.append({"role": "tool", "tool_call_id": tool_call.id,
                         "content": run_tool(tool_call.function.name,
                                             tool_call.function.arguments)})

print(message.content)
Runtime Start command BASE_URL MODEL
DeepSeek API none: set DEEPSEEK_API_KEY https://api.deepseek.com deepseek-flash or deepseek-v4-pro
llama.cpp llama-server --jinja -hf bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M --port 9931 http://localhost:9931/v1 the model name listed at http://localhost:9931/v1/models
LM Studio lms server start http://localhost:1234/v1 a model identifier listed at http://localhost:1234/v1/models
Ollama ollama pull qwen3, then ollama serve http://localhost:11434/v1/ qwen3
vLLM vllm serve Qwen/Qwen3-8B --host 127.0.0.1 --enable-auto-tool-choice --tool-call-parser hermes --reasoning-parser deepseek_r1 http://localhost:8000/v1 Qwen/Qwen3-8B

Notes on each runtime, as of 29 October 2026:

  • DeepSeek API: thinking mode is on by default and supports tool calls. The loop appends each assistant message whole, which passes back its reasoning_content as DeepSeek requires. Current model IDs: Models and pricing. DeepSeek also publishes open weights, such as DeepSeek-V4.1-Flash-1017 (763B parameters; model card).
  • llama.cpp: native tool-call formats include Hermes 2 and 3, Llama 3.1 to 3.3, Mistral Nemo and Qwen 2.5. DeepSeek R1 is listed as work in progress. Other models use a generic format. The default port changed from 8080 to 9931, so the commands on this page set it. Reference: function calling.
  • LM Studio: Qwen, Llama 3.1, Llama 3.2 and Mistral models have native tool use; other models use LM Studio's default tool format. Reference: LM Studio tool use.
  • Ollama: models with tool support in its library include gemma4, gpt-oss, llama3.1, mistral-small3.2, muse-glimmer, qwen3 and qwen3.8. Some entries, such as deepseek-v4.1-flash, have only cloud tags; choose a local tag to keep data on your machine. Ollama sets its default context from the available VRAM: 4k below 24 GiB, 32k from 24 to 48 GiB and 256k from 48 GiB. A 4k context is small for eight tool definitions; on such machines start the server with OLLAMA_CONTEXT_LENGTH=8192 ollama serve. Ollama does not support tool_choice, and the loop does not send it. References: tools listing, context length.
  • vLLM: tool calls need --enable-auto-tool-choice and a --tool-call-parser that matches the model. The command above follows Qwen's vLLM instructions; vLLM's own reasoning page gives --reasoning-parser qwen3 for the Qwen3 series. vLLM's tool-calling page gives the parser for other families, including DeepSeek, Llama and Mistral. For Gemma 4, the Gemma 4 recipe gives --enable-auto-tool-choice --tool-call-parser gemma4 --reasoning-parser gemma4, together with a chat template.

MCP with a local model

Goose connects smsd-mcp to a model served by Ollama. llama.cpp's llama-server can also start stdio MCP servers itself. Save this as mcp.json:

{
  "mcpServers": {
    "smsd": { "command": "smsd-mcp", "args": [], "timeout_ms": 60000 }
  }
}
llama-server --jinja -hf bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M --port 9931 --mcp-servers-config mcp.json

llama.cpp stops each tool call after 30 s unless the entry sets timeout_ms. The value above leaves room for an SMSD search at its 30,000 ms maximum. The SMSD tools then appear as smsd_find_mcs and so on in the built-in web interface at http://127.0.0.1:9931. This option is experimental. The server starts smsd-mcp with its own privileges, and the llama.cpp documentation advises against enabling it in untrusted environments. Reference: llama.cpp server.

LM Studio (version 0.3.17 or later) reads MCP servers from its mcp.json file: on the Program tab, choose Install > Edit mcp.json. The file is ~/.lmstudio/mcp.json on macOS and Linux and %USERPROFILE%/.lmstudio/mcp.json on Windows (LM Studio MCP, LM Studio 0.3.17). Jan adds MCP servers under Settings > MCP Servers (Jan MCP). In either, enter the absolute path to smsd-mcp as the command.

Keeping this current

The commands, settings and model IDs on this page follow each vendor's documentation as of 29 October 2026. Assistants and model lists change often, so check the vendor page before you rely on a command or model ID. The links follow the order of this page:

Safety

  • The tools run on your machine and contact no network service. Molecular arguments are SMILES or SMARTS text, never paths, URLs or commands.
  • Limits are fixed: 16,384 characters per text argument and 1,024 atoms per molecule (256 for depiction and 128 for standardisation). A similarity or screening call takes at most 4,096 atoms in total, and a screen at most 64 targets and 65,536 characters. search_smarts returns at most 32 matches, and screen_molecules at most 32 hits per page. A search stops at timeout_ms (default 1,000, at most 30,000) or node_limit (default 100,000, at most 1,000,000). These budgets cover the search, not the whole call. Standardisation has no time budget. Use process or container limits for a hard deadline. The container recipe runs the server with no network access.
  • smsd-mcp and call_tool check every argument themselves, so a client that drops schema keywords cannot widen these limits.
  • Ask the assistant to report status, optimal and complete with each answer. A search that stopped early may not be maximum, and found: false from an unfinished search does not prove absence.
  • A hosted assistant sends your prompts, tool arguments and results, including any structures, to its provider. Use a local runtime when structures must stay on your machine.
  • API keys come from environment variables such as ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY and DEEPSEEK_API_KEY. Do not write keys into code or committed configuration files. smsd-mcp needs no key, so its client entries need no environment settings.
  • Keep your assistant's tool approval prompts on unless you trust every tool it can call.