# Building Custom MCPs

Build powerful custom integrations with MCP Studio's visual builder. Write Python or Node.js code with AI assistance, validate your integration, and deploy to production—all from your browser.

{% hint style="success" %}
**AI-Powered Development**: Describe what you want in plain English, and DarcyIQ will generate the code for you. Edit, test, and deploy without leaving the browser.
{% endhint %}

## Overview

The MCP Builder provides a complete development environment for creating custom integrations. Whether you're connecting to a proprietary API, building a custom database connector, or creating specialized tools, the builder has everything you need.

| Feature                  | Description                                   |
| ------------------------ | --------------------------------------------- |
| **AI Code Generation**   | Describe your goal in natural language        |
| **Visual Code Editor**   | Full-featured editor with syntax highlighting |
| **Real-Time Validation** | Catch errors before deployment                |
| **Integrated Testing**   | Test your tools without deploying             |
| **Secrets Management**   | Secure credential storage                     |
| **Auto-Save**            | Never lose your work                          |

<figure><img src="/files/wuAximEEIq9HBDX0VvTV" alt=""><figcaption></figcaption></figure>

## The 5-Tab Workflow

MCP Studio guides you through building an integration with five tabs:

```mermaid
graph LR
    A[Create<br/>Start] --> B[Build<br/>Code]
    B --> C[Deploy<br/>Production]
    C --> D[Connect<br/>Use]
    D --> E[Monitor<br/>Infrastructure]
```

| Tab         | Purpose                | Key Actions                                  |
| ----------- | ---------------------- | -------------------------------------------- |
| **Start**   | Begin your integration | AI generation, templates, or blank canvas    |
| **Build**   | Write and test code    | Edit code, configure secrets, validate, test |
| **Deploy**  | Deploy to production   | Validate and deploy with streaming progress  |
| **Connect** | Enable the integration | Toggle for Darcy, get connection details     |
| **Monitor** | Debug and troubleshoot | View real-time logs                          |

## Creating a New MCP Server

{% stepper %}
{% step %}
**Navigate to MCP Studio**

From the DarcyIQ sidebar, click **MCP Studio**, then select the **My MCPs** tab.
{% endstep %}

{% step %}
**Click Create MCP**

Click the **Create MCP** button in the top right corner.
{% endstep %}

{% step %}
**Name Your Integration**

Enter a descriptive name for your integration (e.g., "Customer Database Connector" or "Slack Notifier").
{% endstep %}

{% step %}
**Choose Your Starting Point**

You'll be taken to the **Start** tab where you can choose how to begin.
{% endstep %}
{% endstepper %}

## Start Tab: Choose Your Approach

The Start tab offers three ways to begin building:

### Option 1: AI Code Generation (Recommended)

Let DarcyIQ write the code for you:

1. **Describe Your Goal**: In the text area, describe what you want your integration to do
2. **Click Generate**: DarcyIQ analyzes your request and generates complete code
3. **Review and Edit**: The generated code appears in the Build tab for review

**Example Prompts:**

| Goal                     | Prompt Example                                                                                       |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| **Database Query**       | "Create an integration that connects to PostgreSQL and allows querying customer data by email or ID" |
| **API Integration**      | "Build an integration for the Stripe API that can retrieve payment history and customer details"     |
| **Notification Service** | "Create a Slack integration that can send messages to any channel with formatting support"           |
| **File Processing**      | "Build an integration that reads CSV files and returns the data as structured JSON"                  |

### Option 2: Start from a Template

Choose from pre-built templates:

| Template             | Description                         | Best For               |
| -------------------- | ----------------------------------- | ---------------------- |
| **Basic Python**     | Minimal Python MCP server           | Simple integrations    |
| **Python with Auth** | Python template with authentication | Secure API connections |
| **Basic Node.js**    | Minimal Node.js MCP server          | JavaScript developers  |
| **REST API Wrapper** | Template for wrapping REST APIs     | API integrations       |

### Option 3: Blank Canvas

Start with an empty editor and write everything from scratch. Recommended only for experienced MCP developers or importing existing MCPs into MCP Studio.

## Build Tab: Code Editor

The Build tab is where you write, edit, and test your integration code.

### Code Editor Features

