Model Context Protocol (MCP) Integration
JACS provides native integration with the Model Context Protocol (MCP), enabling secure agent communication within AI systems. JACS uses a transport proxy pattern that wraps any MCP transport with cryptographic signing and verification.
What is MCP?
Model Context Protocol is a standard for AI models to securely access external tools, data, and services. JACS enhances MCP by adding:
- Cryptographic verification of all messages
- Agent identity for all operations
- Transparent encryption of MCP JSON-RPC traffic
- Audit trails of all MCP interactions
How JACS MCP Works
JACS provides a transport proxy that sits between your MCP server/client and the underlying transport (STDIO, SSE, WebSocket). The proxy:
- Outgoing messages: Signs JSON-RPC messages with the JACS agent's key using
signRequest() - Incoming messages: Verifies signatures using
verifyResponse()and extracts the payload - Fallback: If verification fails, passes messages through as plain JSON (graceful degradation)
Quick Start
Basic MCP Server with JACS
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import { z } from 'zod';
const JACS_CONFIG_PATH = "./jacs.config.json";
async function main() {
// Create the base STDIO transport
const baseTransport = new StdioServerTransport();
// Wrap with JACS encryption
const secureTransport = createJACSTransportProxy(
baseTransport,
JACS_CONFIG_PATH,
"server"
);
// Create MCP server
const server = new McpServer({
name: "my-jacs-server",
version: "1.0.0"
});
// Register tools
server.tool("add", {
a: z.number().describe("First number"),
b: z.number().describe("Second number")
}, async ({ a, b }) => {
return { content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }] };
});
// Connect with JACS encryption
await server.connect(secureTransport);
console.error("JACS MCP Server running with encryption enabled");
}
main();
MCP Client with JACS
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
const JACS_CONFIG_PATH = "./jacs.config.json";
async function main() {
// Create base transport to connect to MCP server
const baseTransport = new StdioClientTransport({
command: 'node',
args: ['my-jacs-server.js']
});
// Wrap with JACS encryption
const secureTransport = createJACSTransportProxy(
baseTransport,
JACS_CONFIG_PATH,
"client"
);
// Create MCP client
const client = new Client({
name: "my-jacs-client",
version: "1.0.0"
}, {
capabilities: {
tools: {}
}
});
// Connect with JACS encryption
await client.connect(secureTransport);
// List available tools
const tools = await client.listTools();
console.log('Available tools:', tools.tools.map(t => t.name));
// Call a tool (message will be JACS-signed)
const result = await client.callTool({
name: "add",
arguments: { a: 5, b: 3 }
});
console.log('Result:', result.content);
}
main();
API Reference
JACSTransportProxy
The main class that wraps MCP transports with JACS encryption.
import { JACSTransportProxy } from 'jacsnpm/mcp';
const proxy = new JACSTransportProxy(
transport, // Any MCP transport (Stdio, SSE, WebSocket)
role, // "server" or "client"
jacsConfigPath // Path to jacs.config.json
);
createJACSTransportProxy
Factory function for creating a transport proxy.
import { createJACSTransportProxy } from 'jacsnpm/mcp';
const secureTransport = createJACSTransportProxy(
baseTransport, // The underlying MCP transport
configPath, // Path to jacs.config.json
role // "server" or "client"
);
createJACSTransportProxyAsync
Async factory that waits for JACS to be fully loaded before returning.
import { createJACSTransportProxyAsync } from 'jacsnpm/mcp';
const secureTransport = await createJACSTransportProxyAsync(
baseTransport,
configPath,
role
);
Transport Options
STDIO Transport
Best for CLI tools and subprocess communication:
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
const baseTransport = new StdioServerTransport();
const secureTransport = createJACSTransportProxy(
baseTransport,
"./jacs.config.json",
"server"
);
Important: When using STDIO transport, all debug logging goes to stderr to keep stdout clean for JSON-RPC messages.
SSE Transport (HTTP)
For web-based MCP servers:
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import express from 'express';
const app = express();
app.get('/sse', (req, res) => {
const baseTransport = new SSEServerTransport('/messages', res);
const secureTransport = createJACSTransportProxy(
baseTransport,
"./jacs.config.json",
"server"
);
// Connect your MCP server to secureTransport
server.connect(secureTransport);
});
// Handle POST messages with JACS decryption
app.post('/messages', express.text(), async (req, res) => {
await secureTransport.handlePostMessage(req, res, req.body);
});
app.listen(3000);
Configuration
JACS Config File
Create a jacs.config.json for your MCP server/client:
{
"$schema": "https://hai.ai/schemas/jacs.config.schema.json",
"jacs_data_directory": "./jacs_data",
"jacs_key_directory": "./jacs_keys",
"jacs_default_storage": "fs",
"jacs_agent_key_algorithm": "ring-Ed25519",
"jacs_agent_id_and_version": "agent-uuid:version-uuid"
}
Environment Variables
Enable debug logging (not recommended for STDIO):
export JACS_MCP_DEBUG=true
How Messages Are Signed
Outgoing Messages
When the MCP SDK sends a message, the proxy intercepts it and:
- Serializes the JSON-RPC message
- Calls
jacs.signRequest(message)to create a JACS artifact - Sends the signed artifact to the transport
// Original MCP message
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "add", "arguments": { "a": 5, "b": 3 } }
}
// Becomes a JACS-signed artifact with jacsId, jacsSignature, etc.
Incoming Messages
When the transport receives a message, the proxy:
- Attempts to verify it as a JACS artifact using
jacs.verifyResponse() - If valid, extracts the original JSON-RPC payload
- If not valid JACS, parses as plain JSON (fallback mode)
- Passes the clean message to the MCP SDK
Complete Example
Server (mcp.server.js)
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import { z } from 'zod';
async function main() {
console.error("JACS MCP Server starting...");
// Create transport with JACS encryption
const baseTransport = new StdioServerTransport();
const secureTransport = createJACSTransportProxy(
baseTransport,
"./jacs.server.config.json",
"server"
);
// Create MCP server
const server = new McpServer({
name: "jacs-demo-server",
version: "1.0.0"
});
// Register tools
server.tool("echo", {
message: z.string().describe("Message to echo")
}, async ({ message }) => {
console.error(`Echo called with: ${message}`);
return { content: [{ type: "text", text: `Echo: ${message}` }] };
});
server.tool("add", {
a: z.number().describe("First number"),
b: z.number().describe("Second number")
}, async ({ a, b }) => {
console.error(`Add called with: ${a}, ${b}`);
return { content: [{ type: "text", text: `Result: ${a + b}` }] };
});
// Register resources
server.resource(
"server-info",
"info://server",
async (uri) => ({
contents: [{
uri: uri.href,
text: "JACS-secured MCP Server",
mimeType: "text/plain"
}]
})
);
// Connect
await server.connect(secureTransport);
console.error("Server running with JACS encryption");
}
main().catch(err => {
console.error("Fatal error:", err);
process.exit(1);
});
Client (mcp.client.js)
#!/usr/bin/env node
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
async function main() {
console.log("JACS MCP Client starting...");
// Connect to the server
const baseTransport = new StdioClientTransport({
command: 'node',
args: ['mcp.server.js']
});
const secureTransport = createJACSTransportProxy(
baseTransport,
"./jacs.client.config.json",
"client"
);
const client = new Client({
name: "jacs-demo-client",
version: "1.0.0"
}, {
capabilities: { tools: {} }
});
await client.connect(secureTransport);
console.log("Connected to JACS MCP Server");
// List tools
const tools = await client.listTools();
console.log("Available tools:", tools.tools.map(t => t.name));
// Call echo tool
const echoResult = await client.callTool({
name: "echo",
arguments: { message: "Hello, JACS!" }
});
console.log("Echo result:", echoResult.content[0].text);
// Call add tool
const addResult = await client.callTool({
name: "add",
arguments: { a: 10, b: 20 }
});
console.log("Add result:", addResult.content[0].text);
await client.close();
}
main().catch(console.error);
Security Considerations
Message Verification
All JACS-signed messages include:
jacsId- Unique document identifierjacsVersion- Version trackingjacsSignature- Cryptographic signaturejacsHash- Content hash for integrity
Passthrough Mode
If JACS cannot verify an incoming message, it falls back to plain JSON parsing. This allows:
- Gradual migration to JACS-secured communication
- Interoperability with non-JACS MCP clients/servers
To require JACS verification (no fallback), implement custom validation in your tools.
Key Management
Each MCP server and client needs its own JACS agent with:
- Unique agent ID
- Private/public key pair
- Configuration file
Debugging
Enable Debug Logging
export JACS_MCP_DEBUG=true
This outputs detailed logs about message signing and verification.
STDIO Debug Note
For STDIO transports, debug logs go to stderr to prevent contaminating the JSON-RPC stream on stdout.
Common Issues
"JACS not operational": Check that your config file path is correct and the agent is properly initialized.
Verification failures: Ensure both server and client are using compatible JACS versions and valid keys.
Empty responses: The proxy removes null values from messages to prevent MCP schema validation issues.
Next Steps
- HTTP Server - Create HTTP APIs with JACS
- Express Middleware - Integrate with Express.js
- API Reference - Complete API documentation