Skip to main content

MCP Server Tools

Model Context Protocol (MCP) servers extend Qwen Code’s capabilities by providing access to external tools, APIs, and data sources.

Overview

MCP servers act as bridges between Qwen Code and external systems, allowing the AI to:
  • Discover tools: List available tools with schemas
  • Execute tools: Call tools with structured parameters
  • Access resources: Read data from external sources
  • Extend functionality: Add custom capabilities without modifying Qwen Code

Architecture

Integration Flow

Qwen Code

    v
┌──────────────────────────┐
│ MCP Client                │
│ (mcp-client.ts)           │
│                            │
│ 1. Discover servers        │
│ 2. Connect via transport   │
│ 3. Fetch tool schemas      │
│ 4. Register in registry    │
└──────────┬───────────────┘

           v
┌──────────────────────────┐
│ Tool Registry              │
│                            │
│ - Built-in tools           │
│ - MCP tools (dynamic)      │
└──────────┬───────────────┘

           v
┌──────────────────────────┐
│ MCP Tool Wrapper           │
│ (mcp-tool.ts)              │
│                            │
│ - Confirmation logic       │
│ - Parameter validation     │
│ - Execution & response     │
└──────────┬───────────────┘

           v
┌──────────────────────────┐
│ MCP Server                 │
│ (External Process)         │
│                            │
│ - Custom tools             │
│ - API integrations         │
│ - Database access          │
└──────────────────────────┘

Transport Mechanisms

Qwen Code supports three transport types:

1. Stdio Transport

Communicates via standard input/output:
{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["path/to/server.js"],
      "transport": "stdio"
    }
  }
}
Use for:
  • Local scripts
  • Command-line tools
  • Simple integrations

2. SSE Transport

Server-Sent Events over HTTP:
{
  "mcpServers": {
    "remote-server": {
      "url": "https://api.example.com/mcp",
      "transport": "sse",
      "headers": {
        "Authorization": "Bearer $API_TOKEN"
      }
    }
  }
}
Use for:
  • Remote services
  • Cloud APIs
  • Webhooks

3. Streamable HTTP

HTTP streaming:
{
  "mcpServers": {
    "http-server": {
      "url": "https://api.example.com/mcp",
      "transport": "streamable-http"
    }
  }
}
Use for:
  • RESTful APIs
  • Modern web services

Configuration

Basic Setup

Add to settings.json:
{
  "mcpServers": {
    "serverName": {
      "command": "path/to/server",
      "args": ["--arg1", "value1"],
      "env": {
        "API_KEY": "$MY_API_TOKEN"
      },
      "cwd": "./server-directory",
      "timeout": 30000,
      "trust": false
    }
  }
}

Configuration Properties

interface MCPServerConfig {
  // Execution
  command?: string;           // Command to run server
  args?: string[];           // Command arguments
  env?: Record<string, string>; // Environment variables
  cwd?: string;              // Working directory
  
  // Connection
  url?: string;              // For SSE/HTTP transport
  transport?: 'stdio' | 'sse' | 'streamable-http';
  headers?: Record<string, string>; // HTTP headers
  
  // Behavior
  timeout?: number;          // Timeout in ms
  trust?: boolean;           // Skip confirmation
  disabled?: boolean;        // Disable this server
}

Environment Variables

Use $VAR_NAME syntax for environment variables:
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "$GITHUB_PERSONAL_ACCESS_TOKEN"
      }
    }
  }
}
Variables are resolved from:
  1. Process environment
  2. User’s shell environment
  3. .env files (if supported)

Global MCP Settings

{
  "mcp": {
    "allowed": ["trusted-server"],     // Allowlist
    "excluded": ["experimental-server"], // Blocklist
    "serverCommand": "node"             // Default command
  }
}

Tool Discovery

Discovery Process