| Feature                 | Description                                 |
| ----------------------- | ------------------------------------------- |
| **Syntax Highlighting** | Full color coding for Python and JavaScript |
| **Error Detection**     | Real-time validation as you type            |
| **Auto-Detection**      | Runtime automatically detected from code    |
| **Line Numbers**        | Easy navigation and debugging               |
| **Auto-Save**           | Changes saved automatically every 3 seconds |

### Writing MCP Server Code

Your MCP server code defines the tools that will be available to DarcyIQ. Here's the basic structure:

**Python Example:**

```python
from mcp.server import Server
from mcp.types import TextContent
import os

# Create the server
app = Server("my-integration")

# Define a tool
@app.tool()
async def get_customer(customer_id: str) -> list[TextContent]:
    """
    Retrieve customer information by ID.
    
    Args:
        customer_id: The unique identifier for the customer
    
    Returns:
        Customer details including name, email, and account status
    """
    # Your integration logic here
    api_key = os.environ.get("API_KEY")
    
    # Fetch customer data
    customer = await fetch_customer_from_api(customer_id, api_key)
    
    return [TextContent(
        type="text",
        text=f"Customer: {customer['name']}, Email: {customer['email']}"
    )]

# Run the server
if __name__ == "__main__":
    app.run()
```

**Node.js Example:**

```javascript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { TextContent } from "@modelcontextprotocol/sdk/types.js";

const server = new Server({
  name: "my-integration",
  version: "1.0.0"
});

server.setRequestHandler("tools/call", async (request) => {
  const { name, arguments: args } = request.params;
  
  if (name === "get_customer") {
    const apiKey = process.env.API_KEY;
    const customer = await fetchCustomer(args.customer_id, apiKey);
    
    return {
      content: [{
        type: "text",
        text: `Customer: ${customer.name}, Email: ${customer.email}`
      }]
    };
  }
});

server.run();
```

### Tool Documentation

{% hint style="info" %}
**Best Practice**: Write clear docstrings for your tools. The AI uses these descriptions to understand when and how to use each tool.
{% endhint %}

Good documentation includes:

| Element               | Purpose                                | Example                                                      |
| --------------------- | -------------------------------------- | ------------------------------------------------------------ |
| **Brief Description** | One-line summary of what the tool does | "Retrieve customer information by ID"                        |
| **Args Section**      | Document each parameter                | "customer\_id: The unique identifier for the customer"       |
| **Returns Section**   | Describe the output                    | "Customer details including name, email, and account status" |

### Validation

Click the **Validate** button to check your code for errors:

| Validation Type       | What It Checks                       |
| --------------------- | ------------------------------------ |
| **Syntax Errors**     | Code compiles without errors         |
| **Import Validation** | All imports are available            |
| **Tool Detection**    | At least one tool is defined         |
| **Secret Detection**  | Environment variables are identified |

Validation results appear inline with your code:

| Status         | Meaning                         |
| -------------- | ------------------------------- |
| ✅ **Valid**    | Code passes all checks          |
| ⚠️ **Warning** | Non-critical issues to review   |
| ❌ **Error**    | Must be fixed before deployment |

### Secrets Management

The Secrets section lets you configure environment variables securely:

{% stepper %}
{% step %}
**Auto-Detection**

MCP Studio automatically detects environment variables used in your code (e.g., `os.environ.get("API_KEY")`).
{% endstep %}

{% step %}
**Configure Secrets**

For each detected secret, provide:

* **Name**: The environment variable name (auto-filled)
* **Display Name**: A friendly label for the UI
* **Description**: What this credential is for
* **Required**: Whether deployment requires this value
  {% endstep %}

{% step %}
**Enter Values**

Enter the actual secret values. These are stored securely and never exposed in code or logs.
{% endstep %}
{% endstepper %}

### Testing Your Tools

The Test Panel lets you test tools before deployment:

{% stepper %}
{% step %}
**Open Test Panel**

Click **Test** in the Build tab toolbar to open the test panel.
{% endstep %}

{% step %}
**Select a Tool**

Choose which tool to test from the dropdown.
{% endstep %}

{% step %}
**Enter Parameters**

Fill in the required parameters for the tool.
{% endstep %}

{% step %}
**Run Test**

Click **Run** to execute the tool. Results appear in the panel.
{% endstep %}
{% endstepper %}

## Runtime Support

MCP Studio supports two runtimes:

### Python

