Checkpoint Docs
Cookbooks

Govern: Self-Host (BYOK)

Deploy MCP-I servers on your own infrastructure with full control

Goal

Deploy an MCP-I server on your own infrastructure using the @kya-os/mcp-i packages. By the end of this cookbook, you'll have:

  • An MCP-I server running on Cloudflare Workers or Node.js
  • Agent identity managed locally
  • Full control over configuration and deployment
  • Integration with Checkpoint for delegation verification

Best for: Teams who need full control over infrastructure, custom hosting requirements, or integration with existing deployments.

Prerequisites

  • A Checkpoint account with an API key
  • Node.js 18+ (for local development)
  • Either:
    • Cloudflare account (for Workers deployment)
    • Your own Node.js hosting (Express, Docker, etc.)

Time Estimate

30 minutes


Choose Your Deployment Target

Edge deployment with global distribution, automatic scaling, and KV storage.

Pros:

  • Zero-config scaling
  • Global edge network
  • Built-in KV storage
  • Free tier available

Package: @kya-os/mcp-i-cloudflare

Node.js / Express

Self-hosted Node.js server with full control over the runtime.

Pros:

  • Full runtime control
  • Any hosting provider
  • Existing infrastructure integration

Package: @kya-os/bouncer-middleware

Docker Container

Containerized deployment for Kubernetes or container orchestration.

Pros:

  • Portable deployment
  • Kubernetes-native
  • Consistent environments

Package: @kya-os/mcp-i


Steps

Scaffold the Project

Use the CLI to scaffold a Cloudflare Workers project:

npx @kya-os/create-mcpi-app my-mcp-server --template cloudflare
cd my-mcp-server
npm install

Or manually:

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @kya-os/mcp-i-cloudflare wrangler
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @kya-os/bouncer-middleware @kya-os/mcp-i express
npm install -D typescript @types/express @types/node
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @kya-os/mcp-i express
npm install -D typescript @types/express @types/node

Create a Dockerfile:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]

Generate Agent Identity

Every MCP-I server needs an Ed25519 key pair that produces a DID.

npx @kya-os/mcp-i generate-identity

This creates .mcpi/identity.json:

{
  "did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "privateKey": "base64-encoded-private-key",
  "publicKey": "base64-encoded-public-key",
  "createdAt": "2024-01-15T10:00:00.000Z"
}
// scripts/generate-identity.ts
import { generateIdentity } from '@kya-os/mcp-i';
import fs from 'fs';
import path from 'path';

async function main() {
  const identity = await generateIdentity();

  const identityDir = path.join(process.cwd(), '.mcpi');
  fs.mkdirSync(identityDir, { recursive: true });

  fs.writeFileSync(path.join(identityDir, 'identity.json'), JSON.stringify(identity, null, 2));

  console.log(`Generated DID: ${identity.did}`);
}

main();

Run it:

npx ts-node scripts/generate-identity.ts

Important: Add .mcpi/ to your .gitignore. Never commit private keys to source control.

echo ".mcpi/" >> .gitignore

Configure Environment Variables

Create a .env file (and .env.example for documentation):

# .env
# Checkpoint API key (from dashboard)
AGENTSHIELD_API_KEY=as_live_xxxxxxxxxxxx
CHECKPOINT_PROJECT_ID=proj_abc123def456

# Environment
ENVIRONMENT=production
NODE_ENV=production

# Server URL (where your server will be accessible)
BASE_URL=https://mcp.yourdomain.com

# Identity (for Cloudflare, set as KV or secrets)
MCP_IDENTITY_PRIVATE_KEY=base64-private-key-here

# Optional: Anthropic/OpenAI for LLM features
ANTHROPIC_API_KEY=sk-ant-xxxxx

Add secrets to Wrangler:

wrangler secret put AGENTSHIELD_API_KEY
wrangler secret put MCP_IDENTITY_PRIVATE_KEY
wrangler secret put ANTHROPIC_API_KEY  # Optional

Configure wrangler.toml:

name = "my-mcp-server"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[vars]
ENVIRONMENT = "production"

[[kv_namespaces]]
binding = "NONCE_CACHE"
id = "your-kv-namespace-id"

[[kv_namespaces]]
binding = "PROOF_ARCHIVE"
id = "your-kv-namespace-id"

[[kv_namespaces]]
binding = "IDENTITY_STORAGE"
id = "your-kv-namespace-id"

Install dotenv:

npm install dotenv

Load in your entry point:

import 'dotenv/config';

Create the Server

// src/index.ts
import { MCPICloudflareServer, defineConfig } from '@kya-os/mcp-i-cloudflare';

interface CloudflareEnv {
  ENVIRONMENT: string;
  AGENTSHIELD_API_KEY: string;
  MCP_IDENTITY_PRIVATE_KEY: string;
  NONCE_CACHE: KVNamespace;
  PROOF_ARCHIVE: KVNamespace;
  IDENTITY_STORAGE: KVNamespace;
}

export function getRuntimeConfig(env: CloudflareEnv) {
  return defineConfig({
    vars: {
      ENVIRONMENT: env.ENVIRONMENT,
      AGENTSHIELD_API_KEY: env.AGENTSHIELD_API_KEY,
    },
    identity: {
      privateKey: env.MCP_IDENTITY_PRIVATE_KEY,
    },
    storage: {
      nonceCache: env.NONCE_CACHE,
      proofArchive: env.PROOF_ARCHIVE,
      identityStorage: env.IDENTITY_STORAGE,
    },
    wellKnown: {
      agentName: 'My MCP Server',
      agentDescription: 'An MCP-I enabled server',
    },
  });
}

export default {
  async fetch(request: Request, env: CloudflareEnv, ctx: ExecutionContext): Promise<Response> {
    const server = new MCPICloudflareServer({
      env,
      config: getRuntimeConfig(env),
    });

    return server.handleRequest(request, ctx);
  },
};
// src/index.ts
import express from 'express';
import { createBouncerMiddleware } from '@kya-os/bouncer-middleware';
import { createMCPIRuntime } from '@kya-os/mcp-i';
import fs from 'fs';
import path from 'path';

const app = express();
app.use(express.json());

// Load identity
const identityPath = path.join(process.cwd(), '.mcpi', 'identity.json');
const identity = JSON.parse(fs.readFileSync(identityPath, 'utf-8'));

// Initialize MCP-I runtime
const runtime = await createMCPIRuntime({
  environment: process.env.ENVIRONMENT || 'development',
  identity,
  wellKnown: {
    baseUrl: process.env.BASE_URL!,
    agentName: 'My MCP Server',
    agentDescription: 'An MCP-I enabled server',
  },
});

// Well-known endpoints
app.get('/.well-known/did.json', async (req, res) => {
  const didDocument = await runtime.getDIDDocument();
  res.json(didDocument);
});

app.get('/.well-known/agent.json', async (req, res) => {
  const agentMetadata = await runtime.getAgentMetadata();
  res.json(agentMetadata);
});

// Protected tool endpoints
app.post(
  '/tools/:toolName',
  createBouncerMiddleware({
    apiKey: process.env.AGENTSHIELD_API_KEY!,
    projectId: process.env.CHECKPOINT_PROJECT_ID!,
  }),
  async (req, res) => {
    const { toolName } = req.params;
    const { agentDid, scopes } = req.bouncer;

    // Verify scope for this tool
    const requiredScope = `${toolName}:execute`;
    if (!scopes.includes(requiredScope)) {
      return res.status(403).json({
        error: 'Insufficient scope',
        required: requiredScope,
        granted: scopes,
      });
    }

    // Execute tool
    const result = await executeToolInternal(toolName, req.body, {
      agentDid,
      runtime,
    });

    // Generate proof for the response
    const proof = await runtime.generateProof(result, {
      agentDid,
      toolName,
    });

    res.json({
      result,
      proof,
    });
  }
);

// Health check
app.get('/__health', (req, res) => {
  res.json({ status: 'ok', did: identity.did });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`MCP-I server running on port ${PORT}`);
  console.log(`Agent DID: ${identity.did}`);
});

