Checkpoint Docs
Cookbooks

Govern: Self-Host (BYOK)

Deploy KYA-OS servers on your own infrastructure with full control

Goal

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

  • A KYA-OS 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 (--mode mcp-i is the default):

npx @kya-os/create-mcpi-app my-mcp-server --platform 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 KYA-OS server needs an Ed25519 key pair that produces a DID.

If you scaffolded with --platform cloudflare, an identity was already generated during scaffolding: the public DID lives in wrangler.toml (MCP_IDENTITY_AGENT_DID) and the private key in the git-ignored .dev.vars file.

To generate a fresh identity (new DID and keys) in any scaffolded KYA-OS project, run from the project directory:

npx @kya-os/create-mcpi-app regenerate-identity

For Node.js projects this writes .mcpi/identity.json; for Cloudflare projects it updates wrangler.toml and .dev.vars.

In any project, use the generateIdentity helper exported by @kya-os/create-mcpi-app:

npm install -D @kya-os/create-mcpi-app
// scripts/generate-identity.mjs
import { generateIdentity } from '@kya-os/create-mcpi-app/helpers';
import fs from 'node:fs';

const identity = await generateIdentity();
fs.mkdirSync('.mcpi', { recursive: true });
fs.writeFileSync('.mcpi/identity.json', JSON.stringify(identity, null, 2));
console.log(`Generated DID: ${identity.did}`);

Run it:

node scripts/generate-identity.mjs

This writes .mcpi/identity.json:

{
  "did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "kid": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#key-1",
  "privateKey": "base64-encoded-private-key",
  "publicKey": "base64-encoded-public-key",
  "createdAt": "2024-01-15T10:00:00.000Z",
  "type": "development"
}

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 — see /docs/credentials for where to find it)
AGENTSHIELD_API_KEY=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AGENTSHIELD_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

Two env names, one key: AGENTSHIELD_API_KEY is the KYA-OS worker/bouncer naming convention for the same dashboard API key the Checkpoint SDKs read as CHECKPOINT_API_KEY — see Credentials for where to find the key and which name applies where. The bouncer middleware reads no environment variables itself — you pass values into its config explicitly.

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"

# Required — replay-attack prevention (the only mandatory KV binding)
[[kv_namespaces]]
binding = "NONCE_CACHE"
id = "your-kv-namespace-id"

# Optional — archives generated proofs for auditability
[[kv_namespaces]]
binding = "PROOF_ARCHIVE"
id = "your-kv-namespace-id"

# Optional — persistent agent identity storage
[[kv_namespaces]]
binding = "IDENTITY_STORAGE"
id = "your-kv-namespace-id"

# Required for OAuth/delegation flows — delegation storage
[[kv_namespaces]]
binding = "DELEGATION_STORAGE"
id = "your-kv-namespace-id"

Only NONCE_CACHE is required. PROOF_ARCHIVE, IDENTITY_STORAGE, and the tool-protection cache (TOOL_PROTECTION_KV) are optional; add DELEGATION_STORAGE when you use OAuth/delegation flows. Projects scaffolded with create-mcpi-app instead declare a single auto-provisioned KV namespace that backs all of these logical stores.

Install dotenv:

npm install dotenv

Load in your entry point:

import 'dotenv/config';

Create the Server

These are the files a scaffolded project ships with (trimmed to the essentials). The entry point delegates to createMCPIApp, which wires up the MCP transport, well-known endpoints, and consent routes:

// src/index.ts
import { createMCPIApp } from '@kya-os/mcp-i-cloudflare';
import { MyMcpServerMCP } from './agent';
import { getRuntimeConfig } from './mcpi-runtime-config';

export default createMCPIApp({
  AgentClass: MyMcpServerMCP,
  getRuntimeConfig,
  envPrefix: 'MY_MCP_SERVER', // Maps the prefixed KV binding (MY_MCP_SERVER_MCPI_KV) to standard names
});

// Export the Durable Object classes for Cloudflare Workers bindings
export { MyMcpServerMCP };
export { AuditProducer } from '@kya-os/mcp-i-cloudflare';
// src/mcpi-runtime-config.ts
import { defineConfig, type CloudflareRuntimeConfig } from '@kya-os/mcp-i-cloudflare';
import type { CloudflareEnv } from '@kya-os/mcp-i-cloudflare';
import { greetTool } from './tools/greet';

export function getRuntimeConfig(env: CloudflareEnv): CloudflareRuntimeConfig {
  const environment = (env.MCPI_ENV || env.ENVIRONMENT || 'development') as
    | 'development'
    | 'production';

  return defineConfig({
    environment,
    vars: {
      ENVIRONMENT: environment,
      AGENTSHIELD_API_KEY: env.AGENTSHIELD_API_KEY,
      AGENTSHIELD_API_URL: env.AGENTSHIELD_API_URL,
      AGENTSHIELD_PROJECT_ID: env.AGENTSHIELD_PROJECT_ID,
    },
  });
}

export function getTools() {
  return [greetTool];
}

