View on GitHub →
← Back to Home

Secure Your Python MCP Server

An interactive guide to implementing SchemaPin with Flask.

The Challenge: Insecure Tool Schemas

The Model Context Protocol (MCP) relies on JSON schemas to define tool capabilities. If a schema is tampered with, an attacker can hijack your AI agent's tools. This is a critical supply chain vulnerability.

SchemaPin solves this by using cryptographic signatures to guarantee that tool schemas are authentic and unmodified.

Advanced Demo: Open In Colab

Step 1: Install CLI Tools

The command-line tools for signing are distributed via Pip and are required for the next steps.

pip install schemapin

Step 2: Create & Sign Your Schema

Create a file named get_issue_schema.json with the tool's definition.

{
  "name": "get_issue",
  "description": "Gets the contents of an issue within a repository",
  "input_schema": {
    "type": "object",
    "properties": {
      "owner": { "type": "string", "description": "Repository owner" },
      "repo": { "type": "string", "description": "Repository name" },
      "issue_number": { "type": "number", "description": "Issue number" }
    },
    "required": ["owner", "repo", "issue_number"]
  }
}

Generate your cryptographic key pair.

schemapin-keygen

Now, use your private key to sign the schema, producing a secure, signed version.

schemapin-sign --schema get_issue_schema.json \
  --key private.key \
  --output get_issue_signed.json

Step 3: Host Public Key for Discovery

For services to verify your signatures, they must be able to find your public key. The standard approach is to host it at a .well-known/schemapin.json URL on your domain. Use the automated well-known server to host your public key.

Run the well-known server, which automatically creates the proper directory structure and serves your public key. Keep this server running in a separate terminal.

python -m schemapin.well_known_server --public-key-file public.key --port 8000

Step 4: Build the Secure Python Server

Setup

pip install schemapin Flask
touch server.py

Add to server.py

import json
from flask import Flask, jsonify
from schemapin import core, discovery

app = Flask(__name__)
TOOL_SCHEMAS = {}

def load_and_verify_schema(path, host, key_id):
    print(f"Verifying {path}...")
    try:
        with open(path, 'rb') as f:
            signed_schema_bytes = f.read()
        print(f"Discovering key from {host}...")
        public_key = discovery.discover_public_key(host, key_id)
        verified_bytes = core.verify(signed_schema_bytes, public_key)
        schema = json.loads(verified_bytes)
        print(f"SUCCESS: Verified '{schema['name']}'.")
        return schema
    except Exception as e:
        print(f"ERROR: Verification failed! {e}")
        return None

@app.route('/tools', methods=['GET'])
def get_tools():
    return jsonify(list(TOOL_SCHEMAS.keys()))

if __name__ == '__main__':
    verified_tool = load_and_verify_schema(
        'get_issue_signed.json',
        'http://localhost:8000',
        'default'
    )
    if verified_tool:
        TOOL_SCHEMAS[verified_tool['name']] = verified_tool
        app.run(port=5001)
    else:
        print("\\nShutting down due to verification failure.")

Run and Test Security

Run python server.py. Then, stop the server, tamper with the signature in get_issue_signed.json, and run it again to see the failure.

Valid Schema Tampered
Verifying get_issue_signed.json...
Discovering key from http://localhost:8000...
SUCCESS: Verified 'get_issue'.
* Serving Flask app 'server'
* Running on http://127.0.0.1:5001