Skip to main content

Why Integrate Your MCP Server?

Integrating your MCP server with Mindset AI enables powerful AI-driven automation for your organization:
  • Extend AI Capabilities: Your custom tools and data sources become accessible to AI agents, enabling them to perform domain-specific tasks unique to your business
  • Maintain Control: You retain full control over your data and business logic while leveraging Mindset AI’s Agents
  • User-Specific Actions: AI agents can perform actions on behalf of specific users in your system, maintaining proper access controls and audit trails
  • Seamless Integration: Once registered, your tools appear natively within the Mindset AI platform, requiring no additional integration work

Overview

This guide describes how to implement and register your Model Context Protocol (MCP) server with the Mindset AI platform. Once integrated, your MCP server’s tools can be made available to your AI agents running on our platform.

Architecture

The integration is straightforward:
  1. You implement an MCP server that validates API keys and provides tools
  2. You register your server URL and API key with Mindset AI
  3. Mindset AI sends requests to your server with the API key and user context
  4. Your server validates both and processes the requests

Critical Decision: Use an MCP Library

Recommended: Use FastMCP or the Official MCP SDK

We strongly recommend using an established MCP library: Python (Recommended): TypeScript/JavaScript:

Why This Matters: Common Integration Issues

Manual JSON-RPC/MCP implementations can encounter protocol compliance issues:
  1. Tool responses are missing required result/error field
  2. Notification handling is incorrect → Protocol violations
  3. Missing MCP-specific fields → Incomplete implementation
  4. Inconsistent error codes → Poor error handling
These issues arise from the complexity of correctly implementing all JSON-RPC 2.0 and MCP specification requirements, including edge cases like notification handling and ID type preservation. With FastMCP/SDK: Protocol compliance is handled automatically, eliminating these common pitfalls.

Manual Implementation Requirements

If you choose to implement without a library (not recommended), you must thoroughly familiarize yourself with both the JSON-RPC 2.0 and MCP specifications, and ensure your server is fully compliant with both standards:
  1. JSON-RPC 2.0 Specification
    • Required response fields (result XOR error)
    • Notification handling (requests without id)
    • Error codes and structures
    • ID type preservation
  2. Model Context Protocol Specification
    • Tool discovery (tools/list)
    • Tool execution (tools/call)
    • Response content structures
    • Optional MCP fields (isError, structuredContent)
  3. Validation Requirements
    • You must pass all tests in the “Testing Your Implementation” section below
    • You must handle all edge cases that libraries handle automatically
    • You must stay current with spec updates
Important: Manual implementation requires careful attention to protocol details. Missing or incorrectly formatted fields will cause agent integration to fail. We strongly recommend using an MCP library unless you have specific requirements that prevent it.

When Manual Implementation Might Make Sense

Consider manual implementation only if:
  • You have an existing JSON-RPC infrastructure to integrate with
  • You need specific protocol customizations that libraries don’t support
  • You have deep JSON-RPC/MCP expertise on your team

Authentication

Authentication uses simple API key validation:
  1. You generate a secure API key for your MCP server (this is a secret you create and control)
  2. You provide this key to Mindset AI when registering your MCP server
  3. Mindset AI stores this securely and includes it in the Authorization header as a Bearer token with every request
  4. Your MCP server validates the API key before processing requests
Important: The API key is generated by you, not by Mindset AI. Use a cryptographically secure random string (e.g., openssl rand -hex 32 or equivalent in your preferred language). You’ll provide this key to Mindset AI during the MCP server registration process.

Registration vs Runtime Authentication

Registration endpoints must only validate API keys Your MCP server will be called in two different contexts:

1. Registration Context (No Agent Session)

When customers register your MCP server with Mindset AI, the platform calls these endpoints to validate the server:
  • initialize - Establish connection and capabilities
  • notifications/initialized - Confirm initialization
  • tools/list - Discover available tools
During registration, no agent session exists yet. This means that the following headers cannot be used for authentication purposes during registration:
  • x-session-tags
  • x-user-id
