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.
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 Go Server
Setup
go mod init secure-server
go get github.com/thirdkeyai/schemapin/go@latest
touch main.go
Add to main.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"github.com/thirdkeyai/schemapin/go/core"
"github.com/thirdkeyai/schemapin/go/crypto"
"github.com/thirdkeyai/schemapin/go/discovery"
)
var TOOL_SCHEMAS = make(map[string]interface{})
func loadAndVerifySchema(path, host, keyId string) (map[string]interface{}, error) {
log.Printf("Verifying %s...", path)
signedSchemaBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read signed schema: %w", err)
}
log.Printf("Discovering key from %s...", host)
wellKnownURL := fmt.Sprintf("%s/.well-known/schemapin.json", host)
resp, err := http.Get(wellKnownURL)
if err != nil {
return nil, fmt.Errorf("failed to fetch .well-known: %w", err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
wellKnownConfig, err := discovery.ParseWellKnownConfig(body)
if err != nil {
return nil, fmt.Errorf("failed to parse .well-known: %w", err)
}
keyStr, err := discovery.FindKeyInConfig(wellKnownConfig, keyId)
if err != nil {
return nil, fmt.Errorf("keyId '%s' not found: %w", keyId, err)
}
publicKey, err := crypto.ParsePublicKey(keyStr)
if err != nil {
return nil, fmt.Errorf("failed to parse public key: %w", err)
}
verifiedBytes, err := core.Verify(signedSchemaBytes, publicKey)
if err != nil {
return nil, fmt.Errorf("VERIFICATION FAILED: %w", err)
}
var schema map[string]interface{}
json.Unmarshal(verifiedBytes, &schema)
log.Printf("SUCCESS: Verified '%s'.", schema["name"])
return schema, nil
}
func main() {
verifiedTool, err := loadAndVerifySchema("get_issue_signed.json", "http://localhost:8000", "default")
if err != nil {
log.Fatalf("Could not start server: %v", err)
}
TOOL_SCHEMAS[verifiedTool["name"].(string)] = verifiedTool
http.HandleFunc("/tools", func(w http.ResponseWriter, r *http.Request) {
var names []string
for name := range TOOL_SCHEMAS {
names = append(names, name)
}
json.NewEncoder(w).Encode(names)
})
log.Println("Go MCP server running on port 5003")
http.ListenAndServe(":5003", nil)
}
Run and Test Security
Run the server. Then, stop it, tamper with the signature in get_issue_signed.json
, and run it again to see the fatal error.
2025/06/08 14:38:00 Verifying get_issue_signed.json...
2025/06/08 14:38:00 Discovering key from http://localhost:8000...
2025/06/08 14:38:00 SUCCESS: Verified 'get_issue'.
2025/06/08 14:38:00 Go MCP server running on port 5003