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
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:
npx @kya-os/create-mcpi-app my-mcp-server --template 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 MCP-I server needs an Ed25519 key pair that produces a DID.
npx @kya-os/mcp-i generate-identityThis 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.tsImportant: 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)
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-xxxxxAdd 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"
[[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 dotenvLoad 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.tsDeploy
# 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=as_live_xxx \
-e CHECKPOINT_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
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 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:
- Go to Control Access → Tools
- Add each tool with its scopes
- Configure consent page text
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 DID registration — Run registration script
- Check proof format — Ensure proofs are properly formatted
Cloudflare KV Issues
- KV not bound — Check
wrangler.tomlbindings - 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
| Goal | Resource |
|---|---|
| Add OAuth authentication | OAuth Integration |
| Configure auth methods | Auth Methods |
| Understand delegations | Delegations |
| Managed deployment | Dashboard Deploy |