> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mindset.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools MCP Server Integration Guide

> Guide on implementing and registering your Tools MCP server with the Mindset AI platform. 

## 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):**

* [**FastMCP**](https://gofastmcp.com) v2.10+ - Pythonic, production-ready, handles all protocol complexity
* [Official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)

**TypeScript/JavaScript:**

* [Official MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)

### 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**](https://www.jsonrpc.org/specification)
   * Required response fields (`result` XOR `error`)
   * Notification handling (requests without `id`)
   * Error codes and structures
   * ID type preservation
2. [**Model Context Protocol Specification**](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
   * 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](https://docs.mindset.ai/sdk-api/api/agent-sessions/create-a-new-agent-session#response-tags). 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](https://www.jsonrpc.org/specification) and [Model Context Protocol specification](https://modelcontextprotocol.io/specification/2025-06-18/server/tools). We recommend using [FastMCP](https://gofastmcp.com) 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](https://gofastmcp.com).

### 1. Setting Up API Key Configuration

```python theme={null}
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

```python theme={null}
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

```python theme={null}
# 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.

```python theme={null}
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

```python theme={null}
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

```bash theme={null}
# 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

```bash theme={null}
# 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

```bash theme={null}
# 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

```python theme={null}
#!/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