Your server should not require session-specific validation (like checking for keys in x-session-tags) for these endpoints. If you do, registration will fail because no session data exists during registration. These endpoints should only validate the API key in the Authorization header.

2. Runtime Context (if the AgentSessions API is used to create Agent sessions)

When agents call your tools during conversations, the platform calls:
  • tools/call - Execute a specific tool
During runtime, if you are using the AgentSessions API, an agent session exists. This means:
  • x-session-tags will contain session-specific tags
  • x-user-id will identify the current user
  • x-app-uid will identify the application
Your server can validate session-specific data (permissions, context keys in x-session-tags, etc.) for tools/call requests.

User Context and Permissions

Mindset AI includes the following lowercase headers in every request:

Headers Provided by Mindset AI

  • x-user-id: Identifies the end user on whose behalf the action is being taken
  • x-app-uid: The application identifier
  • x-session-tags: Contains the exact JSON array of tags provided when creating the agent session via the AgentSessions API. These are arbitrary strings that you define for your own filtering/segmentation needs (e.g., [“department:sales”, “SALES”, “region:north”, “Q4-2024”, “premium_access”]). If you don’t need filtering, you can provide an empty array or omit tags when creating the session.
  • x-human-uid: Internal Mindset AI identifier for the user (optional, for informational/logging purposes only)
For Embed customers: The x-user-id value will correspond with the user identifier sent by your system to ours to register the user initially. The x-session-tags will contain the exact tags array you provided when creating the agent session, allowing you to apply custom filtering, access control, or business logic based on these tags.

Important Distinction: Current User vs. Target Users

The x-user-id header identifies the CURRENT user - the person making the request. However, your tools may need to operate on OTHER users:
  • Regular tools: Act on behalf of the current user (e.g., “get my settings”, “update my profile”)
    • Use x-user-id header to identify who is making the request
    • Never accept user_id as a parameter for these operations
  • Admin tools: Allow the current user to manage other users (e.g., “create user John”, “add user Alice to group”)
    • Use x-user-id header to identify the ADMIN performing the action
    • Accept user_id/email as parameters to identify the TARGET users being managed
    • Verify the current user has admin permissions before allowing these operations
The recommended approach for handling user context:
  • Validate the user ID against your user database
  • For admin tools, verify the x-user-id has appropriate permissions
  • Use x-session-tags for additional filtering or access control
  • Map to your internal user ID for operations
  • Apply user-specific permissions and access controls
  • Log actions for audit purposes
This separation between system-level authentication (via API key) and user-level authorization (via header) provides flexibility in how you manage user permissions while maintaining security.

Implementation Requirements

Your MCP server should:
  1. Implement JSON-RPC 2.0 and MCP protocols: Your server must be fully compliant with the JSON-RPC 2.0 specification and Model Context Protocol specification. We recommend using FastMCP or an official MCP SDK, which handle all protocol requirements automatically.
  2. Validate API Keys: Check the Bearer token in the Authorization header against your configured API key
  3. Enforce User Permissions: Use the x-user-id header to identify users and apply appropriate access controls
  4. Return Proper Responses: Follow JSON-RPC 2.0 response structure (every response must have either result or error field)
  5. Follow Tool Naming Convention: Tool names must match the pattern ^[a-zA-Z0-9_-]+ (only letters, numbers, underscores, and hyphens). Tool names with dots, spaces, or special characters will cause errors with OpenAI and other LLM providers. For example, use search_for_user instead of search.for.user.
You may implement your server using any language or framework that can:
  • Validate Bearer tokens (API keys)
  • Implement HTTP endpoints according to the JSON-RPC 2.0 and MCP specifications
  • Parse the x-user-id and optionally the x-human-uid headers from requests
  • Return JSON-RPC 2.0 compliant responses

Example Python Implementation with FastMCP

FastMCP is the recommended framework for building MCP servers in Python. It dramatically simplifies development by handling all the protocol complexities while providing production-ready features like authentication, middleware support, and deployment tools. Learn more at gofastmcp.com.

1. Setting Up API Key Configuration

import os
from fastmcp import FastMCP