Register with Checkpoint

Your server needs to register with Checkpoint for delegation verification:

// scripts/register-server.ts
import { registerServer } from '@kya-os/mcp-i';

async function main() {
  const identity = require('../.mcpi/identity.json');

  const result = await registerServer({
    apiKey: process.env.AGENTSHIELD_API_KEY!,
    projectId: process.env.CHECKPOINT_PROJECT_ID!,
    did: identity.did,
    publicKey: identity.publicKey,
    serverUrl: process.env.BASE_URL!,
    metadata: {
      name: 'My MCP Server',
      description: 'An MCP-I enabled server',
    },
  });

  console.log('Registered:', result);
}

main();

Run once after deployment:

npx ts-node scripts/register-server.ts

Deploy

# Development
wrangler dev

# Production
wrangler deploy

Build and run:

npm run build
node dist/index.js

For production, use PM2 or similar:

npm install -g pm2
pm2 start dist/index.js --name mcp-server
# Build
npm run build
docker build -t my-mcp-server .

# Run
docker run -p 3000:3000 \
  -e AGENTSHIELD_API_KEY=as_live_xxx \
  -e CHECKPOINT_PROJECT_ID=proj_xxx \
  -e BASE_URL=https://mcp.yourdomain.com \
  -v $(pwd)/.mcpi:/app/.mcpi:ro \
  my-mcp-server

Test the Server

# Check DID document
curl https://your-server/.well-known/did.json

# Check agent metadata
curl https://your-server/.well-known/agent.json

# Health check
curl https://your-server/__health

# Test protected endpoint (should fail without delegation)
curl -X POST https://your-server/tools/read_file \
  -H "Content-Type: application/json" \
  -d '{"path": "/test"}'
# Expected: 401 Unauthorized

Adding Tools

Define tools that require delegations:

// src/tools/index.ts
import { Tool, ToolResult } from '@kya-os/mcp-i';

export const tools: Tool[] = [
  {
    name: 'read_file',
    description: 'Reads content from a file',
    scopes: ['files:read'],
    parameters: {
      type: 'object',
      properties: {
        path: { type: 'string', description: 'File path to read' },
      },
      required: ['path'],
    },
    handler: async (params, context): Promise<ToolResult> => {
      // Your implementation
      return { success: true, data: 'file content' };
    },
  },
  {
    name: 'write_file',
    description: 'Writes content to a file',
    scopes: ['files:write'],
    sensitive: true,
    parameters: {
      type: 'object',
      properties: {
        path: { type: 'string' },
        content: { type: 'string' },
      },
      required: ['path', 'content'],
    },
    handler: async (params, context): Promise<ToolResult> => {
      // Your implementation
      return { success: true };
    },
  },
];

Configure in Dashboard

Register your tools in the Checkpoint dashboard:

  1. Go to Control Access → Tools
  2. Add each tool with its scopes
  3. Configure consent page text

This enables the dashboard to show proper consent screens to users.


Troubleshooting

Identity Not Loading

SymptomCauseFix
"Cannot read identity"File not foundCheck .mcpi/identity.json exists
Invalid DID formatCorrupted keyRegenerate identity
Key mismatchWrong env variableVerify MCP_IDENTITY_PRIVATE_KEY

Delegation Verification Fails

  • Check API key — Must be valid for your project
  • Check DID registration — Run registration script
  • Check proof format — Ensure proofs are properly formatted

Cloudflare KV Issues

  • KV not bound — Check wrangler.toml bindings
  • KV namespace missing — Create with wrangler kv:namespace create

What You Learned

  • How to scaffold an MCP-I server project
  • How to generate and manage agent identity
  • How to deploy to Cloudflare Workers or Node.js
  • How to add tools with scope requirements
  • How to register with Checkpoint

Next Steps

GoalResource
Add OAuth authenticationOAuth Integration
Configure auth methodsAuth Methods
Understand delegationsDelegations
Managed deploymentDashboard Deploy