JackAI NexusDocs
DocumentationBuilding an MCP Server

Building an MCP Server

Build an MCP server to connect your business tools to JackAI Nexus

Building an MCP Server for JackAI Nexus

This guide explains how to build an MCP (Model Context Protocol) server that connects to JackAI Nexus. Your MCP server exposes tools, resources, and prompts that the AI assistant can use during conversations.


Quick Start

1. Install the MCP SDK

pip install "mcp>=1.0.0"

2. Create a Server

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("My Business Tools")

@mcp.tool()
def get_product_info(product_id: str) -> str:
    """Get product details by ID."""
    # Your business logic here
    return f"Product {product_id}: Widget Pro, $29.99"

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

3. Connect to Nexus

In the JackAI Nexus dashboard:

  1. Go to AI Assistants > select your assistant > MCP Servers
  2. Click Add MCP Server
  3. Enter:
    • Server URL: https://your-server.com/mcp
    • Transport: Streamable HTTP
    • Auth Token: your secret token (optional)
  4. Click Test Connection to verify

Server Configuration

Transport

Use Streamable HTTP (recommended). Your server will listen on a single /mcp endpoint.

mcp.run(transport="streamable-http")

Default port is 8000. To change it:

mcp.settings.port = 8100
mcp.settings.host = "0.0.0.0"

Authentication

Protect your server with a Bearer token:

import os

MCP_AUTH_TOKEN = os.getenv("MCP_AUTH_TOKEN", "your-secret-token")

# The MCP SDK handles Bearer token auth automatically
# Nexus sends: Authorization: Bearer <token>

In Nexus, enter the same token in the Authentication Token field.

Allowed Hosts

If your server is behind a reverse proxy, allow the proxy's hostname:

from mcp.server.fastmcp.server import TransportSecuritySettings

mcp.settings.transport_security = TransportSecuritySettings(
    enable_dns_rebinding_protection=True,
    allowed_hosts=[
        "localhost",
        "127.0.0.1",
        "mcp.your-domain.com",
    ],
)

Tools

Tools are functions the AI can call during conversations. They receive parameters and return text.

Basic Tool (Plain Text)

@mcp.tool()
def get_order_status(order_id: str) -> str:
    """Check the status of a customer order.

    Args:
        order_id: The order ID to look up
    """
    # Query your database/API
    return f"Order {order_id}: Shipped, arriving March 15"

Key rules:

  • Tools return text — Nexus handles sending it to the customer
  • The docstring becomes the tool description the AI sees
  • Use type hints for parameters — they become the input schema
  • Optional parameters use Optional[str] = None

Tool with Media Response

To send files (PDFs, images, audio) to the customer, return a JSON string with a media key. Nexus detects this format and sends the attachments via the messaging platform (WhatsApp, Messenger, etc.).

Document (PDF)

import json

@mcp.tool()
def send_catalog(category: str) -> str:
    """Send a product catalog PDF to the customer.

    Args:
        category: Product category (e.g., 'electronics', 'furniture')
    """
    pdf_url = f"https://your-server.com/catalogs/{category}.pdf"

    return json.dumps({
        "text": f"Here is our {category} catalog.",
        "media": [{
            "type": "document",
            "url": pdf_url,
            "filename": f"{category}-catalog.pdf",
            "mime_type": "application/pdf"
        }]
    })

Image

@mcp.tool()
def send_product_image(product_id: str) -> str:
    """Send a product image to the customer.

    Args:
        product_id: The product ID
    """
    image_url = f"https://your-server.com/images/{product_id}.jpg"

    return json.dumps({
        "text": "Here is the product image.",
        "media": [{
            "type": "image",
            "url": image_url,
            "filename": f"{product_id}.jpg",
            "mime_type": "image/jpeg"
        }]
    })

Audio / Voice Message

@mcp.tool()
def send_audio_guide(topic: str) -> str:
    """Send a pre-recorded audio explanation.

    Args:
        topic: The topic to explain
    """
    audio_url = f"https://your-server.com/audio/{topic}.opus"

    return json.dumps({
        "text": "Here is an audio explanation about the topic.",
        "media": [{
            "type": "audio",
            "url": audio_url,
            "filename": f"{topic}.opus",
            "mime_type": "audio/ogg"
        }]
    })

Media Response Format

{
  "text": "Message text shown to the customer",
  "media": [
    {
      "type": "document | image | audio",
      "url": "https://your-server.com/path/to/file",
      "filename": "display-name.pdf",
      "mime_type": "application/pdf"
    }
  ]
}
FieldRequiredDescription
textYesText message sent alongside the media
mediaYesArray of media attachments
media[].typeYesdocument, image, or audio
media[].urlYesPublic URL where Nexus can download the file
media[].filenameYesDisplay filename for the recipient
media[].mime_typeNoMIME type (auto-detected from URL if missing)

Important notes:

  • The url must be publicly accessible — Nexus downloads the file server-side
  • Multiple attachments are supported in a single response
  • Media is sent before the text message
  • If a tool returns plain text (no media key), it works as a normal text response — no changes needed
  • The text field is what the AI uses as the tool result for conversation context

Resources

Resources inject knowledge into the AI's context. They are read-only text that the AI can reference during conversations.

