Are you the author? Sign in to claim
An MCP server that allows users to dynamically add custom tools/functions at runtime
A Model Context Protocol (MCP) server that allows you to create custom tools/functions at runtime in any programming language and expose them to Claude or other MCP clients.
The DIY Tools MCP server enables you to dynamically add custom tools without needing to write a full MCP server. Simply provide the function code, parameters schema, and the server handles the rest - validation, execution, persistence, and MCP protocol integration.
This server bridges the gap between simple function definitions and the MCP protocol, making it easy to extend Claude's capabilities with custom tools written in Python, JavaScript, Bash, Ruby, or TypeScript.
# Clone the repository
git clone https://github.com/yourusername/diy-tools-mcp.git
cd diy-tools-mcp
# Install dependencies
npm install
# Build the project
npm run build
# Start the server
npm start
# Or for development with auto-reload
npm run dev
The server provides four built-in tools:
add_tool - Register a new custom functionremove_tool - Remove a registered functionlist_tools - List all available custom toolsview_source - View the source code of a registered tool{
"name": "calculate_factorial",
"description": "Calculate the factorial of a number",
"language": "python",
"code": "def main(n):\n if n <= 1:\n return 1\n return n * main(n - 1)",
"parameters": {
"type": "object",
"properties": {
"n": {
"type": "integer",
"description": "The number to calculate factorial for",
"minimum": 0
}
},
"required": ["n"]
},
"returns": "The factorial of the input number"
}
{
"name": "format_date",
"description": "Format a date string",
"language": "javascript",
"code": "function main({ date, format }) {\n const d = new Date(date);\n if (format === 'short') {\n return d.toLocaleDateString();\n }\n return d.toLocaleString();\n}",
"parameters": {
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "Date string to format"
},
"format": {
"type": "string",
"enum": ["short", "long"],
"default": "long"
}
},
"required": ["date"]
}
}
{
"name": "system_info",
"description": "Get basic system information",
"language": "bash",
"code": "main() {\n echo '{\"os\": \"'$(uname -s)'\", \"kernel\": \"'$(uname -r)'\", \"arch\": \"'$(uname -m)'\"}'\n}",
"parameters": {
"type": "object",
"properties": {}
}
}
You can now define functions in separate files instead of inline code. This is useful for:
my_function.py:from datetime import datetime
def main(name, age):
"""
Generate a personalized greeting.
"""
return {
"greeting": f"Hello {name}!",
"message": f"You are {age} years old.",
"timestamp": datetime.now().isoformat()
}
{
"name": "personalized_greeting",
"description": "Generate a personalized greeting with timestamp",
"language": "python",
"codePath": "./my_function.py",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Person's name"
},
"age": {
"type": "integer",
"description": "Person's age"
}
},
"required": ["name", "age"]
}
}
data_processor.js:async function main({ data, format }) {
// Process data based on format
if (format === 'csv') {
return processCSV(data);
} else if (format === 'json') {
return processJSON(data);
}
throw new Error(`Unsupported format: ${format}`);
}
function processCSV(data) {
// CSV processing logic
return { processed: true, format: 'csv', rows: data.split('\n').length };
}
function processJSON(data) {
// JSON processing logic
const parsed = JSON.parse(data);
return { processed: true, format: 'json', keys: Object.keys(parsed) };
}
module.exports = { main };
{
"name": "data_processor",
"description": "Process data in various formats",
"language": "javascript",
"codePath": "./data_processor.js",
"parameters": {
"type": "object",
"properties": {
"data": {
"type": "string",
"description": "Raw data to process"
},
"format": {
"type": "string",
"enum": ["csv", "json"],
"description": "Data format"
}
},
"required": ["data", "format"]
}
}
You can now specify any function name as the entry point, not just main. This allows you to:
math_utils.py:def calculate_tax(income, tax_rate):
"""Calculate tax amount."""
return {
"tax_amount": income * tax_rate,
"net_income": income * (1 - tax_rate)
}
def compound_interest(principal, rate, time):
"""Calculate compound interest."""
amount = principal * (1 + rate) ** time
return {
"principal": principal,
"interest": amount - principal,
"total": amount
}
// Tax calculator tool
{
"name": "tax_calculator",
"description": "Calculate tax and net income",
"language": "python",
"codePath": "./math_utils.py",
"entryPoint": "calculate_tax", // Specify which function to use
"parameters": {
"type": "object",
"properties": {
"income": { "type": "number" },
"tax_rate": { "type": "number" }
},
"required": ["income", "tax_rate"]
}
}
// Interest calculator tool
{
"name": "interest_calculator",
"description": "Calculate compound interest",
"language": "python",
"codePath": "./math_utils.py",
"entryPoint": "compound_interest", // Different function from same file
"parameters": {
"type": "object",
"properties": {
"principal": { "type": "number" },
"rate": { "type": "number" },
"time": { "type": "number" }
},
"required": ["principal", "rate", "time"]
}
}
If no entryPoint is specified, the system defaults to looking for a function named main for backward compatibility.
Use the view_source tool to inspect any registered function:
{
"name": "view_source",
"arguments": {
"name": "data_processor",
"verbose": true
}
}
The verbose option includes full metadata about the tool in addition to the source code.
python) - Requires Python 3.xjavascript or node) - Requires Node.jsbash) - Requires Bash shelltypescript) - Transpiled and run as JavaScriptruby) - Requires Rubymain function that accepts keyword argumentsdef main(x, y):
return x + y
main function (regular or async)function main({ x, y }) {
return x + y;
}
main functionmain() {
# Parse JSON args if needed
echo '{"result": "success"}'
}
main method that accepts keyword argumentsdef main(name:, age:)
{ greeting: "Hello #{name}, you are #{age} years old!" }
end
You can specify a timeout for each function (in milliseconds):
{
"timeout": 5000 // 5 seconds
}
Maximum timeout is 300000ms (5 minutes).
For Python functions, you can specify required packages:
{
"dependencies": ["numpy", "pandas"]
}
Note: Automatic dependency installation is not yet implemented.
The server provides detailed error messages for:
Functions are stored in the functions/ directory as JSON files. Each file contains:
# Run in development mode with auto-reload
npm run dev
# Run tests
npm test
# Build for production
npm run build
# Clean build artifacts
npm run clean
When using file-based functions, the server implements multiple security layers:
/etc, /usr/bin, /System (macOS), C:\Windows~/.ssh, ~/.aws, etc.)eval() and exec() callsrm -rf /, etc.)main entry pointUse file-based functions for:
Use inline functions for:
Organize your functions for maintainability:
my-mcp-tools/
├── functions/ # Auto-managed metadata (don't edit)
├── function-code/ # Auto-managed copies (don't edit)
└── my-functions/ # Your source files
├── data/
│ ├── processor.js
│ └── validator.py
├── ml/
│ ├── predictor.py
│ └── trainer.py
└── tests/
├── test_processor.js
└── test_validator.py
returns field to explain output formatExisting inline functions continue to work without changes. To migrate an inline function to file-based:
Extract the code to a new file:
# Before (inline)
"code": "def main(x, y):\n return x + y"
# After (calculator.py)
def main(x, y):
return x + y
Update the tool definition:
// Before
{
"name": "calculator",
"code": "def main(x, y):\n return x + y",
...
}
// After
{
"name": "calculator",
"codePath": "./my-functions/calculator.py",
...
}
Re-register the tool:
remove_tool to remove the old inline versionadd_tool with the new file-based definitionThe server automatically handles the transition, copying the file to its managed directory and preserving all functionality.
view_source)main function entry point with comprehensive validationmain)Contributions are welcome! Please:
MIT
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows