You can compose the default Appium MCP server with custom business logic using the appium-mcp/core package. This allows you to register custom MCP tools, prompts, resources, and resource templates without maintaining a fork of the main repository.
Key Concepts
- Registration: Plugins use the
McpRegistry to add capabilities. Tools, prompts, and resources follow the FastMCP definition shapes. - Lifecycle Hooks: Plugins can wrap tool execution with
beforeCall and afterCall hooks. Note that these hooks only apply to tools, not to prompts or resources. - Discovery Control: Use the
policy option in createAppiumMcpServer to hide specific tools or resources from MCP discovery using regular expressions. - Naming Collisions:
- Plugin Names: Must be unique. If two plugins share a name, the first one wins and the second is skipped with a warning. Use prefixed names like
acme-checkout-plugin to avoid collisions. - Tool Names: Follow FastMCP behavior (last-registration-wins). Since Appium MCP registers built-in tools first, a plugin tool with the same name will override a built-in tool.
import { createAppiumMcpServer } from 'appium-mcp/core';
import type { AppiumMcpPlugin, McpRegistry, ToolCallContext } from 'appium-mcp/core';
import { z } from 'zod';
class CheckoutPlugin implements AppiumMcpPlugin {
readonly name = 'checkout-plugin';
readonly version = '1.0.0';
register(registry: McpRegistry): void {
const parameters = z.object({ orderId: z.string() });
registry.addTool({
name: 'assert_checkout_summary',
description: 'Assert that the checkout summary screen shows an expected order ID.',
parameters,
execute: async (args) => {
const { orderId } = parameters.parse(args);
return {
content: [{ type: 'text', text: `Assert checkout order ${orderId}` }],
};
},
});
}
async beforeCall(ctx: ToolCallContext): Promise<void> {
if (ctx.toolName === 'appium_gesture') {
console.error(`[checkout-plugin] about to call ${ctx.toolName}`);
}
}
}
const server = await createAppiumMcpServer({
plugins: [new CheckoutPlugin()],
additionalInstructions: 'Custom checkout policies are active.',
policy: {
allowTools: [/^appium_session_management$/, /^assert_checkout_summary$/],
allowResources: [/^Generate Code With Locators$/],
},
});
await server.start({ transportType: 'stdio' });