mcp-lockdown
Version:
Strict TypeScript library for Model Context Protocol (MCP) tool manifest pinning, schema enforcement, and policy vetos.
118 lines • 5.84 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LockdownServer = void 0;
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
const zod_from_json_schema_1 = require("zod-from-json-schema");
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
const readDownstreamMcpServersJson = (mcpServersJson) => {
const servers = Object.keys(mcpServersJson.servers).map((key) => (Object.assign({ name: key }, mcpServersJson.servers[key])));
console.log("Connecting Lockdown to the following MCP servers...");
console.log(servers.map((server) => `- ${server.name} (${server.type})`).join("\n"));
return servers;
};
// Call a given tool on a server
const callTool = (server, tool, args) => __awaiter(void 0, void 0, void 0, function* () {
const client = new index_js_1.Client({
name: "MCP Lockdown Client",
version: "1.0.0",
});
console.log(`Using: ${server.name} - ${tool.name}`);
if (server.type === "stdio") {
const transport = new stdio_js_1.StdioClientTransport(server);
yield client.connect(transport);
}
else {
const transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(server.url), {
requestInit: server.requestInit
});
yield client.connect(transport);
}
return client.callTool({ name: tool.name, arguments: args });
});
const toolIsValid = (pm, tool) => __awaiter(void 0, void 0, void 0, function* () {
const result = yield pm.evaluate(tool);
if (result) {
console.log(`\t✅ Tool validation passed for ${tool.name}`);
}
else {
console.error(`\t❌ Tool validation failed, ${tool.name} will not be registered\n`);
}
return result;
});
// given server connection details
// return a list of tools for that server that lockdown allows
const fetchValidToolsManifest = (pm, server) => __awaiter(void 0, void 0, void 0, function* () {
const client = new index_js_1.Client({
name: "MCP Lockdown Client",
version: "1.0.0",
});
console.log(`Registering MCP Server: ${server.name}`);
if (server.type === "stdio") {
const transport = new stdio_js_1.StdioClientTransport(server);
yield client.connect(transport);
}
else {
const transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(server.url), {
requestInit: server.requestInit
});
yield client.connect(transport);
}
const tools = yield client.listTools();
return Object.assign(Object.assign({}, server), { tools: (yield Promise.all(tools.tools.map((t) => __awaiter(void 0, void 0, void 0, function* () {
return (Object.assign(Object.assign({}, t), { isValid: yield toolIsValid(pm, t) }));
})))).filter((tool) => tool.isValid) });
});
const LockdownServer = (_a) => __awaiter(void 0, [_a], void 0, function* ({ policyManager, lockdownServerJson, }) {
// Loop through all servers requested from lockdown
// remove tools that don't pass policies
const validatedServers = yield Promise.all(readDownstreamMcpServersJson(lockdownServerJson).map((server) => fetchValidToolsManifest(policyManager, server)));
// Create an MCP server to return with the proxy tools registered
const lockdownServer = new mcp_js_1.McpServer({
name: "Lockdown MCP Server",
version: "1.0.0",
});
// Create a tool for each for tool in the MCP servers
validatedServers.forEach((server) => __awaiter(void 0, void 0, void 0, function* () {
server.tools.forEach((tool) => {
// convert JSON schema back to Zod schema
const zodSchema = (0, zod_from_json_schema_1.convertJsonSchemaToZod)(tool.inputSchema);
const keys = zodSchema.keyof().options;
let inputSchema = {};
for (const key of keys) {
inputSchema[key] = zodSchema.shape[key];
}
lockdownServer.registerTool(`${tool.name}_using_${server.name}`, Object.assign(Object.assign({}, tool), { inputSchema: inputSchema }), (args) => {
return callTool(server, tool, args);
});
});
}));
// Register Lockdown internal tools
lockdownServer.registerTool("explain_missing_tools", {
title: "Lockdown Explain Missing Tools",
description: "Returns a list of reasons that tools were removed from the lockdown MCP server",
annotations: {
title: "Lockdown Explain Missing Tools",
description: "Returns a list of reasons that tools were removed from the lockdown MCP server",
},
}, () => __awaiter(void 0, void 0, void 0, function* () {
return {
content: [
{ type: "text", text: policyManager.listRejections().join(", ") },
],
};
}));
return lockdownServer;
});
exports.LockdownServer = LockdownServer;
//# sourceMappingURL=server.js.map
;