Skip to main content

Creating Your First Extension

This guide walks you through creating a complete Qwen Code extension from scratch. You’ll learn how to set up the project structure, add an MCP server with custom tools, create commands, and configure everything properly.

Prerequisites

Before starting, ensure you have:
  • Qwen Code installed and working
  • Node.js 20+ (if creating MCP servers)
  • Basic understanding of TypeScript/JavaScript
  • A text editor or IDE

Quick Start: Using Templates

The fastest way to start is using the built-in templates:
# List available templates
qwen extensions new --help

# Create from template
qwen extensions new my-extension mcp-server
Available templates:
  • mcp-server - MCP server with example tools
  • commands - Custom command examples
  • skills - Skill examples
  • agent - Subagent examples
  • context - Context provider example

Manual Setup

To create an extension manually:

Step 1: Create the Extension Directory

mkdir my-first-extension
cd my-first-extension

Step 2: Create the Manifest File

Create qwen-extension.json:
{
  "name": "my-first-extension",
  "version": "1.0.0"
}
Naming rules:
  • Use lowercase letters, numbers, and dashes only
  • No spaces or underscores
  • Must be unique among your installed extensions
  • Directory name should match the name field

Step 3: Add an MCP Server (Optional)

MCP servers provide tools the AI can use. Let’s create a simple server.

Create Package Files

Create package.json:
{
  "name": "my-first-extension",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.11.0",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^20.11.25",
    "typescript": "~5.4.5"
  }
}
Create tsconfig.json:
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist"
  },
  "include": ["server.ts"]
}

Create the MCP Server

Create server.ts:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

const server = new McpServer({
  name: 'my-first-extension',
  version: '1.0.0',
});

// Register a simple tool
server.registerTool(
  'get_greeting',
  {
    description: 'Returns a greeting message',
    inputSchema: z.object({
      name: z.string().describe('Name to greet'),
    }).shape,
  },
  async ({ name }) => {
    return {
      content: [
        {
          type: 'text',
          text: `Hello, ${name}! Welcome to my extension.`,
        },
      ],
    };
  },
);

// Register a prompt
server.registerPrompt(
  'greeting-prompt',
  {
    title: 'Greeting Generator',
    description: 'Generate a creative greeting',
    argsSchema: {
      name: z.string(),
      style: z.enum(['formal', 'casual', 'funny']).optional(),
    },
  },
  ({ name, style }) => ({
    messages: [
      {
        role: 'user',
        content: {
          type: 'text',
          text: `Generate a ${style || 'friendly'} greeting for ${name}.`,
        },
      },
    ],
  }),
);

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

Update the Manifest

Update qwen-extension.json to reference the MCP server:
{
  "name": "my-first-extension",
  "version": "1.0.0",
  "mcpServers": {
    "myServer": {
      "command": "node",
      "args": ["${extensionPath}${/}dist${/}server.js"],
      "cwd": "${extensionPath}"
    }
  }
}
Variable substitution:
  • ${extensionPath} - Full path to extension directory
  • ${workspacePath} - Current workspace directory
  • ${/} or ${pathSeparator} - OS-specific path separator

Build the Server

npm install
npm run build
This compiles server.ts to dist/server.js.

Step 4: Add Custom Commands (Optional)

Create a commands directory:
mkdir -p commands/utils
Create commands/utils/summarize.md:
---
description: Summarize the content of a file
---

Please provide a concise summary of the following file:

File: {{args}}

!{cat {{args}}}
Create commands/search.md:
---
description: Search for pattern and provide analysis
---

Search for: `{{args}}`

Results:
!{grep -r "{{args}}" . --include="*.ts" --include="*.js"}

Please analyze these search results and provide insights.
Command naming:
  • Top-level files: /search
  • Nested files: /utils:summarize
  • If conflicts exist: /my-first-extension.search

Step 5: Add a Skill (Optional)

Create a skill directory:
mkdir -p skills/code-reviewer
Create skills/code-reviewer/SKILL.md:
---
name: code-reviewer
description: Performs thorough code reviews focusing on best practices, potential bugs, and improvements
license: MIT
---

# Code Reviewer Skill

This skill helps review code for quality, correctness, and maintainability.

## Review Focus Areas

### Code Quality
- Readability and clarity
- Naming conventions
- Code organization
- Documentation completeness

### Potential Issues
- Logic errors
- Edge cases not handled
- Performance concerns
- Security vulnerabilities

### Best Practices
- Design patterns
- SOLID principles
- Error handling
- Testing coverage

## Output Format

Provide reviews in this structure:

