Contributing Guide for AI Agents
March 3, 2026 · View on GitHub
This guide outlines the development standards and best practices for AI assistants contributing to this codebase. Our philosophy prioritizes type safety, single source of truth, modularity, and clean, self-documenting code.
Core Principles
0. Adhere to the principles of working with legacy codebases
- Deeply investigate and understand existing code before making changes.
- Adhere to existing coding styles and patterns.
- Minimize changes to working code; prioritize stability.
- Read documentation, check that it is accurate, and update it if it is not
1. Type Safety First
Always leverage TypeScript's type system fully:
✅ Good:
interface Task {
id: string;
title: string;
completed: boolean;
}
function addTask(task: Task): void {
// Implementation
}
❌ Bad:
function addTask(task: any) {
// Implementation
}
Guidelines:
- Never use
any- useunknownif type is truly unknown, then narrow it - Prefer union types over enums for string constants
- Use strict TypeScript settings (already configured in tsconfig files)
- Leverage type inference but annotate function signatures
- Use Zod for runtime validation (already used in WebMCP tool schemas)
Verify types: Run pnpm typecheck before committing.
2. Single Source of Truth
Never duplicate information - always reference the canonical source.
✅ Good:
/**
* WebMCP tool names
* See: src/main.ts for tool implementations
*/
export const TOOL_NAMES = ['add_to_cart', 'remove_from_cart'] as const;
❌ Bad:
// Duplicating tool list that already exists in main.ts
export const TOOL_NAMES = ['add_to_cart', 'remove_from_cart'];
Guidelines:
- Configuration lives in one place (e.g.,
vite.config.ts) - Constants are exported from a single module and imported elsewhere
- Types are defined once and shared via imports
- Documentation references other docs rather than duplicating content
This applies to documentation too:
- README.md has the example overview
- AGENTS.md links to other documentation (not duplicating it)
- Package-specific docs stay in their directories
3. Modularity
Write small, focused, reusable modules with clear boundaries.
✅ Good:
// src/lib/cart.ts - Pure cart logic
export function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// src/App.tsx - UI component
import { calculateTotal } from './lib/cart';
export function App() {
const total = calculateTotal(cartItems);
// Render UI
}
❌ Bad:
// Everything in one file
export function App() {
// Cart logic mixed with UI
const calculateTotal = () => { /* ... */ };
// Render UI
}
Guidelines:
- One responsibility per file/function
- Separate concerns: logic, UI, types
- Components should be composable and testable
- Pure functions for business logic
- Side effects isolated to specific modules
Recommended file organization:
src/
├── lib/ # Pure utility functions
├── types/ # Shared TypeScript types
├── components/ # React components (React examples only)
├── App.tsx # Main app component
└── main.tsx # Entry point
4. Code Cleanliness
Code should be self-documenting. Use JSDoc for public APIs, not inline comments.
✅ Good:
/**
* Register a WebMCP tool that adds items to the shopping cart
*
* @param productId - Unique identifier for the product
* @param quantity - Number of items to add (must be positive)
* @returns Tool result with updated cart state
*
* @example
* ```ts
* useWebMCP({
* name: "add_to_cart",
* description: "Add item to shopping cart",
* inputSchema: {
* productId: z.string(),
* quantity: z.number().positive()
* },
* handler: async ({ productId, quantity }) => ({ success: true })
* });
* ```
*/
export function addToCart(productId: string, quantity: number) {
// Implementation
}
❌ Bad:
export function addToCart(productId: string, quantity: number) {
// First we check if the product exists
const product = products.find(p => p.id === productId);
// Then we add it to the cart
cart.push({ product, quantity });
// Finally we save the cart
saveCart(cart);
}
Guidelines:
- Write clear function/variable names instead of comments
- Use JSDoc for all exported functions, classes, and types
- Include
@param,@returns, and@examplein JSDoc - No inline comments explaining "what" - code should be clear
- Only use inline comments for "why" if truly necessary (rare)
- Keep functions small and focused (easier to understand without comments)
JSDoc best practices:
- Document the interface, not the implementation
- Include examples for complex APIs
- Link to related documentation:
@see src/main.ts - Keep it concise but complete
Practical Guidelines
Adding New Examples
Before creating a new example:
- Check if it fits better as an enhancement to an existing example
- Ensure it demonstrates a unique WebMCP use case
- Review existing patterns in
/vanillaand/reactdirectories - Identify where types, logic, and UI should live
When creating a new example:
- Define TypeScript types/interfaces first
- Write pure logic functions (testable)
- Create UI components that use the logic
- Add JSDoc to public APIs
- Create comprehensive README.md
- Use the modern WebMCP API (
@mcp-b/globalor@mcp-b/react-webmcp) - Never use deprecated APIs from
/relegated
After creating the example:
cd your-example
pnpm typecheck # Verify types
pnpm lint # Check code quality
pnpm build # Ensure builds succeed
Modifying Existing Examples
Follow the existing patterns:
- If file uses named exports, continue using named exports
- If types are in a separate file, add new types there
- Match the JSDoc style of the module
- Maintain the same level of abstraction
Don't refactor unnecessarily:
- If code works and follows these principles, leave it
- Only refactor if fixing a bug or adding a feature
- Refactoring should improve clarity, not just change style
Documentation Updates
When documentation needs updating:
- Update the canonical source (e.g., example's README.md)
- AGENTS.md should only link, never duplicate
- Keep documentation close to code when possible
- Update CHANGELOG.md for significant changes
Code Review Checklist
Before submitting changes, verify:
- Type safety: No
any, all types are explicit - No duplication: Information lives in one place
- Modularity: Functions/components have single responsibility
- Clean code: JSDoc on public APIs, no inline comments explaining "what"
- Lint & typecheck pass:
pnpm typecheckandpnpm lintsucceed with no errors - Build succeeds:
pnpm buildcompletes without errors - Tested with MCP-B extension: All tools register and work correctly
- Documentation updated: README and inline docs are current
- Follows patterns: Matches existing code style and structure
- Modern API: Uses
@mcp-b/globalor@mcp-b/react-webmcp, NOT deprecated packages
Common Patterns
WebMCP Tool Registration - Vanilla
/**
* Register a tool with WebMCP
* Tool executes pure business logic from lib/ modules
*/
import '@mcp-b/global';
navigator.modelContext.registerTool({
name: 'tool_name',
description: 'Clear description of what this tool does',
inputSchema: {
type: 'object',
properties: {
param: { type: 'string', description: 'Parameter description' }
},
required: ['param']
},
async execute(args) {
// Call pure functions from lib/
const result = await processData(args.param);
return {
content: [{ type: 'text', text: result }]
};
}
});
WebMCP Hook Usage - React
/**
* Register a tool that can be called by the AI
* The tool is automatically unregistered when component unmounts
*/
import { useWebMCP } from '@mcp-b/react-webmcp';
import { z } from 'zod';
function App() {
const [state, setState] = useState<State>(initialState);
useWebMCP({
name: "tool_name",
description: "What the tool does",
inputSchema: {
param: z.string().describe('Parameter description')
},
handler: async ({ param }) => {
// Type-safe params from Zod schema
// Can access and modify React state
setState(prev => updateState(prev, param));
return { success: true, result: 'Done!' };
}
});
return <div>{/* UI */}</div>;
}
React Component Structure
/**
* Task manager component with WebMCP integration
* Manages task state and registers MCP tools for AI interaction
*/
export function TaskManager() {
// State
const [tasks, setTasks] = useState<Task[]>([]);
// Logic (can be extracted to lib/ if complex)
const completedCount = tasks.filter(t => t.completed).length;
// WebMCP integration
useWebMCP({
name: 'add_task',
handler: async ({ title }) => {
const newTask = { id: crypto.randomUUID(), title, completed: false };
setTasks(prev => [...prev, newTask]);
return { success: true, taskId: newTask.id };
}
});
// Render
return <div>{/* UI */}</div>;
}
Error Handling
/**
* Handle tool execution errors gracefully
* Always return valid response even on error
*/
useWebMCP({
name: 'risky_operation',
handler: async (params) => {
try {
const result = await performOperation(params);
return { success: true, data: result };
} catch (error) {
console.error('Tool execution failed:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
});
Type Definitions
/**
* Define types once, import everywhere
* Keep types close to their usage
*/
// src/types/cart.ts
export interface CartItem {
productId: string;
name: string;
price: number;
quantity: number;
}
export type CartAction =
| { type: 'ADD_ITEM'; item: CartItem }
| { type: 'REMOVE_ITEM'; productId: string }
| { type: 'CLEAR_CART' };
// Import in other files
import type { CartItem, CartAction } from './types/cart';
Development Workflow
Setting Up
# Clone the repository
git clone https://github.com/WebMCP-org/examples.git
cd examples
# Navigate to the example you want to work on
cd vanilla # or react
# Install dependencies
pnpm install
# Run development server
pnpm dev
Testing
Manual testing with MCP-B extension is required:
- Install the MCP-B Chrome Extension
- Run your example (
pnpm dev) - Open the extension and verify tools are registered
- Test each tool through the AI interface
- Verify UI updates correctly
- Test error cases (invalid inputs, network errors, etc.)
Pull Request Process
- Fork the repository and create a branch from
main - Make your changes following the guidelines above
- Test thoroughly using the MCP-B extension
- Update documentation if you're changing functionality
- Run quality checks:
pnpm typecheck && pnpm lint && pnpm build - Submit a pull request using the PR template
Example Structure
Each example should follow this structure:
example-name/
├── README.md # What it demonstrates, how to run
├── package.json # Dependencies (@mcp-b/* packages)
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript config (strict mode)
├── index.html # HTML entry point
└── src/
├── main.tsx # Application entry point
├── App.tsx # Main app component
├── types/ # TypeScript type definitions
│ └── index.ts
└── lib/ # Pure utility functions (optional)
└── utils.ts
Resources
Primary Documentation
- README.md - Repository overview
- AGENTS.md - Navigation hub for AI agents
- CHANGELOG.md - Version history
WebMCP Documentation
External Resources
Questions?
If you're unsure about a pattern or approach:
- Check existing examples for similar patterns
- Review the vanilla or react examples
- Check WebMCP documentation
- When in doubt, prioritize clarity and type safety
Remember: These principles exist to make the examples clear, maintainable, and educational. When followed consistently, they help developers learn WebMCP best practices.