// packages/core/src/tools/mcp-client.ts
export async function discoverMcpTools(
  config: Config,
): Promise<void> {
  const serverConfigs = config.getMcpServers();
  
  for (const [name, serverConfig] of Object.entries(serverConfigs)) {
    // 1. Create transport
    const transport = createTransport(serverConfig);
    
    // 2. Connect to server
    const client = new Client({ name, version: '1.0' });
    await client.connect(transport);
    
    // 3. List available tools
    const toolsList = await client.listTools();
    
    // 4. Register each tool
    for (const toolDef of toolsList.tools) {
      const mcpTool = new DiscoveredMCPTool(
        toolDef,
        name,
        serverConfig.trust,
      );
      config.getToolRegistry().registerTool(mcpTool);
    }
  }
}

Tool Registration

MCP tools are registered with unique names:
Original: get_user
Registered: mcp_github_get_user

Original: list_repos  
Registered: mcp_github_list_repos
Naming pattern: mcp_{serverName}_{toolName} This prevents conflicts between:
  • Built-in tools
  • Different MCP servers
  • Tools with same names

Tool Execution

Execution Flow

// User/Model requests tool
mcp_github_get_user({ username: "octocat" });

// 1. Tool validation
const tool = registry.getTool("mcp_github_get_user");

// 2. Confirmation (if needed)
if (requiresConfirmation) {
  await confirmWithUser();
}

// 3. Call MCP server
const result = await mcpClient.callTool({
  name: "get_user",
  arguments: { username: "octocat" },
});

// 4. Process response
return {
  llmContent: result.content,
  returnDisplay: formatForUI(result),
};

Confirmation

MCP tools require confirmation unless:
  1. Server is trusted:
    {
      "mcpServers": {
        "my-server": {
          "trust": true  // Skip confirmations
        }
      }
    }
    
  2. Tool is read-only:
    // MCP tool annotations
    {
      readOnlyHint: true,  // Safe, no confirmation
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    }
    
  3. User allowlisted it:
    • Choose “Always allow” in confirmation dialog
    • Applies to current session

Tool Annotations

MCP tools can provide hints about their behavior:
interface McpToolAnnotations {
  readOnlyHint?: boolean;      // Safe read operation
  destructiveHint?: boolean;   // Modifies or deletes data
  idempotentHint?: boolean;    // Same result on repeated calls
  openWorldHint?: boolean;     // Accesses external network
}
These help Qwen Code make informed decisions about confirmation and execution.

Examples

GitHub Server

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-github"
      ],
      "env": {
        "GITHUB_TOKEN": "$GITHUB_PERSONAL_ACCESS_TOKEN"
      }
    }
  }
}
Available Tools:
  • mcp_github_create_or_update_file
  • mcp_github_search_repositories
  • mcp_github_create_repository
  • mcp_github_get_file_contents
  • mcp_github_push_files
  • mcp_github_create_issue
  • mcp_github_create_pull_request
  • mcp_github_fork_repository
  • mcp_github_create_branch

Filesystem Server

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/allowed/path"
      ]
    }
  }
}
Available Tools:
  • mcp_filesystem_read_file
  • mcp_filesystem_read_multiple_files
  • mcp_filesystem_write_file
  • mcp_filesystem_create_directory
  • mcp_filesystem_list_directory
  • mcp_filesystem_move_file
  • mcp_filesystem_search_files
  • mcp_filesystem_get_file_info

Database Server

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres"
      ],
      "env": {
        "POSTGRES_CONNECTION_STRING": "$DATABASE_URL"
      }
    }
  }
}
Available Tools:
  • mcp_postgres_query
  • mcp_postgres_list_tables
  • mcp_postgres_describe_table
  • mcp_postgres_insert
  • mcp_postgres_update

Custom Server

{
  "mcpServers": {
    "my-custom-server": {
      "command": "node",
      "args": ["./mcp-servers/custom.js"],
      "cwd": "./",
      "timeout": 60000,
      "trust": false
    }
  }
}

Creating MCP Servers

Server Structure

MCP servers follow a standard protocol:
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

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

// Register tools
server.setRequestHandler('tools/list', async () => ({
  tools: [
    {
      name: 'my_tool',
      description: 'Does something useful',
      inputSchema: {
        type: 'object',
        properties: {
          param: {
            type: 'string',
            description: 'A parameter',
          },
        },
        required: ['param'],
      },
    },
  ],
}));