1. **Summary**: Overall assessment
2. **Strengths**: What's done well
3. **Issues**: Problems found (severity: critical/major/minor)
4. **Suggestions**: Specific improvements
5. **Examples**: Code snippets showing improvements
Update qwen-extension.json:
{
  "name": "my-first-extension",
  "version": "1.0.0",
  "mcpServers": { ... },
  "commands": "commands",
  "skills": "skills"
}

Step 6: Add a Subagent (Optional)

Create an agents directory:
mkdir agents
Create agents/test-writer.md:
---
name: test-writer
description: Specialized in writing comprehensive unit tests and test suites
color: green
tools:
  - Read
  - Write
  - Grep
  - Bash
modelConfig:
  model: qwen3-coder-plus
  temperature: 0.7
---

You are a testing specialist focused on creating high-quality test suites.

## Your Expertise

- Writing unit tests with proper coverage
- Creating integration tests
- Test-driven development (TDD)
- Mocking and stubbing strategies
- Testing edge cases and error conditions

## Testing Approach

1. **Understand the Code**: Read and analyze the implementation
2. **Identify Test Cases**: Consider normal, edge, and error cases
3. **Write Clear Tests**: Use descriptive names and arrange-act-assert pattern
4. **Ensure Coverage**: Test all critical paths and branches
5. **Use Best Practices**: Follow framework conventions and patterns

## Test Structure

For each function or module:
- Test the happy path
- Test boundary conditions
- Test error handling
- Test integration points
- Mock external dependencies

Always run tests after writing them to ensure they pass.
Update qwen-extension.json:
{
  "name": "my-first-extension",
  "version": "1.0.0",
  "mcpServers": { ... },
  "commands": "commands",
  "skills": "skills",
  "agents": "agents"
}

Step 7: Add Context (Optional)

Create QWEN.md:
# My First Extension

This extension provides development tools and AI assistants.

## Available Tools

### get_greeting
Returns a greeting message. Use this when the user wants a friendly greeting.

Example: "Can you greet John?"

## Custom Commands

- `/search <pattern>` - Search codebase and analyze results
- `/utils:summarize <file>` - Summarize file contents

## Guidelines

- Always be helpful and concise
- Use tools when appropriate
- Provide code examples when explaining concepts
Update qwen-extension.json:
{
  "name": "my-first-extension",
  "version": "1.0.0",
  "contextFileName": "QWEN.md",
  "mcpServers": { ... },
  "commands": "commands",
  "skills": "skills",
  "agents": "agents"
}

Step 8: Add Settings (Optional)

If your extension needs configuration, add settings:
{
  "name": "my-first-extension",
  "version": "1.0.0",
  "settings": [
    {
      "name": "API Key",
      "description": "Your API key for the external service",
      "envVar": "MY_EXTENSION_API_KEY",
      "sensitive": true
    },
    {
      "name": "Base URL",
      "description": "Base URL for API requests",
      "envVar": "MY_EXTENSION_BASE_URL",
      "sensitive": false
    }
  ],
  "contextFileName": "QWEN.md",
  "mcpServers": {
    "myServer": {
      "command": "node",
      "args": ["${extensionPath}${/}dist${/}server.js"],
      "cwd": "${extensionPath}",
      "env": {
        "API_KEY": "${MY_EXTENSION_API_KEY}",
        "BASE_URL": "${MY_EXTENSION_BASE_URL}"
      }
    }
  }
}
Settings are:
  • Prompted during installation
  • Stored securely (sensitive values in system keychain)
  • Passed as environment variables to MCP servers
  • Manageable via qwen extensions settings command

Testing Your Extension

Create a symlink to your extension for development:
qwen extensions link .
This allows you to test changes without reinstalling. However, you’ll need to restart Qwen Code sessions to see changes (except for runtime commands which hot-reload).

Test the Extension

Restart Qwen Code and try:
# Test MCP tool
"Can you greet Alice?"

# Test command
/search "TODO"

# Test agent
/agents manage
# Select and interact with test-writer

# View skills
/skills
To remove the symlink:
qwen extensions uninstall my-first-extension

Final Directory Structure

Your complete extension:
my-first-extension/
├── qwen-extension.json
├── package.json
├── tsconfig.json
├── server.ts
├── dist/
│   └── server.js              # Built by `npm run build`
├── QWEN.md
├── commands/
│   ├── search.md
│   └── utils/
│       └── summarize.md
├── skills/
│   └── code-reviewer/
│       └── SKILL.md
└── agents/
    └── test-writer.md

Next Steps