Build a Self-Extending AI: Dynamic MCP Tools with a Deno Sandbox
What if your AI assistant could create its own tools? Not just use pre-built integrations, but actually write, store, and execute new capabilities on demand?
This isn't science fiction. By combining MCP (Model Context Protocol), a database-backed tool registry, and a secure code execution sandbox, you can build an AI system that extends itself at runtime.
Here's the architecture we built—and why each piece matters.
The Problem with Static Tools
Traditional MCP setups have a fixed set of tools. Want to add a new API integration? You need to:
- Write the tool code
- Deploy a new version of your MCP server
- Restart your AI client to pick up the changes
This works fine for stable, well-defined integrations. But it falls apart when you need flexibility—when you want the AI to adapt to new requirements without developer intervention.
The Dynamic Tools Architecture
flowchart TB
subgraph AI["AI Agent (Claude, etc.)"]
A[User Request]
end
subgraph MCP["MCP Server (n8n)"]
B[MCP Trigger]
C[List Tools]
D[Create Tool]
E[Update Tool]
F[Delete Tool]
G[Run Tool]
end
subgraph Storage["Persistence Layer"]
H[(Tool Registry DB)]
end
subgraph Execution["Execution Layer"]
I[Code Executor API]
J[Deno Sandbox]
end
A --> B
B --> C & D & E & F & G
C & D & E & F --> H
G --> H
H -->|fetch code| G
G --> I
I --> J
J -->|result| G
G -->|response| B
style AI fill:#e3f2fd
style MCP fill:#fff3e0
style Storage fill:#e8f5e9
style Execution fill:#fce4ec
The system has four layers:
- AI Agent — Any MCP-compatible client (Claude, custom agents, etc.)
- MCP Server — Exposes CRUD operations + a "Run Tool" meta-tool
- Tool Registry — Database storing tool definitions (name, description, code)
- Execution Sandbox — Secure runtime for executing tool code
Why a Database-Backed Registry?
Storing tools in a database instead of hardcoding them gives you:
Runtime flexibility — Create new tools without redeploying anything
Versioning — Track when tools were created and modified
Discoverability — AI can list available tools and read their implementations
Persistence — Tools survive server restarts
The schema is simple:
CREATE TABLE mcp_tools (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT NOT NULL,
code TEXT NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP,
modified DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
The Five Core MCP Tools
The MCP server exposes just five tools that enable everything:
| Tool | Purpose |
|---|---|
| List Tools | Returns all available dynamic tools |
| Get Tool | Retrieves a tool's full definition including code |
| Create Tool | Stores a new tool in the registry |
| Update Tool | Modifies an existing tool |
| Delete Tool | Removes a tool from the registry |
| Run Tool | Executes a tool by name with parameters |
The magic is in Run Tool. It's a meta-tool that:
- Looks up the requested tool in the database
- Fetches its code
- Passes any parameters as context
- Executes in a sandbox
- Returns the result
sequenceDiagram
participant AI as AI Agent
participant MCP as MCP Server
participant DB as Tool Registry
participant Sandbox as Deno Sandbox
AI->>MCP: Run Tool("weather_checker", {city: "NYC"})
MCP->>DB: SELECT code FROM tools WHERE name = 'weather_checker'
DB-->>MCP: return tool code
MCP->>Sandbox: Execute code with context {city: "NYC"}
Sandbox->>Sandbox: fetch weather API, process data
Sandbox-->>MCP: return {temp: 72, conditions: "sunny"}
MCP-->>AI: Tool result
Why Deno for the Sandbox?
We evaluated several approaches for secure code execution:
| Approach | Pros | Cons |
|---|---|---|
| Node.js VM | Fast, same runtime | Weak isolation, no npm access |
| isolated-vm | Strong V8 isolation | Complex boundary crossing, no native npm |
| Docker containers | Full isolation | 500ms+ cold start, heavy |
| Deno subprocess | Good isolation, native npm | ~50ms cold start |
We chose Deno because:
Permission model — Granular control over network, filesystem, and subprocess access
Native npm support — Use any package with import x from "npm:package"
TypeScript built-in — No compilation step needed
Simple sandboxing — Just spawn a subprocess with restricted permissions
flowchart LR
subgraph Allowed["Allowed"]
A[fetch API calls]
B[npm packages]
C[TypeScript]
D[async/await]
end
subgraph Denied["Denied"]
E[Filesystem read]
F[Filesystem write]
G[Spawn processes]
H[Environment vars]
end
style Allowed fill:#c8e6c9
style Denied fill:#ffcdd2
The sandbox runs with these flags:
--allow-net— Enable HTTP requests--deny-read— Block filesystem access--deny-write— Block file creation--deny-run— Block subprocess spawning--deny-ffi— Block native library loading
What Tool Code Looks Like
Tools are just JavaScript/TypeScript that:
- Access parameters via a
contextobject - Use
returnto output results - Can import any npm package
Simple example:
const name = context.name || "World";
return `Hello, ${name}!`;
API integration:
const response = await fetch(`https://api.weather.com/${context.city}`);
const data = await response.json();
return {
temperature: data.temp,
conditions: data.weather[0].description
};
Using npm packages:
import _ from "npm:lodash";
const data = await fetch(context.apiUrl).then(r => r.json());
return _.chain(data.items)
.filter(item => item.active)
.sortBy("priority")
.take(10)
.value();
The Self-Extension Loop
Here's where it gets interesting. The AI can:
- Identify a need — "I need to check stock prices but don't have a tool for that"
- Create the tool — Write and register a
stock_pricetool - Use the tool — Call it immediately with parameters
- Iterate — Update the tool if it needs improvements
flowchart TD
A[AI identifies capability gap] --> B{Tool exists?}
B -->|No| C[Create new tool]
B -->|Yes| D[Use existing tool]
C --> D
D --> E{Result satisfactory?}
E -->|No| F[Update tool code]
F --> D
E -->|Yes| G[Return result to user]
style A fill:#e3f2fd
style C fill:#c8e6c9
style F fill:#fff3e0
style G fill:#e8f5e9
This creates a feedback loop where the AI system grows more capable over time, learning which tools it needs and refining their implementations.
Security Considerations
Dynamic code execution requires careful security design:
Sandbox isolation — Deno's permission system prevents filesystem access and process spawning
Network restrictions — Optionally limit which domains the sandbox can reach
No secret access — Environment variables from the host are not passed to the sandbox
Timeout enforcement — Long-running or infinite loops are terminated
Input validation — Tool names must match a strict pattern (lowercase, underscores only)
For production use, consider:
- Rate limiting tool creation and execution
- Audit logging of all tool operations
- Review workflows for new tool creation
- Separate execution environments per user/tenant
Performance Characteristics
| Operation | Typical Latency |
|---|---|
| List Tools | 10-20ms |
| Create/Update/Delete Tool | 15-30ms |
| Run Tool (cached packages) | 50-150ms |
| Run Tool (first npm import) | 500ms-2s |
The first run with a new npm package takes longer as Deno downloads and caches it. Subsequent runs use the cached version.
When to Use This Pattern
Good fit:
- AI assistants that need to adapt to new integrations
- Prototyping and experimentation environments
- Multi-tenant systems where each user needs custom tools
- Self-healing automation that can write its own fixes
Not ideal for:
- High-frequency, latency-sensitive operations
- Tools requiring persistent state or database connections
- Operations needing filesystem access
- Highly sensitive environments where code review is mandatory
Implementation Notes
The system uses three main components:
n8n as MCP Server — Handles the MCP protocol, tool routing, and workflow orchestration
MySQL for Tool Storage — Simple, reliable persistence for tool definitions
Deno Subprocess for Execution — Spawned per-request with restricted permissions
The "Run Tool" flow uses a sub-workflow pattern:
- Main MCP workflow receives the request
- Triggers a separate execution workflow
- Execution workflow queries DB, calls sandbox, returns result
- Main workflow returns result to AI
This separation keeps the MCP server simple and makes the execution logic reusable.
What's Next
This foundation enables more advanced patterns:
Tool composition — Tools that call other tools
Scheduled tools — Run tools on a cron schedule
Tool versioning — Keep multiple versions, roll back if needed
Tool sharing — Export/import tools between systems
AI-generated tests — Have the AI write tests for its own tools
The key insight is that by making tool creation a first-class operation, you transform your AI from a fixed system into an evolving one. It's not just using tools—it's building them.
The irony isn't lost on us: this article was written by an AI using the exact system it describes.