server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;
  
  if (name === 'my_tool') {
    const result = await doSomething(args.param);
    return {
      content: [
        {
          type: 'text',
          text: `Result: ${result}`,
        },
      ],
    };
  }
  
  throw new Error(`Unknown tool: ${name}`);
});

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

Tool Schema

Define tools with JSON Schema:
{
  name: 'search_code',
  description: 'Search for code patterns in the repository',
  inputSchema: {
    type: 'object',
    properties: {
      query: {
        type: 'string',
        description: 'Search query (regex supported)',
      },
      file_pattern: {
        type: 'string',
        description: 'File pattern to search in (glob)',
      },
      case_sensitive: {
        type: 'boolean',
        description: 'Whether search is case-sensitive',
        default: false,
      },
    },
    required: ['query'],
  },
  annotations: {
    readOnlyHint: true,  // Safe read operation
  },
}

Response Format

Return results in MCP format:
return {
  content: [
    // Text content
    {
      type: 'text',
      text: 'Search found 5 results',
    },
    // Image content
    {
      type: 'image',
      data: base64Data,
      mimeType: 'image/png',
    },
    // Resource link
    {
      type: 'resource',
      resource: {
        uri: 'file:///path/to/file',
        text: 'Content...',
      },
    },
  ],
  isError: false,
};

Security

Trust Levels

Untrusted (default):
  • Requires confirmation for each tool use
  • Shows tool name and parameters
  • User can allowlist specific tools
Trusted:
  • No confirmation required
  • Use only for verified servers
  • Set trust: true in config

Best Practices

  1. Review Server Code:
    • Inspect server implementation
    • Verify it’s from trusted source
    • Check for suspicious behavior
  2. Limit Permissions:
    • Give minimum necessary access
    • Use environment variables for secrets
    • Don’t store secrets in config
  3. Use Allowlists:
    {
      "mcp": {
        "allowed": ["github", "filesystem"]
      }
    }
    
  4. Monitor Activity:
    • Review confirmation prompts
    • Check logs for suspicious calls
    • Disable unused servers
  5. Sandbox if Possible:
    export QWEN_SANDBOX=docker
    

Environment Variable Security

Store sensitive data in environment:
# .env (never commit this)
export GITHUB_TOKEN="ghp_..."
export DATABASE_URL="postgresql://..."
export API_KEY="sk-..."
Reference in config:
{
  "env": {
    "GITHUB_TOKEN": "$GITHUB_TOKEN",
    "API_KEY": "$API_KEY"
  }
}

Troubleshooting

Server Not Found

Error: MCP server "my-server" not found Solutions:
  1. Check server name in config
  2. Verify command is in PATH
  3. Use absolute path: /usr/local/bin/server
  4. Check disabled: false

Connection Timeout

Error: Connection to MCP server timed out Solutions:
  1. Increase timeout: "timeout": 60000
  2. Check server starts quickly
  3. Verify network connectivity (for remote servers)
  4. Check server logs

Tool Not Available

Error: Tool "mcp_server_tool" not found Solutions:
  1. Restart Qwen Code (tools discovered on startup)
  2. Check server is enabled
  3. Verify server implements tool
  4. Check for name conflicts

Permission Denied

Error: Permission denied: /path/to/server Solutions:
  1. Make server executable: chmod +x /path/to/server
  2. Check file permissions
  3. Run with correct user

Implementation

Key Files:
  • packages/core/src/tools/mcp-client.ts - Discovery and client
  • packages/core/src/tools/mcp-tool.ts - Tool wrapper
  • packages/core/src/tools/mcp-client-manager.ts - Connection management

Client Manager

export class McpClientManager {
  private clients = new Map<string, McpDirectClient>();

  async getOrCreateClient(
    serverName: string,
    config: MCPServerConfig,
  ): Promise<McpDirectClient> {
    if (this.clients.has(serverName)) {
      return this.clients.get(serverName)!;
    }

    const transport = createTransport(config);
    const client = new Client({ name: serverName, version: '1.0' });
    await client.connect(transport);

    this.clients.set(serverName, client);
    return client;
  }
}

Next Steps