# Get API key from environment variable
MCP_API_KEY = os.environ.get('MCP_API_KEY')
if not MCP_API_KEY:
    raise ValueError("MCP_API_KEY environment variable is required")

VALID_API_KEYS = {MCP_API_KEY: "production_client"}

2. Minimal Authentication Middleware

from fastmcp.server.middleware import Middleware, MiddlewareContext
from fastmcp.server.dependencies import get_http_headers

class AuthenticationMiddleware(Middleware):
    """Simple API key authentication middleware."""

    async def on_message(self, context: MiddlewareContext, call_next):
        headers = get_http_headers()

        # Extract API key from Bearer token
        auth_header = headers.get('authorization', '').lower()
        api_key = None
        if auth_header.startswith('bearer '):
            api_key = headers.get('authorization', '')[7:]

        # Validate API key
        if api_key in VALID_API_KEYS:
            return await call_next(context)
        else:
            raise ValueError("Authentication failed: Invalid or missing API key")

3. Initialize MCP Server with Middleware

# Initialize MCP server
mcp = FastMCP("your_mcp_server_name")

# Enable authentication middleware
mcp.add_middleware(AuthenticationMiddleware())

4. Implement Your Tools

Tool Docstrings And Logical Variable Names Are Critical

The docstring is what the LLM uses to decide when to call your tool. Keep it concise but descriptive - explain WHAT the tool does and WHEN it should be used. Tool Description Length: Tool descriptions should be at least 5 words. Descriptions shorter than 5 words may not provide enough context for LLMs to understand when and how to use the tool effectively. For example, “Search for users” (3 words) is too short, but “Search for users by name or email” (7 words) provides clear context. Parameter names should be self-explanatory - the LLM uses these names to understand what data to provide. For example, use search_query not q, include_completed not inc_comp. Clear names reduce errors and improve the LLM’s ability to call your tool correctly. Always use type hints for parameters as FastMCP uses these to validate inputs.

Return Object Guidelines

Always include a status field - Use “success”, “error”, or “partial” to help the LLM understand the outcome. Use message for human-readable outcomes - Brief, actionable information the LLM can relay to the user. Avoid including IDs or technical identifiers in messages. Use data for structured results - Machine-readable data the LLM needs to process further. This is where identifiers should live when needed. Include error details in the data field for errors - When returning an error status, include API/system error details in the data field so the agent has feedback and can adapt subsequent attempts. For example, include rate limit information, validation errors, or specific fields that failed. Be cautious with internal identifiers - Avoid exposing internal database keys or system internals that could confuse users or create security risks. However, you may need to include public-facing identifiers (like course IDs, document IDs, or order numbers) in the data field when they’re necessary for subsequent operations or user reference. Keep IDs out of the message field - use natural language there instead.

Important: Tool Return Value vs Wire Format

If using FastMCP/SDK: Your tool functions return plain data structures (objects/dictionaries). The library automatically wraps these in the MCP-required content field before sending on the wire. You don’t need to handle this wrapping yourself. If implementing manually: You must wrap your response in the MCP content structure yourself. The examples below show what your tool function returns when using an MCP library. For manual implementations, see the “Expected Response” section in “Testing Your Implementation” for the complete wire format with the content field.
from typing import List, Optional

# Example 1: Information Retrieval Tool
# This tool retrieves data scoped to the user without modifying anything