@mcp.resource("myapp://company-info")
def company_info() -> str:
    """Company overview, contact details, and policies."""
    return """COMPANY INFO
    Name: Acme Corp
    Phone: +1-555-0100
    Email: support@acme.com
    Hours: Mon-Fri 9am-6pm

    RETURN POLICY
    30-day returns on all products..."""


@mcp.resource("myapp://pricing")
def pricing() -> str:
    """Current product pricing and discount tiers."""
    return """PRICING
    Widget Pro: $29.99
    Widget Plus: $49.99
    Enterprise: Contact sales

    DISCOUNTS
    10+ units: 10% off
    50+ units: 20% off"""

Best practices:

  • Use resources for static knowledge (company info, FAQs, pricing, policies)
  • Keep resource content focused and concise
  • The AI reads all resources as context — don't include irrelevant data
  • URI scheme is arbitrary (e.g., myapp://, company://)

Prompts

Prompts provide behavioral guidelines for the AI. They define how the AI should interact with customers.

@mcp.prompt()
def customer_service_guidelines() -> str:
    """Role definition and behavior rules for the AI assistant."""
    return """CUSTOMER SERVICE GUIDELINES

    ROLE: You are a customer service agent for Acme Corp.

    RULES:
    1. Be polite and professional
    2. Answer questions using the provided resources
    3. If you don't know, say so — don't make up information
    4. For complaints, collect details and escalate
    5. Keep responses short and helpful"""


@mcp.prompt()
def conversation_flow() -> str:
    """Step-by-step conversation flow."""
    return """CONVERSATION FLOW

    STEP 1: Greet the customer
    STEP 2: Identify their need
    STEP 3: Provide information using tools
    STEP 4: Guide to next steps
    STEP 5: Escalate if needed"""

Tips:

  • Prompts guide the AI's behavior — define the role, tone, and rules
  • Reference tool names in prompts so the AI knows when to use them
  • Keep instructions clear and specific

Complete Example

Here's a minimal but complete MCP server:

#!/usr/bin/env python
"""My Business MCP Server"""
import json
import os
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("My Business Tools")

# --- Configuration ---
SITE_URL = os.getenv("SITE_URL", "https://my-business.com")
MCP_PORT = int(os.getenv("MCP_PORT", "8100"))

# --- Resources (AI context) ---
@mcp.resource("biz://company-info")
def company_info() -> str:
    """Company overview and contact information."""
    return "Company: My Business Inc. Phone: +1-555-0100..."

# --- Prompts (AI behavior) ---
@mcp.prompt()
def guidelines() -> str:
    """Customer service behavior rules."""
    return "You are a helpful assistant for My Business Inc..."

# --- Tools (AI actions) ---
@mcp.tool()
def get_product_info(product: str) -> str:
    """Get information about a product.
    Args:
        product: Product name or ID
    """
    return f"Product details for {product}..."

@mcp.tool()
def send_catalog(category: str) -> str:
    """Send a product catalog PDF.
    Args:
        category: Product category
    """
    return json.dumps({
        "text": f"Here is our {category} catalog.",
        "media": [{
            "type": "document",
            "url": f"{SITE_URL}/catalogs/{category}.pdf",
            "filename": f"{category}-catalog.pdf",
            "mime_type": "application/pdf"
        }]
    })

# --- Run ---
if __name__ == "__main__":
    mcp.settings.port = MCP_PORT
    mcp.settings.host = "0.0.0.0"
    mcp.run(transport="streamable-http")

Deployment

Docker

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "server.py"]
# docker-compose.yml
services:
  mcp_server:
    build: .
    ports:
      - "127.0.0.1:8100:8100"
    environment:
      - MCP_AUTH_TOKEN=your-secret-token
      - SITE_URL=https://your-domain.com
    restart: always

HTTPS

Nexus connects to your server via HTTPS. Options:

  • Cloudflare proxy (recommended) — free SSL, set DNS to proxied (orange cloud)
  • Let's Encrypt — use certbot with nginx
  • Cloudflare Origin Certificate — for Full (strict) SSL mode

Nginx reverse proxy example:

server {
    listen 443 ssl;
    server_name mcp.your-domain.com;

    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:8100;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;
    }
}

Nexus Connection Settings

SettingDescriptionDefault
Server URLYour MCP server endpoint
TransportStreamable HTTP (recommended) or SSEStreamable HTTP
Auth TokenBearer token for authentication(optional)
Cache TTLHow often to re-discover tools (seconds)3600
TimeoutMax wait for tool execution (seconds)30
Static ParametersFixed values auto-injected into every tool call(optional)
Custom HeadersExtra HTTP headers for the MCP server(optional)
Verify SSLVerify the server's SSL certificatetrue

Static Parameters

Use static parameters to inject context that every tool call needs (e.g., tenant ID, API keys). These are added automatically to all tool calls from Nexus.

Tool Filter

By default, all discovered tools are enabled. You can select specific tools to enable if you only want the AI to use a subset.


Troubleshooting

ProblemSolution
Connection refusedCheck server is running and port is correct
SSL certificate errorSet verify_ssl: false in config, or install a valid cert
403 ForbiddenCheck auth token matches between Nexus and server
Tools not discoveredClick Refresh on the MCP server card
Media not deliveredEnsure the url in media response is publicly accessible
Audio not playing on WhatsAppUse .opus or .ogg format — WhatsApp requires OGG/Opus