Model Context Protocol
MCP tools for AI agent integration
Model Context Protocol (MCP)
Model Context Protocol (MCP) is a standard interface for exposing tools, prompts, and resources to AI agents. Every product can expose MCP tools that allow AI agents like Claude to directly invoke product functionality.
What is MCP?
MCP enables AI agents to:
- Use tools - Invoke functions and APIs
- Access resources - Read files, databases, web content
- Execute prompts - Pre-configured prompt templates
- Maintain context - Persistent sessions across interactions
MCP vs REST API
| Aspect | REST API | MCP |
|---|---|---|
| Consumer | Developers | AI agents |
| Interface | HTTP endpoints | Tool definitions |
| Documentation | API reference | Schema + descriptions |
| Auth | API keys, OAuth | API keys, tokens |
| Discovery | Manual docs reading | Auto-discovery via schemas |
| Integration | Code integration | Agent can use directly |
MCP Architecture
MCP Server
import { Server } from '@modelcontextprotocol/sdk/server'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
// Create MCP server
const server = new Server(
{
name: 'acme-product',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
}
)
// Start server
const transport = new StdioServerTransport()
await server.connect(transport)Tool Definition
interface MCPTool {
name: string // Tool identifier (snake_case)
description: string // What the tool does
inputSchema: { // JSON Schema for inputs
type: 'object'
properties: Record<string, any>
required?: string[]
}
}Tool Handler
server.setRequestHandler(
ListToolsRequestSchema,
async () => ({
tools: [
{
name: 'enrich_company',
description: 'Enrich company data from domain',
inputSchema: {
type: 'object',
properties: {
domain: {
type: 'string',
description: 'Company domain (e.g., acme.com)'
}
},
required: ['domain']
}
}
]
})
)
server.setRequestHandler(
CallToolRequestSchema,
async (request) => {
if (request.params.name === 'enrich_company') {
const { domain } = request.params.arguments
const data = await enrichCompany(domain)
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2)
}
]
}
}
throw new Error('Tool not found')
}
)Product MCP Integration
Auto-Generate from Product Schema
Products automatically expose MCP tools based on their API:
// Product definition
interface Product {
id: string
name: string
type: 'SaaS' | 'API' | 'Data' | 'Headless'
endpoints: APIEndpoint[]
}
// Auto-generated MCP tools
function generateMCPTools(product: Product): MCPTool[] {
return product.endpoints.map(endpoint => ({
name: `${product.id}_${endpoint.operation}`,
description: endpoint.description,
inputSchema: endpoint.inputSchema
}))
}Example: Data Product MCP
// Company data product
const companyDataTools: MCPTool[] = [
{
name: 'search_companies',
description: 'Search for companies by industry, size, location',
inputSchema: {
type: 'object',
properties: {
industry: { type: 'string', description: 'Industry filter' },
minEmployees: { type: 'number', description: 'Minimum employees' },
maxEmployees: { type: 'number', description: 'Maximum employees' },
location: { type: 'string', description: 'Country or region' },
limit: { type: 'number', description: 'Max results (default 10)' }
}
}
},
{
name: 'enrich_company',
description: 'Get detailed company information from domain',
inputSchema: {
type: 'object',
properties: {
domain: { type: 'string', description: 'Company domain' }
},
required: ['domain']
}
},
{
name: 'find_contacts',
description: 'Find employee contacts at a company',
inputSchema: {
type: 'object',
properties: {
company: { type: 'string', description: 'Company name or domain' },
role: { type: 'string', description: 'Job title or department' },
limit: { type: 'number', description: 'Max results' }
},
required: ['company']
}
}
]MCP Tool Patterns
CRUD Operations
// Create
{
name: 'create_product',
description: 'Create a new product',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string' },
type: { enum: ['SaaS', 'API', 'Data', 'Headless'] },
business: { type: 'string' }
},
required: ['name', 'type', 'business']
}
}
// Read
{
name: 'get_product',
description: 'Retrieve product by ID',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string' }
},
required: ['id']
}
}
// Update
{
name: 'update_product',
description: 'Update product fields',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string' },
updates: {
type: 'object',
properties: {
name: { type: 'string' },
description: { type: 'string' },
isActive: { type: 'boolean' }
}
}
},
required: ['id', 'updates']
}
}
// Delete
{
name: 'delete_product',
description: 'Delete a product',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string' }
},
required: ['id']
}
}Search and Query
{
name: 'search_products',
description: 'Search and filter products',
inputSchema: {
type: 'object',
properties: {
q: { type: 'string', description: 'Search query' },
type: { enum: ['SaaS', 'API', 'Data', 'Headless'] },
isActive: { type: 'boolean' },
limit: { type: 'number', default: 20 },
offset: { type: 'number', default: 0 }
}
}
}Complex Operations
{
name: 'analyze_product_performance',
description: 'Analyze product metrics and performance',
inputSchema: {
type: 'object',
properties: {
productId: { type: 'string' },
startDate: { type: 'string', format: 'date' },
endDate: { type: 'string', format: 'date' },
metrics: {
type: 'array',
items: { enum: ['revenue', 'users', 'api_calls', 'churn'] }
}
},
required: ['productId']
}
}MCP Response Format
Text Response
{
content: [
{
type: 'text',
text: 'Product created successfully with ID: prod_123'
}
]
}JSON Data Response
{
content: [
{
type: 'text',
text: JSON.stringify({
id: 'prod_123',
name: 'Acme API',
type: 'API',
isActive: true
}, null, 2)
}
]
}Image Response
{
content: [
{
type: 'image',
data: base64EncodedImage,
mimeType: 'image/png'
}
]
}Resource Response
{
content: [
{
type: 'resource',
uri: 'file:///path/to/file.json',
mimeType: 'application/json'
}
]
}Claude Desktop Integration
Configuration
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"acme-products": {
"command": "npx",
"args": ["-y", "@acme/mcp-server"],
"env": {
"ACME_API_KEY": "sk_live_..."
}
}
}
}Usage in Claude
Human: Search for SaaS products in the CRM category
Claude: I'll search for SaaS products in the CRM category using the search_products tool.
[Uses search_products tool with parameters: { type: "SaaS", category: "CRM" }]
I found 15 SaaS products in the CRM category:
1. Acme CRM - Complete customer relationship management
2. SalesPro - Sales automation and pipeline management
3. ContactHub - Contact management and enrichment
...MCP Resources
Resources provide access to data and files:
server.setRequestHandler(
ListResourcesRequestSchema,
async () => ({
resources: [
{
uri: 'acme://products/all',
name: 'All Products',
description: 'Complete product catalog',
mimeType: 'application/json'
},
{
uri: 'acme://docs/api-reference',
name: 'API Reference',
description: 'Complete API documentation',
mimeType: 'text/markdown'
}
]
})
)
server.setRequestHandler(
ReadResourceRequestSchema,
async (request) => {
const uri = request.params.uri
if (uri === 'acme://products/all') {
const products = await fetchAllProducts()
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(products, null, 2)
}
]
}
}
throw new Error('Resource not found')
}
)MCP Prompts
Pre-configured prompt templates:
server.setRequestHandler(
ListPromptsRequestSchema,
async () => ({
prompts: [
{
name: 'analyze_product',
description: 'Analyze product performance and metrics',
arguments: [
{
name: 'productId',
description: 'Product ID to analyze',
required: true
}
]
}
]
})
)
server.setRequestHandler(
GetPromptRequestSchema,
async (request) => {
if (request.params.name === 'analyze_product') {
const { productId } = request.params.arguments
const product = await fetchProduct(productId)
const metrics = await fetchMetrics(productId)
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Analyze the performance of ${product.name}:
Product Type: ${product.type}
Active Users: ${metrics.activeUsers}
Revenue (30d): $${metrics.revenue}
Growth Rate: ${metrics.growthRate}%
Churn Rate: ${metrics.churnRate}%
Provide insights on:
1. Overall performance
2. Growth trends
3. Areas of concern
4. Recommendations`
}
}
]
}
}
throw new Error('Prompt not found')
}
)Security & Authentication
API Key in Environment
{
"mcpServers": {
"acme": {
"command": "npx",
"args": ["-y", "@acme/mcp-server"],
"env": {
"ACME_API_KEY": "sk_live_123...",
"ACME_BASE_URL": "https://api.acme.com"
}
}
}
}OAuth Flow
// MCP server with OAuth
const server = new Server({ name: 'acme-oauth' })
// Check for valid token
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const token = process.env.ACME_ACCESS_TOKEN
if (!token) {
return {
content: [
{
type: 'text',
text: 'Please authenticate: Run `acme-cli login` to get access token'
}
],
isError: true
}
}
// Proceed with authenticated request
const response = await fetch('https://api.acme.com/v1/...', {
headers: { Authorization: `Bearer ${token}` }
})
// ...
})Scoped Permissions
interface MCPPermissions {
tools: string[] // Allowed tool names
resources: string[] // Allowed resource URIs
readOnly: boolean // No mutations allowed
}
// Validate permissions before execution
function checkPermission(
tool: string,
permissions: MCPPermissions
): boolean {
return permissions.tools.includes(tool)
}Best Practices
Tool Naming
// Good: Descriptive, snake_case
'search_companies'
'enrich_email'
'create_product'
// Bad: Unclear, camelCase, generic
'search'
'getCompanies'
'doThing'Descriptions
// Good: Clear, specific, includes examples
{
name: 'search_companies',
description: 'Search for companies by industry, size, and location. Returns up to 100 companies matching the criteria. Example: Search for SaaS companies with 50-200 employees in the US.',
// ...
}
// Bad: Vague, no context
{
name: 'search_companies',
description: 'Search companies',
// ...
}Error Handling
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const result = await executeTool(request.params)
return {
content: [{ type: 'text', text: JSON.stringify(result) }]
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}\n\nPlease check your input and try again.`
}
],
isError: true
}
}
})Input Validation
import { z } from 'zod'
const EnrichCompanyInput = z.object({
domain: z.string().regex(/^[a-z0-9-]+\.[a-z]{2,}$/),
})
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'enrich_company') {
// Validate input
const validated = EnrichCompanyInput.parse(request.params.arguments)
// Proceed with validated data
const result = await enrichCompany(validated.domain)
// ...
}
})Examples
SaaS Product MCP
// Expose SaaS product features as MCP tools
const saasTools = [
{
name: 'create_workspace',
description: 'Create a new workspace for team collaboration',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string' },
plan: { enum: ['free', 'pro', 'enterprise'] }
},
required: ['name']
}
},
{
name: 'invite_team_member',
description: 'Invite a user to join workspace',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
email: { type: 'string' },
role: { enum: ['member', 'admin'] }
},
required: ['workspace', 'email']
}
}
]Data Product MCP
// Expose data product as MCP tools
const dataTools = [
{
name: 'lookup_company',
description: 'Get company data by domain',
inputSchema: {
type: 'object',
properties: {
domain: { type: 'string' }
},
required: ['domain']
}
},
{
name: 'search_companies',
description: 'Search companies with filters',
inputSchema: {
type: 'object',
properties: {
industry: { type: 'string' },
employees: { type: 'number' },
revenue: { type: 'number' }
}
}
}
]Next Steps
- Headless Products - API-only products for agents
- REST APIs - REST API documentation
- SDKs - Client libraries wrapping APIs and MCP