@mcp.tool()
async def get_user_enrolled_courses(
    include_completed: Optional[bool] = True,
    category: Optional[str] = None
) -> dict:
    """
    Get courses the current user is enrolled in.
    Returns list of active and optionally completed courses.
    """
    # Extract current user from headers (not a parameter!)
    headers = get_http_headers()
    user_id = headers.get('x-user-id')  # This is the user making the request

    try:
        # Fetch courses for this user (internal implementation)
        courses = fetch_user_courses(user_id, include_completed, category)  # Your implementation

        if not courses:
            return {
                "status": "success",
                "message": "No courses found",
                "data": {
                    "courses": [],
                    "total": 0
                }
            }

        # Build response without exposing internal user IDs or database keys
        course_list = []
        for course in courses:
            course_list.append({
                "course_id": course["public_id"],  # Use public identifiers
                "title": course["title"],
                "progress": course["progress_percentage"],
                "status": course["status"],
                "last_accessed": course["last_accessed"]
            })

        return {
            "status": "success",
            "message": f"Found {len(course_list)} enrolled courses",
            "data": {
                "courses": course_list,
                "total": len(course_list),
                "filters_applied": {
                    "include_completed": include_completed,
                    "category": category
                }
            }
        }

    except ValueError as e:
        # User-friendly error for invalid inputs
        # Include error details in data field so agent can adapt
        return {
            "status": "error",
            "message": f"Invalid filter parameters: {str(e)}",
            "data": {
                "error_type": "validation_error",
                "error_details": str(e)
            }
        }
    except PermissionError:
        # Clear message about access restrictions
        # Include what permission was needed
        return {
            "status": "error",
            "message": "Unable to access course enrollment data",
            "data": {
                "error_type": "permission_denied",
                "required_permission": "read_courses"
            }
        }
    except Exception as e:
        # Generic error - log internally but don't expose details
        print(f"Course fetch error for user {user_id}: {e}")  # Internal logging only
        return {
            "status": "error",
            "message": "Unable to retrieve courses. Please try again.",
            "data": {
                "error_type": "internal_error",
                "retry_after": 60  # Suggest retry delay in seconds
            }
        }

# Example 2: Admin Tool - Managing Other Users
# This tool allows an admin to create and manage OTHER users

@mcp.tool()
async def create_user(
    user_name: str,
    email: str
) -> dict:
    """
    Admin tool: Create a new user account.
    Requires admin permissions.
    """
    headers = get_http_headers()
    admin_id = headers.get('x-user-id')  # The ADMIN performing this action

    # Verify admin permissions
    if not is_user_admin(admin_id):
        return {
            "status": "error",
            "message": "Admin permissions required"
        }

    # Create the new user (email is the TARGET user, not the admin)
    result = create_new_user(user_name, email)

    return {
        "status": "success",
        "message": f"User '{user_name}' created successfully",
        "data": {
            "user_id": result["id"],  # Return ID for subsequent operations
            "name": user_name,
            "email": email
        }
    }

# Example 3: Admin Tool - Modifying Another User's Settings

@mcp.tool()
async def update_user_settings(
    user_id: str,  # TARGET user whose settings to update
    setting_name: str,
    value: str
) -> dict:
    """
    Change settings (theme, language, notifications) for a specific user.
    Use when modifying settings for a user other than the current user.
    """
    headers = get_http_headers()
    admin_id = headers.get('x-user-id')  # The ADMIN performing this action

    # Verify admin permissions
    if not is_user_admin(admin_id):
        return {
            "status": "error",
            "message": "Admin permissions required"
        }

    valid_settings = ["theme", "language", "notifications"]

    if setting_name not in valid_settings:
        return {
            "status": "error",
            "message": f"Unknown setting '{setting_name}'. Valid options: {', '.join(valid_settings)}"
        }

    # Update the TARGET user's settings (not the admin's)
    # Note: The user_id parameter identifies a different user than the one in x-user-id header
    # This user_id would have been identified through prior conversation or tool calls
    update_user_setting(user_id, setting_name, value)  # Your implementation

    return {
        "status": "success",
        "message": f"Settings updated for user {user_id}"
    }

5. Run the Server

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 5000))
    print(f"Starting MCP server on port {port}")

    mcp.run(
        transport="streamable-http",
        host="0.0.0.0",
        port=port,
        path="/mcp",
        stateless_http=True  # Stateless mode for better scalability
    )

Testing Your Implementation

These tests verify JSON-RPC 2.0 and MCP protocol compliance. All servers (library-based or manual) must pass these tests before registration with Mindset.
  • If using FastMCP/SDK: These tests verify your tool logic works correctly (protocol compliance is handled automatically)
  • If implementing manually: These tests are essential - they catch protocol violations that break agent integration

