Model Context Protocol (MCP)
AI エージェントが標準の MCP protocol を通じてバックエンドツールにアクセスできるように、カスタム MCP server を作成します。
なぜ MCP を使うのか
AI エージェントが外部サーバー上でホストされているツールを呼び出す必要がある場合、または独自のバックエンドロジックを、任意の MCP-compatible client が検出して呼び出せるツールとして公開したい場合に利用します。
MCP がない場合、agent-to-tool 接続ごとにカスタム統合ロジックを構築する必要があります。MCP を使えば、サーバー上でツールを定義するだけで、互換性のある任意のエージェントが標準 protocol を通じてそれらを検出し、呼び出せます。
- TypeScript
- Python
// Backend: define an MCP server with a tool
@mcpServer({
name: 'weather',
id: 'weather',
description: 'Provides weather data',
version: '1.0.0',
})
export class WeatherMcpService extends SquidService {
@mcpTool({
description: 'Returns the current weather for a city',
inputSchema: {
type: 'object',
properties: {
city: { type: 'string', description: 'City name' },
},
required: ['city'],
},
})
async getWeather({ city }: { city: string }): Promise<string> {
return `The weather in ${city} is sunny, 25°C.`;
}
}
# Backend: define an MCP server with a tool
@mcp_server({
'name': 'weather',
'id': 'weather',
'description': 'Provides weather data',
'version': '1.0.0',
})
class WeatherMcpService(SquidService):
@mcp_tool({
'description': 'Returns the current weather for a city',
'inputSchema': {
'type': 'object',
'properties': {
'city': {'type': 'string', 'description': 'City name'},
},
'required': ['city'],
},
})
async def get_weather(self, args: dict) -> str:
return f"The weather in {args['city']} is sunny, 25°C."
これで、任意の MCP-compatible AI agent が標準 protocol を通じて weather tool を検出し、呼び出せるようになります。
概要
MCP (Model Context Protocol) は、AI agent が外部サーバー上のツールをどのように検出し、呼び出すかを標準化するオープン protocol です。Squid は、バックエンド内で MCP server を作成するための組み込みサポートを提供しており、デコレートされたメソッドとしてツールを定義し、JSON-RPC 経由で agent が呼び出せるようにします。
MCP を使うべき場面
| Use Case | Recommendation |
|---|---|
| 任意の MCP-compatible agent にバックエンドツールを公開する | MCP server |
| 会話中に agent がカスタムの server-side logic を必要とする | AI functions を使う |
| agent が外部の MCP server 上のツールを呼び出す必要がある | MCP connector を使う |
| agent が接続済みサービス(CRM、API など)にアクセスする | connected integration を使う |
仕組み
SquidServiceを継承した service のクラスに@mcpServerを付与します- そのクラス内のメソッドに
@mcpToolを付与して、server に tool を追加します - Squid は deploy 時に MCP server を登録し、JSON-RPC endpoint として公開します
- MCP-compatible client は接続後、
tools/listで利用可能な tool を検出し、tools/callで呼び出します - 必要に応じて、アクセス制御のために
@mcpAuthorizerメソッドを追加します
Quick Start
前提条件
- TypeScript
- Python
Step 1: tool を持つ MCP server を作成する
SquidService を継承した service class を作成し、@mcpServer を付与して、tool を追加します。
- TypeScript
- Python
import { mcpServer, mcpTool, SquidService } from '@squidcloud/backend';
@mcpServer({
name: 'greetingServer',
id: 'greetingServer',
description: 'A simple MCP server that greets users',
version: '1.0.0',
})
export class McpService extends SquidService {
@mcpTool({
description: 'Returns a greeting for the given name',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'The name to greet' },
},
required: ['name'],
},
})
async greet({ name }: { name: string }): Promise<string> {
return `Hello, ${name}!`;
}
}
from squidcloud_backend import SquidService, mcp_server, mcp_tool
@mcp_server({
'name': 'greetingServer',
'id': 'greetingServer',
'description': 'A simple MCP server that greets users',
'version': '1.0.0',
})
class McpService(SquidService):
@mcp_tool({
'description': 'Returns a greeting for the given name',
'inputSchema': {
'type': 'object',
'properties': {
'name': {'type': 'string', 'description': 'The name to greet'},
},
'required': ['name'],
},
})
async def greet(self, args: dict) -> str:
return f"Hello, {args['name']}!"
Python の tool メソッドは、TypeScript のような destructured object ではなく、inputSchema の properties に一致する単一の args: dict を受け取ります。
Step 2: service を登録する
- TypeScript
- Python
service index file から service が export されていることを確認してください。
export * from './mcp-service';
個別の登録手順は不要です。Python では、module が import されると各 @mcp_server class が自動的に登録されるため、その class は src/main.py 内に置くか、そこから import されていれば十分です。
Step 3: backend を deploy する
ローカル開発では、Squid CLI を使用して backend をローカルで実行します。
squid start
クラウドへ deploy する方法については、deploying your backend を参照してください。
Step 4: MCP server を agent に接続する
deploy 後、MCP server を connector として追加し、agent の abilities にアタッチします。
- Squid Console で MCP connector を追加し、deploy 済みの MCP server を指定します
- Agent Studio でその connector を agent の abilities に追加します
これで agent は会話中に MCP tools を検出し、呼び出せるようになります。
Core Concepts
@mcpServer decorator
@mcpServer decorator は、SquidService class を MCP server としてマークします。以下のフィールドを持つ設定 object を受け取ります。
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | MCP endpoint URL で使用される一意の識別子 |
name | string | Yes | MCP manifest に公開される server 名 |
description | string | Yes | server の目的を説明します |
version | string | Yes | MCP manifest に公開される server version |
各 id は、アプリケーション内のすべての MCP server 間で一意である必要があります。ID が重複すると deploy error が発生します。
- TypeScript
- Python
import { mcpServer, SquidService } from '@squidcloud/backend';
@mcpServer({
id: 'inventory',
name: 'inventoryServer',
description: 'Provides product inventory lookup and management tools',
version: '1.0.0',
})
export class InventoryMcpService extends SquidService {
// Tools go here
}
from squidcloud_backend import SquidService, mcp_server
@mcp_server({
'id': 'inventory',
'name': 'inventoryServer',
'description': 'Provides product inventory lookup and management tools',
'version': '1.0.0',
})
class InventoryMcpService(SquidService):
pass # Tools go here
@mcpTool decorator
@mcpTool decorator は、MCP client が検出して呼び出せる tool としてメソッドを公開します。以下のフィールドを持つ設定 object を受け取ります。
| Field | Type | Required | Description |
|---|---|---|---|
description | string | Yes | tool が何をし、いつ呼び出すべきかを agent に伝えます |
inputSchema | JSONSchema | Yes | tool の入力パラメータを定義する JSON Schema |
outputSchema | JSONSchema | No | tool の出力形式を説明する JSON Schema |
メソッド名は MCP manifest 内での tool 名になります。各 tool 名は 1 つの server 内で一意である必要があります。
- TypeScript
- Python
@mcpTool({
description: 'Looks up the current stock level for a product by SKU',
inputSchema: {
type: 'object',
properties: {
sku: {
type: 'string',
description: 'The product SKU code',
},
},
required: ['sku'],
},
})
async getStockLevel({ sku }: { sku: string }): Promise<number> {
const product = await this.squid.collection('products').doc(sku).snapshot();
if (!product) {
throw new Error(`Product with SKU ${sku} not found`);
}
return product.data.stockLevel;
}
@mcp_tool({
'description': 'Looks up the current stock level for a product by SKU',
'inputSchema': {
'type': 'object',
'properties': {
'sku': {
'type': 'string',
'description': 'The product SKU code',
},
},
'required': ['sku'],
},
})
async def get_stock_level(self, args: dict) -> int:
sku = args['sku']
product = await self.squid.collection('products').doc(sku).snapshot()
if not product:
raise RuntimeError(f"Product with SKU {sku} not found")
return product['stockLevel']
Input schema
inputSchema は JSON Schema 形式に従います。TypeScript では、tool メソッドは schema に一致する properties を持つ単一の destructured object を受け取ります。Python では、メソッドは単一の args: dict を受け取り、各 property を key で読み取ります。
- TypeScript
- Python
@mcpTool({
description: 'Searches products by category and price range',
inputSchema: {
type: 'object',
properties: {
category: {
type: 'string',
description: 'Product category to search',
enum: ['electronics', 'clothing', 'home', 'sports'],
},
maxPrice: {
type: 'number',
description: 'Maximum price in USD',
},
inStockOnly: {
type: 'boolean',
description: 'If true, only return items currently in stock',
},
},
required: ['category'],
},
})
async searchProducts({
category,
maxPrice,
inStockOnly,
}: {
category: string;
maxPrice?: number;
inStockOnly?: boolean;
}): Promise<string> {
// Query logic here
return JSON.stringify(results);
}
@mcp_tool({
'description': 'Searches products by category and price range',
'inputSchema': {
'type': 'object',
'properties': {
'category': {
'type': 'string',
'description': 'Product category to search',
'enum': ['electronics', 'clothing', 'home', 'sports'],
},
'maxPrice': {
'type': 'number',
'description': 'Maximum price in USD',
},
'inStockOnly': {
'type': 'boolean',
'description': 'If true, only return items currently in stock',
},
},
'required': ['category'],
},
})
async def search_products(self, args: dict) -> str:
category = args['category']
max_price = args.get('maxPrice')
in_stock_only = args.get('inStockOnly')
# Query logic here
return json.dumps(results)
Output schema
省略可能な outputSchema は、tool の戻り値の構造を記述し、client がレスポンス形式を理解するのに役立ちます。
- TypeScript
- Python
@mcpTool({
description: 'Returns product details for a given SKU',
inputSchema: {
type: 'object',
properties: {
sku: { type: 'string', description: 'Product SKU' },
},
required: ['sku'],
},
outputSchema: {
type: 'object',
properties: {
name: { type: 'string' },
price: { type: 'number' },
inStock: { type: 'boolean' },
},
},
})
async getProduct({ sku }: { sku: string }) {
return { name: 'Widget', price: 9.99, inStock: true };
}
@mcp_tool({
'description': 'Returns product details for a given SKU',
'inputSchema': {
'type': 'object',
'properties': {
'sku': {'type': 'string', 'description': 'Product SKU'},
},
'required': ['sku'],
},
'outputSchema': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'price': {'type': 'number'},
'inStock': {'type': 'boolean'},
},
},
})
async def get_product(self, args: dict) -> dict:
return {'name': 'Widget', 'price': 9.99, 'inStock': True}
@mcpAuthorizer decorator
@mcpAuthorizer decorator は、MCP server へのすべてのリクエストの前に実行されるメソッドを指定します。受信リクエストを検証し、認可されていない呼び出し元を拒否するために使用します。
authorizer メソッドは McpAuthorizationRequest object を受け取り、boolean(または Promise<boolean>)を返す必要があります。true を返すとリクエストを許可し、false を返すと "Unauthorized" error で拒否します。
- TypeScript
- Python
import { mcpAuthorizer, McpAuthorizationRequest, mcpServer, mcpTool, SquidService } from '@squidcloud/backend';
@mcpServer({
name: 'secureMcp',
id: 'secureMcp',
description: 'An MCP server with authorization',
version: '1.0.0',
})
export class SecureMcpService extends SquidService {
@mcpAuthorizer()
async authorize(request: McpAuthorizationRequest): Promise<boolean> {
const token = request.headers['authorization'];
return token === `Bearer ${this.secrets['MCP_AUTH_TOKEN']}`;
}
@mcpTool({
description: 'Returns sensitive data',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'The data query' },
},
required: ['query'],
},
})
async getSensitiveData({ query }: { query: string }): Promise<string> {
// This tool is only accessible if the authorizer returns true
return `Results for: ${query}`;
}
}
from squidcloud_backend import (
McpAuthorizationRequest,
SquidService,
mcp_authorizer,
mcp_server,
mcp_tool,
)
@mcp_server({
'name': 'secureMcp',
'id': 'secureMcp',
'description': 'An MCP server with authorization',
'version': '1.0.0',
})
class SecureMcpService(SquidService):
@mcp_authorizer()
async def authorize(self, request: McpAuthorizationRequest) -> bool:
headers = request.get('headers') or {}
token = headers.get('authorization')
return token == f"Bearer {self.secrets['MCP_AUTH_TOKEN']}"
@mcp_tool({
'description': 'Returns sensitive data',
'inputSchema': {
'type': 'object',
'properties': {
'query': {'type': 'string', 'description': 'The data query'},
},
'required': ['query'],
},
})
async def get_sensitive_data(self, args: dict) -> str:
# This tool is only accessible if the authorizer returns True
return f"Results for: {args['query']}"
McpAuthorizationRequest fields
| Field | Type | Description |
|---|---|---|
body | any | パース済みの JSON-RPC request body |
queryParams | Record<string, string> | request URL からの query parameter |
headers | Record<string, string> | request の HTTP header |
@mcpAuthorizer メソッドが定義されていない場合、MCP server へのすべてのリクエストが許可されます。
Error Handling
Tool errors
tool メソッドが error を throw すると、MCP server はそれをキャッチし、isError: true を伴う tool response として返します。呼び出し元の agent は error message を受け取り、それを伝えたり、どう進めるかを判断したりできます。
agent が有用なフィードバックを提供できるよう、明確で説明的な error を throw してください。
- TypeScript
- Python
@mcpTool({
description: 'Cancels an order by ID',
inputSchema: {
type: 'object',
properties: {
orderId: { type: 'string', description: 'The order ID to cancel' },
},
required: ['orderId'],
},
})
async cancelOrder({ orderId }: { orderId: string }): Promise<string> {
const order = await this.squid.collection('orders').doc(orderId).snapshot();
if (!order) {
throw new Error(`Order ${orderId} not found`);
}
if (order.data.status === 'shipped') {
throw new Error(`Order ${orderId} has already shipped and cannot be cancelled`);
}
await this.squid.collection('orders').doc(orderId).update({ status: 'cancelled' });
return `Order ${orderId} has been cancelled`;
}
@mcp_tool({
'description': 'Cancels an order by ID',
'inputSchema': {
'type': 'object',
'properties': {
'orderId': {'type': 'string', 'description': 'The order ID to cancel'},
},
'required': ['orderId'],
},
})
async def cancel_order(self, args: dict) -> str:
order_id = args['orderId']
order = await self.squid.collection('orders').doc(order_id).snapshot()
if not order:
raise RuntimeError(f"Order {order_id} not found")
if order.get('status') == 'shipped':
raise RuntimeError(f"Order {order_id} has already shipped and cannot be cancelled")
await self.squid.collection('orders').doc(order_id).update({'status': 'cancelled'})
return f"Order {order_id} has been cancelled"
Protocol-level errors
MCP server は、protocol-level の問題に対して標準の JSON-RPC error code を使用します。
| Error Code | Meaning | Cause |
|---|---|---|
-32001 | Unauthorized | @mcpAuthorizer メソッドが false を返した |
-32601 | Method not found | 要求された JSON-RPC method または tool 名が存在しない |
-32000 | Server error | MCP server ID が見つからなかった、または内部 error が発生した |
Common issues
| Issue | Cause | Solution |
|---|---|---|
| ID 重複 error で deploy に失敗する | 2 つの @mcpServer class が同じ id を使っている | 各 MCP server に一意の id を使用する |
| tool 名の重複で deploy に失敗する | 1 つの server 内の 2 つの @mcpTool メソッドが同名 | どちらか一方のメソッド名を変更する |
| tool が agent から一度も呼び出されない | tool description が user prompt と一致していない | tool が何をするかを明確に示す description に書き換える |
| authorization が常に失敗する | token または header のチェックが正しくない | McpAuthorizationRequest の fields をログ出力して debug する |
Best Practices
明確な tool description を書く
description は、agent がいつ tool を呼び出すべきかを判断するための主要な手段です。tool が何をし、どのような情報を返すのかを具体的に書いてください。
Good: 'Returns the current stock level for a product. Use when asked about inventory or availability.'
Bad: 'Gets product info'
input schema を慎重に設計する
- tool がそれなしでは機能しない場合にのみ、パラメータを
requiredにする - 値を既知の集合に制限するには
enumを使う - agent がどの形式を提供すべきか分かるよう、各 property に明確な
descriptionを書く - 適切な JSON Schema type(
string,number,boolean,array,object)を使う
MCP server を安全に保つ
- server が機密性の高い操作を公開する場合は、必ず
@mcpAuthorizerを追加する - authorization token はハードコードした値ではなく、保存された secret と照合して検証する
- authentication(誰が呼び出しているか)と authorization(何を実行できるか)の両方を確認する
tool input を検証する
input schema は型制約を提供しますが、エッジケースに対応するために tool メソッド内でも入力を検証してください。
- TypeScript
- Python
@mcpTool({
description: 'Transfers funds between accounts',
inputSchema: {
type: 'object',
properties: {
fromAccount: { type: 'string', description: 'Source account ID' },
toAccount: { type: 'string', description: 'Destination account ID' },
amount: { type: 'number', description: 'Amount to transfer in USD' },
},
required: ['fromAccount', 'toAccount', 'amount'],
},
})
async transferFunds({
fromAccount,
toAccount,
amount,
}: {
fromAccount: string;
toAccount: string;
amount: number;
}): Promise<string> {
if (amount <= 0) {
throw new Error('Transfer amount must be positive');
}
if (fromAccount === toAccount) {
throw new Error('Source and destination accounts must be different');
}
// Process transfer...
return `Transferred $${amount} from ${fromAccount} to ${toAccount}`;
}
@mcp_tool({
'description': 'Transfers funds between accounts',
'inputSchema': {
'type': 'object',
'properties': {
'fromAccount': {'type': 'string', 'description': 'Source account ID'},
'toAccount': {'type': 'string', 'description': 'Destination account ID'},
'amount': {'type': 'number', 'description': 'Amount to transfer in USD'},
},
'required': ['fromAccount', 'toAccount', 'amount'],
},
})
async def transfer_funds(self, args: dict) -> str:
from_account = args['fromAccount']
to_account = args['toAccount']
amount = args['amount']
if amount <= 0:
raise RuntimeError('Transfer amount must be positive')
if from_account == to_account:
raise RuntimeError('Source and destination accounts must be different')
# Process transfer...
return f"Transferred ${amount} from {from_account} to {to_account}"
tool は焦点を絞って保つ
各 tool は 1 つのことをうまく行うべきです。多くの操作を処理する 1 つの tool よりも、小さく焦点の絞られた複数の tool を優先してください。これにより、agent がタスクに適した tool を選びやすくなります。
Next Steps
- MCP connectors - Squid Console を通じて agent を MCP server に接続する
- Abilities - MCP connector やその他の tool を agent にアタッチする
- AI functions - バックエンドロジックを agent に公開する別の方法
- AI agent documentation - AI agent を構築して設定する