| Aspect               | Details                          |
| -------------------- | -------------------------------- |
| **Version**          | Python 3.11+                     |
| **Package Manager**  | pip with requirements.txt        |
| **MCP SDK**          | `mcp` package                    |
| **Common Libraries** | requests, httpx, asyncio, pandas |

**Specifying Dependencies:**

Add a comment block at the top of your file:

```python
# requirements:
# requests>=2.28.0
# pandas>=2.0.0
# httpx>=0.24.0

from mcp.server import Server
# ... rest of your code
```

### Node.js

| Aspect               | Details                     |
| -------------------- | --------------------------- |
| **Version**          | Node.js 18+                 |
| **Package Manager**  | npm with package.json       |
| **MCP SDK**          | `@modelcontextprotocol/sdk` |
| **Common Libraries** | axios, node-fetch, lodash   |

**Specifying Dependencies:**

Dependencies are auto-detected from import statements, or you can include a package.json comment:

```javascript
/* package.json:
{
  "dependencies": {
    "axios": "^1.4.0",
    "lodash": "^4.17.21"
  }
}
*/

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
// ... rest of your code
```

## Best Practices for Custom MCPs

### Code Organization

| Practice                  | Description                                        |
| ------------------------- | -------------------------------------------------- |
| **Single Responsibility** | Each tool should do one thing well                 |
| **Clear Naming**          | Use descriptive tool and parameter names           |
| **Error Handling**        | Always handle potential errors gracefully          |
| **Logging**               | Add logging for debugging (visible in Monitor tab) |

### Error Handling

```python
@app.tool()
async def safe_api_call(endpoint: str) -> list[TextContent]:
    """Make a safe API call with error handling."""
    try:
        response = await make_request(endpoint)
        return [TextContent(type="text", text=response)]
    except ConnectionError:
        return [TextContent(type="text", text="Error: Could not connect to the API")]
    except AuthenticationError:
        return [TextContent(type="text", text="Error: Invalid API credentials")]
    except Exception as e:
        return [TextContent(type="text", text=f"Error: {str(e)}")]
```

### Security Considerations

| Practice                      | Description                              |
| ----------------------------- | ---------------------------------------- |
| **Use Environment Variables** | Never hardcode credentials               |
| **Validate Input**            | Check parameters before using them       |
| **Limit Scope**               | Only request permissions you need        |
| **Sanitize Output**           | Don't expose sensitive data in responses |

## Common Patterns

### REST API Wrapper

```python
@app.tool()
async def api_request(
    method: str,
    endpoint: str,
    body: str = None
) -> list[TextContent]:
    """
    Make a request to the external API.
    
    Args:
        method: HTTP method (GET, POST, PUT, DELETE)
        endpoint: API endpoint path
        body: Optional JSON body for POST/PUT requests
    """
    base_url = os.environ.get("API_BASE_URL")
    api_key = os.environ.get("API_KEY")
    
    headers = {"Authorization": f"Bearer {api_key}"}
    url = f"{base_url}{endpoint}"
    
    async with httpx.AsyncClient() as client:
        response = await client.request(
            method=method,
            url=url,
            headers=headers,
            json=json.loads(body) if body else None
        )
        return [TextContent(type="text", text=response.text)]
```

### Database Query

```python
@app.tool()
async def query_database(sql: str) -> list[TextContent]:
    """
    Execute a read-only SQL query.
    
    Args:
        sql: The SQL query to execute (SELECT only)
    """
    if not sql.strip().upper().startswith("SELECT"):
        return [TextContent(type="text", text="Error: Only SELECT queries allowed")]
    
    connection_string = os.environ.get("DATABASE_URL")
    
    async with asyncpg.connect(connection_string) as conn:
        rows = await conn.fetch(sql)
        result = [dict(row) for row in rows]
        return [TextContent(type="text", text=json.dumps(result, indent=2))]
```

## Next Steps

Once your code is ready, proceed to deployment:

| Goal                    | Documentation                                                                        |
| ----------------------- | ------------------------------------------------------------------------------------ |
| Deploy your integration | [Deploying & Managing](/integrations-and-configuration/mcp-studio/deploying-mcps.md) |
| View more code examples | [Code Samples](/integrations-and-configuration/mcp-studio/mcp-studio-samples.md)     |
| Troubleshoot issues     | [Troubleshooting](/integrations-and-configuration/mcp-studio/troubleshooting.md)     |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.darcyiq.com/integrations-and-configuration/mcp-studio/building-custom-mcps.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