The MCP Handshake Test Sequence

When connecting, the Mindset agent performs this exact sequence:
  1. Initialize - Establish connection and exchange capabilities
  2. Notification - Confirm initialization complete (no response expected)
  3. List Tools - Get available tools
  4. Call Tool - Execute a tool
All four must succeed or integration will fail.

1. Test Tool Discovery

# List available tools
curl -X POST http://localhost:5000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer your-api-key" \
  -H "x-user-id: test_user@example.com" \
  -H "x-session-tags: [\"department:sales\",\"region:north\"]" \
  -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":"1"}'
Expected Response:
data: {"jsonrpc":"2.0","result":{"tools":[{"name":"get_user_enrolled_courses","description":"Get courses the current user is enrolled in. Returns list of active and optionally completed courses.","inputSchema":{"type":"object","properties":{"include_completed":{"type":"boolean","default":true},"category":{"type":"string"}}}}]},"id":"1"}

2. Test Tool Execution

# Call a tool
curl -X POST http://localhost:5000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer your-api-key" \
  -H "x-user-id: test_user@example.com" \
  -H "x-human-uid: human_123456789" \
  -H "x-session-tags: [\"department:sales\",\"region:north\"]" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"get_user_enrolled_courses","arguments":{"include_completed":true}},"id":"2"}'
Expected Response (your actual data will vary): If using FastMCP (SSE format with automatic MCP wrapping):
data: {"jsonrpc":"2.0","result":{"content":[{"type":"text","text":"{\"status\":\"success\",\"message\":\"Found 3 enrolled courses\",\"data\":{\"courses\":[{\"course_id\":\"CS101\",\"title\":\"Introduction to Python\",\"progress\":75,\"status\":\"active\"}],\"total\":3}}"}],"isError":false},"id":"2"}
Note: Your tool function returns {"status":"success","message":"...","data":{...}} and FastMCP automatically wraps it in the MCP-required content field. Manual implementations must handle this wrapping explicitly.

3. Test Security Failures

# Test with no API key - should fail
curl -X POST http://localhost:5000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "X-User-Id: test_user@example.com" \
  -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":"1"}'

# Test with invalid API key - should fail
curl -X POST http://localhost:5000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer invalid-key-123" \
  -H "X-User-Id: test_user@example.com" \
  -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":"2"}'
Expected Response (authentication error):
data: {"jsonrpc":"2.0","error":{"code":-32603,"message":"Authentication failed: Invalid or missing API key"},"id":"2"}

4. Minimal Python Test Script

#!/usr/bin/env python3
import requests
import json

base_url = "http://localhost:5000/mcp"
headers = {
    "Content-Type": "application/json",
    "Accept": "application/json, text/event-stream",
    "Authorization": "Bearer your-api-key",
    "x-user-id": "user@example.com"
    # x-human-uid is optional - included by Mindset but not required for validation
}

# Test tools/list endpoint
response = requests.post(
    base_url,
    headers=headers,
    json={"jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": "1"},
    stream=True
)

# Parse SSE response
for line in response.iter_lines():
    if line and line.decode().startswith("data: "):
        data = json.loads(line.decode()[6:])
        if "result" in data:
            print(f"✅ Found {len(data['result']['tools'])} tools")
            break

Production Considerations

Deployment

Your MCP server requires HTTPS in production. Deploy using your preferred infrastructure - containerization, serverless platforms, or traditional hosting all work well with the MCP protocol.

Security

  • For regular user operations: Don’t accept user identification in tool parameters - rely exclusively on the x-user-id header to identify the current user
  • For admin operations: The x-user-id identifies the admin, while target users must be specified as parameters (after verifying admin permissions)
  • Consider integrating with your existing user database and RBAC systems
  • Rate limiting can help prevent abuse
  • Use your organization’s standard secret management practices
  • Plan for API key rotation policies

Operations

  • Implement logging and monitoring appropriate for your environment
  • Consider deploying behind load balancers with health checks
  • Set up alerts for repeated authentication failures