Skip to main content

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:

  1. Write the tool code
  2. Deploy a new version of your MCP server
  3. 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:

  1. AI Agent — Any MCP-compatible client (Claude, custom agents, etc.)
  2. MCP Server — Exposes CRUD operations + a "Run Tool" meta-tool
  3. Tool Registry — Database storing tool definitions (name, description, code)
  4. 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:

  1. Looks up the requested tool in the database
  2. Fetches its code
  3. Passes any parameters as context
  4. Executes in a sandbox
  5. 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 context object
  • Use return to 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:

  1. Identify a need — "I need to check stock prices but don't have a tool for that"
  2. Create the tool — Write and register a stock_price tool
  3. Use the tool — Call it immediately with parameters
  4. 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:

  1. Main MCP workflow receives the request
  2. Triggers a separate execution workflow
  3. Execution workflow queries DB, calls sandbox, returns result
  4. 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.

Need help with your project or have questions?

We specialize in AI automation, custom integrations, and intelligent workflows tailored to your business needs.

Whether you need help deploying, building, implementing, or creating a solution - or just want expert guidance on your project - we're here to help.

Contact us today to discuss your project.