This guide provides a comprehensive overview of all development approaches for extending Frappe Assistant Core. There are two primary methods for adding custom tools, each suited for different use cases.
Best for: Custom business logic, app-specific features, client projects
External app tools are the recommended approach for most use cases. They allow you to:
- Keep tools with your business logic
- Deploy tools with your app
- Maintain separate version control
- Avoid modifying core system
Quick Start:
# In your app directory
mkdir -p your_app/assistant_tools
touch your_app/assistant_tools/__init__.pyFor detailed implementation: See EXTERNAL_APP_DEVELOPMENT.md
Best for: Core functionality, system-wide features
Internal plugins are for features that should be part of the core system:
- System utilities
- Framework integrations
- Features used across multiple apps
Quick Start:
# In frappe_assistant_core
mkdir -p plugins/my_plugin/tools
touch plugins/my_plugin/plugin.pyFor detailed implementation: See PLUGIN_DEVELOPMENT.md
| Criteria | External App Tools | Internal Plugins |
|---|---|---|
| Location | Your app directory | frappe_assistant_core/plugins |
| Deployment | With your app | With core system |
| Updates | Independent | Requires core update |
| Access | Your app only | System-wide |
| Testing | In your app | In core test suite |
| Best For | Business features | System features |
All tools inherit from BaseTool:
from frappe_assistant_core.core.base_tool import BaseTool
class MyTool(BaseTool):
def __init__(self):
super().__init__()
self.name = "my_tool"
self.description = self._get_description()
self.category = "My Category"
self.source_app = "my_app" # or "frappe_assistant_core"
def execute(self, arguments):
# Tool logic here
return {"success": True, "data": result}Tools must define input schemas following the Model Context Protocol:
self.inputSchema = {
"type": "object",
"properties": {
"param1": {"type": "string", "description": "Parameter description"},
"param2": {"type": "integer", "minimum": 0}
},
"required": ["param1"]
}Ready-to-use templates are available:
-
Tool Template:
/docs/templates/tool_template.py- Complete tool structure
- Error handling patterns
- Permission checks
- Response formatting
-
Test Template:
/docs/templates/test_template.py- Unit test patterns
- Integration tests
- Mock strategies
- Performance tests
Usage:
# Copy and customize
cp docs/templates/tool_template.py your_app/assistant_tools/my_tool.py
cp docs/templates/test_template.py your_app/tests/test_my_tool.pyfrom frappe_assistant_core.tests.base_test import BaseAssistantTest
class TestMyTool(BaseAssistantTest):
def setUp(self):
super().setUp()
# Test setup
def test_tool_functionality(self):
# Test implementation# For external app tools
bench run-tests --app your_app
# For internal plugins
bench run-tests --app frappe_assistant_coreAlways validate permissions:
if not frappe.has_permission(doctype, "read"):
return {
"success": False,
"error": "Insufficient permissions"
}Validate all inputs:
# Check DocType exists
if not frappe.db.exists("DocType", doctype):
return {
"success": False,
"error": f"DocType '{doctype}' does not exist"
}Never expose sensitive fields:
# Use get_safe_filters() for automatic filtering
safe_data = self.get_safe_filters(data)In your app's hooks.py:
# Tool registration
assistant_tools = [
"your_app.assistant_tools.sales_analyzer.SalesAnalyzer",
"your_app.assistant_tools.inventory_manager.InventoryManager"
]
# Tool configuration
assistant_tool_configs = {
"sales_analyzer": {
"max_records": 5000,
"cache_timeout": 300
}
}Plugins can use the centralized config system:
config = self.get_config() # Merges defaults with site config
max_records = config.get("max_records", 1000)- Single Responsibility: One tool, one purpose
- Clear Naming: Descriptive tool and method names
- Comprehensive Docs: Docstrings for all methods
- Type Hints: Use typing for clarity
try:
# Operation
result = perform_operation()
except Exception as e:
frappe.log_error(
title=f"{self.name} Error",
message=str(e)
)
return {
"success": False,
"error": str(e)
}- Pagination: Always paginate large datasets
- Caching: Use Frappe's cache for expensive operations
- Batch Operations: Process in batches when possible
- Timeouts: Set appropriate timeouts for long operations
Tools deploy automatically with your app:
bench get-app your_app
bench --site site1.local install-app your_app
# Tools are immediately availablePlugins are available after updating frappe_assistant_core:
bench update --apps frappe_assistant_core# In your tool
frappe.logger().debug(f"{self.name}: Processing {len(data)} records")- Tool Not Found: Check registration in hooks.py
- Permission Errors: Verify user roles and DocType permissions
- Schema Validation: Ensure MCP schema matches parameters
- Import Errors: Check all dependencies are installed
If migrating existing tools:
- Move tool class to appropriate location
- Inherit from
BaseTool - Add MCP schema definition
- Update tool registration
- Test thoroughly
- Architecture Details: ARCHITECTURE.md
- API Reference: API_REFERENCE.md
- Tool Catalog: TOOL_REFERENCE.md
- Testing Guide: TEST_CASE_CREATION_GUIDE.md
- Core Tools:
/frappe_assistant_core/core/document_tools.py - Plugin Tools:
/plugins/data_science/tools/ - External Apps: See hooks documentation
- Check existing tool implementations
- Review test cases for patterns
- Use templates as starting points
- Follow Frappe development guidelines
- Choose your approach: External app or internal plugin
- Use templates: Start with provided templates
- Follow patterns: Use existing tools as reference
- Test thoroughly: Use the test template
- Document well: Update your app's documentation