MCP Plugin Development

MCP (Model Context Protocol) plugins wrap tools conforming to the Model Context Protocol standard. They run as separate processes and communicate via JSON-RPC, making them suitable for wrapping existing services or implementing language-agnostic tools.

Overview

An MCP plugin is a plugin entry in the CortexPrism registry whose type is mcp and whose entry point launches an MCP-compatible server process. The CortexPrism plugin manager manages the child process lifecycle and translates capability calls into MCP tools/call requests.

How It Works

Agent → Plugin Manager → MCP Client (JSON-RPC over stdio/HTTP)
                              ↓
                         MCP Server Process
                              ↓
                         Tool Implementation

The MCP server advertises available tools via tools/list and handles invocations via tools/call. The CortexPrism MCP client is built on the official MCP TypeScript SDK.

Plugin Manifest

{
  "name": "git-server",
  "version": "1.0.0",
  "type": "mcp",
  "entryPoint": "node dist/server.js",
  "description": "Git repository management tools",
  "mcp": {
    "transport": "stdio",
    "args": ["--repo-dir", "/workspace"],
    "env": {
      "GIT_ALLOWED_COMMANDS": "status,log,diff"
    },
    "timeout": 30000
  }
}

Creating an MCP Server

You can create an MCP server in any language. Here is a TypeScript example:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  { name: "git-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "git_status",
      description: "Show working tree status",
      inputSchema: {
        type: "object",
        properties: {
          path: { type: "string", description: "Repository path" },
        },
      },
    },
    {
      name: "git_log",
      description: "Show commit log",
      inputSchema: {
        type: "object",
        properties: {
          path: { type: "string" },
          maxCount: { type: "number", default: 10 },
        },
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  switch (name) {
    case "git_status": {
      const output = await execGit(["status"], args.path);
      return { content: [{ type: "text", text: output }] };
    }
    case "git_log": {
      const output = await execGit(["log", `--max-count=${args.maxCount || 10}`], args.path);
      return { content: [{ type: "text", text: output }] };
    }
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);

Python MCP Server Example

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import TextContent, Tool

app = Server("weather-server")

@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="get_weather",
            description="Get current weather for a city",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name"}
                },
                "required": ["city"],
            },
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "get_weather":
        city = arguments["city"]
        result = await fetch_weather(city)
        return [TextContent(type="text", text=str(result))]
    raise ValueError(f"Unknown tool: {name}")

if __name__ == "__main__":
    import anyio
    anyio.run(stdio_server, app)

Transports

MCP supports two transport mechanisms:

stdio (Default)

The plugin manager spawns the MCP server as a subprocess and communicates via stdin/stdout.

{
  "mcp": { "transport": "stdio" }
}

HTTP/SSE

The MCP server runs as an HTTP server with Server-Sent Events.

{
  "mcp": {
    "transport": "http",
    "url": "http://localhost:8080/mcp"
  }
}

Lifecycle Management

EventAction
Plugin installEntry point registered but not started
First capability callMCP server process spawned
Subsequent callsMessages sent to the running process
Plugin disableProcess sent SIGTERM (graceful shutdown)
Plugin uninstallProcess killed, resources cleaned up
Plugin idle timeoutProcess terminated after inactivity period

Error Handling

MCP tools should return structured error responses:

{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "Repository not found at path: /invalid/path"
    }
  ]
}

Security Considerations

  • MCP processes run with the permissions declared in the manifest
  • Network access is controlled by the Parallax policy engine
  • Long-running processes are monitored for resource usage
  • Output is capped to prevent flooding (default: 64KB)
  • Timeout is enforced per tool call (default: 30s)

Debugging

Enable verbose logging to inspect MCP communication:

cortex --verbose plugin list

The raw JSON-RPC messages are logged to the Cortex Lens audit trail.