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
Cloudflare Workers (Recommended)
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 installOr manually:
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @kya-os/mcp-i-cloudflare wranglermkdir 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/nodemkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @kya-os/mcp-i express
npm install -D typescript @types/express @types/nodeCreate 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-identityFor 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.mjsThis 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/" >> .gitignoreConfigure 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-xxxxxTwo 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 # OptionalConfigure 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 dotenvLoad 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-registerto skip the external registration. Interactive runs prompt for it (default yes) -
From a project that has an identity — run the
mcpiCLI's register command. It reads.mcpi/identity.json(or identity env vars), registers the agent with KnowThat.ai, and saves a claim receipt. Requires aKYA_VOUCHED_API_KEYenvironment 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_REGISTRATIONsecret
Deploy
# Development
wrangler dev
# Production
wrangler deployBuild and run:
npm run build
node dist/index.jsFor 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-serverTest 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 UnauthorizedAdding 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:
- Go to Policy → Auth
- Create a protection with the scopes it grants, and assign it as the owner of each tool in the coverage table
- 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
| Symptom | Cause | Fix |
|---|---|---|
| "Cannot read identity" | File not found | Check .mcpi/identity.json exists |
| Invalid DID format | Corrupted key | Regenerate identity |
| Key mismatch | Wrong env variable | Verify 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.jwsin the request body
Cloudflare KV Issues
- KV not bound — Check
wrangler.tomlbindings - 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
| Goal | Resource |
|---|---|
| Add OAuth authentication | OAuth Integration |
| Configure auth methods | Auth Methods |
| Understand delegations | Delegations |
| Managed deployment | Dashboard Deploy |