Skip to main content

How to Extend MCP

This page provides two extension guides: one for adding new MCP transport types to Elftia, and another for writing custom MCP servers that work with Elftia.

Guide One: Adding New Transport Types

If you need to support transport methods other than Stdio/SSE/HTTP (such as WebSocket, gRPC, etc.), you need to modify the following files.

Modification Checklist

StepFileChanges
1packages/desktop/app/shared/contracts/mcp-types.tsExtend McpServerTransport type
2packages/desktop/app/main/services/capabilities/tools/mcp-users/McpService.tsAdd new branch in createTransport()
3packages/desktop/app/main/services/routers/McpRouter.tsExtend type enum in mcpAddSchema
4packages/renderer/src/features/settings/components/tabs/tools-tab/types.tsUpdate frontend types
5packages/renderer/src/features/settings/components/tabs/tools-tab/McpServerForm.tsxAdd form options
6i18n filesAdd display names for new transport type

Step 1: Extend Types

Add new transport type value in mcp-types.ts:

export type McpServerTransport = 'stdio' | 'http' | 'sse' | 'websocket';

Also check if McpServerConfig needs new fields:

export type McpServerConfig = {
command?: string;
args?: string[];
env?: Record<string, string>;
url?: string;
headers?: Record<string, string>;
transport?: string;
// Add new fields if needed
wsProtocol?: string;
};

Step 2: Implement Transport Layer

Add a new case branch in createTransport() method in McpService.ts:

private async createTransport(server: McpServerRecord) {
switch (server.type) {
case 'stdio':
// ...
case 'sse':
// ...
case 'http':
// ...
case 'websocket':
if (!server.config.url) {
throw new Error('WebSocket server requires url');
}
// Use your WebSocket transport implementation
return new WebSocketClientTransport(
new URL(server.config.url),
{ headers: server.config.headers || {} }
);
default:
throw new Error(`Unsupported transport type: ${server.type}`);
}
}

Requirement: Transport implementation must conform to MCP SDK's Transport interface and work with Client.connect().

Step 3: Update IPC Validation

Extend Zod schema in McpRouter.ts:

const mcpAddSchema = z.object({
// ...
type: z.enum(['stdio', 'http', 'sse', 'websocket']),
// ...
});

Steps 4-5: Update Frontend

Add options to the transport type selector in McpServerForm.tsx:

options={[
{ value: 'stdio', label: t('...stdio') },
{ value: 'sse', label: t('...sse') },
{ value: 'http', label: t('...http') },
{ value: 'websocket', label: t('...websocket') },
]}

Depending on your new transport type requirements, add corresponding configuration form fields (URL, protocol selection, etc.).

Step 6: Internationalization

Add display names for new transport type in locales/{en,zh,ja}/settings/tools.json.

Guide Two: Writing Custom MCP Servers

Writing an MCP server that works with Elftia requires following the MCP protocol specification.

Minimal Stdio Server (Node.js)

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new Server(
{ name: 'my-custom-server', version: '1.0.0' },
{
capabilities: {
tools: {}
}
}
);

// Register tool list
server.setRequestHandler('tools/list', async () => ({
tools: [
{
name: 'hello',
description: 'Return a greeting',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Name to greet' }
},
required: ['name']
}
}
]
}));

// Handle tool calls
server.setRequestHandler('tools/call', async (request) => {
if (request.params.name === 'hello') {
const name = request.params.arguments?.name ?? 'World';
return {
content: [
{ type: 'text', text: `Hello, ${name}!` }
]
};
}
return {
content: [{ type: 'text', text: 'Unknown tool' }],
isError: true
};
});

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

Using in Elftia

Save the above server as my-server.js and add a Stdio server in Elftia:

FieldValue
Namemy-custom-server
Transport TypeStdio
Commandnode
Arguments/path/to/my-server.js

Tool Definition Specification

Elftia has the following requirements for MCP tools:

RequirementDescription
NameMust be provided, used to generate tool ID (mcp__server__name)
DescriptionHighly recommended, LLM relies on description to determine when to call
inputSchemaMust be valid JSON Schema, type: 'object'
Return formatcontent array, each item contains type and corresponding data

Return Content Types

// Text return
{ type: 'text', text: 'Result text' }

// Image return
{ type: 'image', mimeType: 'image/png', data: '<base64-encoded>' }

// Audio return
{ type: 'audio', mimeType: 'audio/wav', data: '<base64-encoded>' }

Note: Elftia's McpToolAdapter converts images and audio to placeholder text ([Image: mime]), and the actual binary data is handled by the ToolCallDisplay component for rendering.

Timeout Considerations

Elftia sets a 120-second timeout for MCP tool calls. If your tool may execute longer operations, we recommend:

  • Implement asynchronous processing, return a task ID first and provide a polling interface
  • Note possible wait times in the tool description
  • Consider using streaming returns (if transport layer supports it)

npm Package Publishing

If you want to publish your MCP server as an npm package (supporting one-click launch with npx):

  1. Set the bin field in package.json:

    {
    "name": "@yourorg/mcp-server-example",
    "bin": {
    "mcp-server-example": "./dist/index.js"
    }
    }
  2. Ensure the entry file has a shebang:

    #!/usr/bin/env node
  3. Users configure it in Elftia as:

    FieldValue
    Commandnpx
    Arguments-y , @yourorg/mcp-server-example

Adding to Official Presets

If you want your MCP server to appear in Elftia's official preset list, add configuration in packages/desktop/app/shared/mcp-presets.ts:

export const OFFICIAL_MCP_PRESETS: OfficialMcpPreset[] = [
// Existing presets...
{
id: 'your-server-id',
name: 'Your Server Name',
provider: 'your-org',
category: 'general', // search | vision | web-reading | code-repo | general
description: 'Server feature description',
tools: ['tool1', 'tool2'],
command: 'npx',
args: ['-y', '@yourorg/mcp-server@latest'],
requiresApiKey: true,
apiKeyEnvVar: 'YOUR_API_KEY',
linkedProviderIds: ['provider-id'],
documentationUrl: 'https://docs.example.com',
icon: 'icon-name',
},
];

Preset field descriptions:

FieldRequiredDescription
idYesUnique identifier
nameYesDisplay name
providerYesProvider identifier
categoryYesCategory: search / vision / web-reading / code-repo / general
descriptionYesFeature description
toolsYesList of provided tool names
transportTypeNostdio (default) / http
commandFor StdioLaunch command
argsFor StdioCommand arguments
urlFor HTTPRemote URL
requiresApiKeyYesWhether API Key is required
apiKeyEnvVarNoEnvironment variable name for API Key
linkedProviderIdsNoAssociated LLM provider IDs (auto-fetch Key)
extraEnvNoExtra fixed environment variables
documentationUrlNoDocumentation link

DependencyCheckService Extension

If your MCP server depends on non-standard command-line tools, you can extend the installCommand() method of DependencyCheckService:

File path: packages/desktop/app/main/services/capabilities/tools/mcp-users/DependencyCheckService.ts

async installCommand(command: string): Promise<DependencyInstallResult> {
// ...
if (command === 'your-tool') {
return await this.installYourTool(command);
}
// ...
}

Currently supported auto-installation:

CommandInstallation Method
npx / npm / nodeWindows: winget; macOS: Homebrew; Others: Download page
uv / uvxOfficial install script (Windows: PowerShell; Unix: curl + sh)