Extensions¶
Extensions add specialized tools to community assistants beyond what YAML can auto-generate. Use extensions when you need external API calls, CLI tool integration, or complex processing logic.
When to Use Extensions¶
| Need | Solution |
|---|---|
| Fetch documentation pages | Built-in (auto-generated from documentation config) |
| Search GitHub issues/PRs | Built-in (auto-generated from github config) |
| Search academic papers | Built-in (auto-generated from citations config) |
| Search code docstrings | Built-in (auto-generated from docstrings config) |
| Search mailing list FAQ | Built-in (auto-generated from mailman + faq_generation config) |
| Call an external validation API | Python plugin |
| Run a CLI tool | Python plugin |
| Connect to an MCP server | MCP server extension |
Python Plugins¶
Python plugins are the primary extension mechanism. Each plugin is a Python module containing functions decorated with LangChain's @tool.
Writing a Plugin¶
Create a tools.py file in your community directory:
# src/assistants/my-tool/tools.py
"""Specialized tools for My Tool community."""
import httpx
from langchain_core.tools import tool
@tool
def validate_config(config_text: str, version: str = "2.0") -> dict:
"""Validate a My Tool configuration file.
Args:
config_text: The configuration content to validate.
version: Schema version to validate against.
Returns:
Dict with 'valid' boolean and 'errors' list if invalid.
"""
try:
response = httpx.post(
"https://my-tool.org/api/validate",
json={"config": config_text, "version": version},
timeout=15.0,
)
response.raise_for_status()
return response.json()
except httpx.HTTPError as e:
return {"valid": False, "errors": [f"Validation service error: {e}"]}
@tool
def search_examples(query: str, limit: int = 5) -> list[dict]:
"""Search the My Tool examples database.
Args:
query: Search query describing the desired example.
limit: Maximum number of results to return.
Returns:
List of matching examples with title, description, and code.
"""
response = httpx.get(
"https://my-tool.org/api/examples",
params={"q": query, "limit": limit},
timeout=10.0,
)
response.raise_for_status()
return response.json().get("results", [])
Plugin Requirements¶
- Use
@tooldecorator - This is the LangChain standard for tool definitions - Clear docstring - Becomes the tool description the LLM sees; be specific about what the tool does and when to use it
- Type hints - All parameters must have type annotations
- Return JSON-serializable data - Dicts, lists, strings, numbers, booleans
- Handle errors gracefully - Return error information rather than raising exceptions
Registering the Plugin¶
Reference your plugin module in config.yaml:
extensions:
python_plugins:
- module: src.assistants.my-tool.tools
tools:
- validate_config
- search_examples
If tools is omitted, all names exported in the module's __all__ list that are valid tool objects are loaded:
extensions:
python_plugins:
- module: src.assistants.my-tool.tools
# All tools listed in __all__ are loaded
Your module must define __all__ listing the tools to export:
Examples from Implemented Communities¶
HED - External API integration:
| Tool | Purpose | External Dependency |
|---|---|---|
validate_hed_string | Validate HED annotations | hedtools.org REST API |
suggest_hed_tags | Natural language to HED tags | hed-lsp CLI tool |
get_hed_schema_versions | List available schema versions | hedtools.org REST API |
BIDS - Specialized knowledge lookup:
| Tool | Purpose | Data Source |
|---|---|---|
lookup_bep | Look up BIDS Extension Proposals | Synced BEP database |
EEGLAB - Community-scoped wrappers:
| Tool | Purpose | Data Source |
|---|---|---|
search_eeglab_code_docs | Search MATLAB/Python function docs | Synced docstrings database |
search_eeglab_faq | Search mailing list FAQ entries | LLM-generated FAQ database |
These tools provide domain-specific functionality that cannot be replicated in YAML configuration alone.
Best Practices¶
Do:
- Keep tools focused on a single task
- Return structured data the LLM can interpret
- Include usage guidance in the docstring (when to use, expected workflow)
- Handle network timeouts and errors
- Log errors for debugging (
logging.getLogger(__name__))
Don't:
- Create tools for documentation retrieval (use the
documentationconfig instead) - Include hardcoded secrets (use environment variables)
- Make tools that produce very long outputs (the LLM has limited context)
- Raise exceptions to the LLM (return error dicts instead)
Tool Discovery¶
The CommunityAssistant loads plugin tools during initialization:
- Imports the specified module
- Filters for functions with the
@tooldecorator - If specific
toolsnames are listed, only those are loaded - Tools are added to the LLM's tool list alongside auto-generated tools
MCP Servers¶
MCP (Model Context Protocol) servers provide an alternative extension mechanism: tools served by a separate process instead of a python_plugins module in this repository. This is implemented: src/tools/mcp_client.py discovers a server's tools and wraps each as a LangChain tool, and the NEMAR community uses it in production for its six dataset-discovery tools (see NEMAR Tools).
Every tool name is prefixed with the server's own name, so a server named nemar serving a tool called search_datasets is exposed to the assistant as nemar_search_datasets. Discovery runs against a 20-second budget and is cached for 5 minutes; a server that is unreachable or times out yields no tools and a log line rather than failing assistant startup.
Configuration¶
extensions:
mcp_servers:
# Remote server (connects via URL)
- name: remote-service
url: https://mcp.my-tool.org
The McpServer model (src/core/config/community.py) also accepts a command: [...] field for a local, subprocess-started server, and requires exactly one of command or url. Only url (remote, Streamable HTTP) servers are wired up at runtime today: discover_mcp_tools declines a command-only entry explicitly, logs a warning, and returns no tools for it, rather than attempting to spawn a process.
Local vs Remote¶
| Type | Config | Status |
|---|---|---|
| Local | command: [...] | Accepted by the schema; not implemented in the runtime |
| Remote | url: https://... | Implemented; e.g. NEMAR's https://mcp.nemar.org/mcp |
Exactly one of command or url must be provided for each server, even though only url currently does anything.
Extension Loading Order¶
When a CommunityAssistant is created, tools are loaded in this order:
- Knowledge tools from YAML config:
search_{community}_discussions- GitHub issues/PR search (ifgithub.reposconfigured)list_{community}_recent- Recent GitHub activity (ifgithub.reposconfigured)search_{community}_papers- Academic paper search (ifcitationsconfigured)search_{community}_code_docs- Code docstring search (ifdocstringsconfigured)search_{community}_faq- Mailing list FAQ search (ifmailmanconfigured)
- Documentation retrieval -
retrieve_{community}_docs - Page context -
fetch_current_page(ifenable_page_context: true) - Python plugin tools from
extensions.python_plugins - MCP server tools from
extensions.mcp_servers
All tools are available to the LLM simultaneously. The system prompt should guide the LLM on when to use each tool.