src/agent.ts (also scaffolded) extends MCPICloudflareAgent and registers each tool from getTools() with automatic proof generation. Identity is not part of this config — the worker reads it from its bindings: the MCP_IDENTITY_PRIVATE_KEY secret plus the MCP_IDENTITY_AGENT_DID / MCP_IDENTITY_PUBLIC_KEY vars in wrangler.toml.

// src/index.ts
import express from 'express';
import { createBouncerMiddleware } from '@kya-os/bouncer-middleware';
import { createMCPIRuntime } from '@kya-os/mcp-i';

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

// Initialize the KYA-OS runtime. It loads its own identity:
// MCP_IDENTITY_PRIVATE_KEY / MCP_IDENTITY_PUBLIC_KEY / MCP_IDENTITY_AGENT_DID
// env vars when set, otherwise .mcpi/identity.json.
const runtime = createMCPIRuntime({
  identity: {
    environment: process.env.ENVIRONMENT === 'production' ? 'production' : 'development',
    devIdentityPath: '.mcpi', // directory containing identity.json
  },
});
await runtime.initialize();
const identity = await runtime.getIdentity();

// Well-known endpoints
const wellKnown = runtime.createWellKnownHandler({
  serviceName: 'My MCP Server',
  serviceEndpoint: process.env.BASE_URL,
});

app.get('/.well-known/did.json', async (req, res) => {
  const response = await wellKnown('/.well-known/did.json');
  if (response && 'status' in response) {
    res.status(response.status).set(response.headers).send(response.body);
  } else {
    res.status(404).end();
  }
});

app.get('/.well-known/agent.json', async (req, res) => {
  const response = await wellKnown('/.well-known/agent.json');
  if (response && 'status' in response) {
    res.status(response.status).set(response.headers).send(response.body);
  } else {
    res.status(404).end();
  }
});

// Protected tool endpoints
app.post(
  '/tools/:toolName',
  createBouncerMiddleware({
    apiKey: process.env.AGENTSHIELD_API_KEY!,
    projectId: process.env.AGENTSHIELD_PROJECT_ID!,
  }),
  async (req, res) => {
    const { toolName } = req.params;
    const { 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 the tool through the runtime — it runs your handler and
    // generates a signed proof automatically. Proofs stay out-of-band
    // (runtime.getLastProof()), never in the response body.
    const result = await runtime.processToolCall(toolName, req.body, async (args) =>
      executeToolInternal(toolName, args)
    );

    res.json(result);
  }
);

// 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(`KYA-OS server running on port ${PORT}`);
  console.log(`Agent DID: ${identity.did}`);
});

Register with KnowThat.ai (Optional)

Delegation verification does not require a registration call — the bouncer middleware authenticates to Checkpoint with your API key and project ID. Registering your agent with the KnowThat.ai registry is optional: it creates a public agent profile and enables reputation tracking.

Ways to register:

  • At scaffold time — with npx @kya-os/create-mcpi-app, -y/CI runs register by default; pass --no-register to skip the external registration. Interactive runs prompt for it (default yes)

  • From a project that has an identity — run the mcpi CLI's register command. It reads .mcpi/identity.json (or identity env vars), registers the agent with KnowThat.ai, and saves a claim receipt. Requires a KYA_VOUCHED_API_KEY environment variable.

    npx @kya-os/cli register
  • Managed deploys — the dashboard deployment pipeline registers the identity for you (its Register Identity step) and provisions the KTA_REGISTRATION secret

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=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
  -e AGENTSHIELD_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 (the Express example above serves /__health;
# scaffolded Cloudflare projects serve /health instead)
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 as ToolDefinition objects — name, description, a JSON Schema inputSchema, and a handler. Which tools require a delegation (and with which scopes) is not declared in code: you configure it in the dashboard (next section), and the runtime enforces it before the handler runs.

// src/tools/index.ts
import type { ToolDefinition } from '@kya-os/mcp-i-cloudflare';

export const tools: ToolDefinition[] = [
  {
    name: 'read_file',
    description: 'Reads content from a file',
    inputSchema: {
      type: 'object',
      properties: {
        path: { type: 'string', description: 'File path to read' },
      },
      required: ['path'],
    },
    handler: async (args: { path: string }) => {
      // Your implementation
      return { content: [{ type: 'text', text: 'file content' }] };
    },
  },
  {
    name: 'write_file',
    description: 'Writes content to a file',
    inputSchema: {
      type: 'object',
      properties: {
        path: { type: 'string' },
        content: { type: 'string' },
      },
      required: ['path', 'content'],
    },
    handler: async (args: { path: string; content: string }) => {
      // Your implementation
      return { content: [{ type: 'text', text: 'ok' }] };
    },
  },
];

Configure in Dashboard

Register your tools in the Checkpoint dashboard:

  1. Go to Policy → Auth
  2. Create a protection with the scopes it grants, and assign it as the owner of each tool in the coverage table
  3. Customize the consent screen text and branding on the same page

Tool discovery and manual add/remove remain on the legacy surface at /dashboard/{orgId}/{projectId}/control-access/tools (legacy surface, moving to Access).

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 project ID — The middleware authenticates with API key + project ID
  • Check proof format — Proofs travel as a compact JWS at _meta.proof.jws in the request body

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 a KYA-OS 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 KnowThat.ai (optional)

Next Steps

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