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
| Step | File | Changes |
|---|---|---|
| 1 | packages/desktop/app/shared/contracts/mcp-types.ts | Extend McpServerTransport type |
| 2 | packages/desktop/app/main/services/capabilities/tools/mcp-users/McpService.ts | Add new branch in createTransport() |
| 3 | packages/desktop/app/main/services/routers/McpRouter.ts | Extend type enum in mcpAddSchema |
| 4 | packages/renderer/src/features/settings/components/tabs/tools-tab/types.ts | Update frontend types |
| 5 | packages/renderer/src/features/settings/components/tabs/tools-tab/McpServerForm.tsx | Add form options |
| 6 | i18n files | Add 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:
| Field | Value |
|---|---|
| Name | my-custom-server |
| Transport Type | Stdio |
| Command | node |
| Arguments | /path/to/my-server.js |
Tool Definition Specification
Elftia has the following requirements for MCP tools:
| Requirement | Description |
|---|---|
| Name | Must be provided, used to generate tool ID (mcp__server__name) |
| Description | Highly recommended, LLM relies on description to determine when to call |
| inputSchema | Must be valid JSON Schema, type: 'object' |
| Return format | content 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):
-
Set the
binfield inpackage.json:{"name": "@yourorg/mcp-server-example","bin": {"mcp-server-example": "./dist/index.js"}} -
Ensure the entry file has a shebang:
#!/usr/bin/env node -
Users configure it in Elftia as:
Field Value Command npxArguments -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:
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier |
name | Yes | Display name |
provider | Yes | Provider identifier |
category | Yes | Category: search / vision / web-reading / code-repo / general |
description | Yes | Feature description |
tools | Yes | List of provided tool names |
transportType | No | stdio (default) / http |
command | For Stdio | Launch command |
args | For Stdio | Command arguments |
url | For HTTP | Remote URL |
requiresApiKey | Yes | Whether API Key is required |
apiKeyEnvVar | No | Environment variable name for API Key |
linkedProviderIds | No | Associated LLM provider IDs (auto-fetch Key) |
extraEnv | No | Extra fixed environment variables |
documentationUrl | No | Documentation 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:
| Command | Installation Method |
|---|---|
npx / npm / node | Windows: winget; macOS: Homebrew; Others: Download page |
uv / uvx | Official install script (Windows: PowerShell; Unix: curl + sh) |
Related Files
- Connection Pool Management — Understand how connections are established
- Tool Format Adapters — Understand tool format conversion
- Worker Architecture — CLI bridging method
- Module Overview — Overall architecture