メインコンテンツまでスキップ

Model Context Protocol (MCP)

標準の MCP protocol 経由で AI agents が backend tools にアクセスできるように、カスタム MCP servers を作成します。

MCP を使う理由

AI agent が外部 server でホストされている tools を呼び出す必要がある場合、または任意の MCP-compatible client が検出して呼び出せる tools として独自の backend logic を公開したい場合があります。

MCP がない場合、agent-to-tool 接続ごとにカスタム integration logic を構築する必要があります。MCP を使うと、server 上で tools を定義し、互換性のある任意の agent が標準 protocol を通じてそれらを検出して呼び出せます。

Backend code
// 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.`;
}
}

これで、任意の MCP-compatible AI agent が標準 protocol を通じて weather tool を検出して呼び出せるようになります。

概要

MCP (Model Context Protocol) は、AI agents が外部 servers 上の tools を検出して呼び出す方法を標準化する open protocol です。Squid は backend で MCP servers を作成するための組み込みサポートを提供し、agents が JSON-RPC 経由で呼び出せる decorated methods として tools を定義できます。

MCP を使うタイミング

ユースケース推奨事項
backend tools を任意の MCP-compatible agent に公開するMCP server
Squid agent 全体を MCP client に公開するagent を MCP server として公開するを参照
会話中に agent が custom server-side logic を必要とするAI functions を使用
agent が外部 MCP server 上の tools を呼び出す必要があるMCP connector を使用
agent が接続済み service (CRM、API など) にアクセスする必要があるconnected integration を使用

仕組み

  1. SquidService を拡張する service 内の class に @mcpServer を付与します
  2. その class 内の methods に @mcpTool を使って server に tools を追加します
  3. Squid は deploy 時に MCP server を登録し、JSON-RPC endpoint として公開します
  4. MCP-compatible clients は接続し、tools/list で利用可能な tools を検出し、tools/call で呼び出します
  5. 任意で、OAuth を有効にするか、@mcpAuthorizer method を追加して access を制御します

Quick Start

前提条件

  • squid init で初期化された Squid backend project
  • NPM からインストールされた @squidcloud/backend package
  • 作成済みの AI agent(MCP server を Squid agent に接続したい場合)

Step 1: tool を持つ MCP server を作成する

SquidService を拡張する service class を作成し、@mcpServer を付与して tool を追加します。

Backend code
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}!`;
}
}

Step 2: service を登録する

service が service index file から export されていることを確認します。

service/index.ts
export * from './mcp-service';

Step 3: backend を deploy する

local development では、Squid CLI を使用して backend を local で実行します。

squid start

cloud に deploy するには、backend の deploy を参照してください。

Step 4: MCP server を agent に接続する

deploy 後、MCP server を connector として追加し、agent の abilities に attach します。

  1. Squid Console で MCP connector を追加し、deployed MCP server を指定します
  2. Agent Studio で connector を agent の abilities に追加します

これで agent は会話中に MCP tools を検出して呼び出せるようになります。

Core Concepts

@mcpServer decorator

@mcpServer decorator は、SquidService class を MCP server としてマークします。次の fields を持つ configuration object を受け取ります。

FieldTypeRequiredDescription
idstringYesMCP endpoint URL で使用される一意の identifier
namestringYesMCP manifest で公開される server name
descriptionstringYesserver の目的を説明します
versionstringYesMCP manifest で公開される server version
oauthMcpOAuthOptionsNoすべての request で OAuth 2.0 bearer tokens を要求します。OAuth authentication を参照してください。

id は application 内のすべての MCP servers で一意である必要があります。重複する ID は deployment error を引き起こします。

Backend code
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
}

@mcpTool decorator

@mcpTool decorator は、method を MCP clients が検出して呼び出せる tool として公開します。次の fields を持つ configuration object を受け取ります。

FieldTypeRequiredDescription
descriptionstringYestool が何を行い、いつ呼び出すべきかを agent に伝えます
inputSchemaJSONSchemaYestool の input parameters を定義する JSON Schema
outputSchemaJSONSchemaNotool の output format を説明する JSON Schema

method name は MCP manifest 内の tool name になります。各 tool name は server 内で一意である必要があります。

Backend code
@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> {
// Look up the product using your own data layer.
const product = await this.findProduct(sku);
if (!product) {
throw new Error(`Product with SKU ${sku} not found`);
}
return product.stockLevel;
}

Input schema

inputSchemaJSON Schema format に従います。TypeScript では tool method は schema に一致する properties を持つ単一の destructured object を受け取り、Python では method は単一の args: dict を受け取って各 property を key で読み取ります。

Backend code
@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);
}

Output schema

任意の outputSchema は tool の return value の構造を説明し、clients が response format を理解するのに役立ちます。

Backend code
@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 };
}

@mcpAuthorizer decorator

@mcpAuthorizer decorator は、MCP server へのすべての request の前に実行される method を指定します。incoming requests を validate し、unauthorized callers を拒否するために使用します。

authorizer method は McpAuthorizationRequest object を受け取り、boolean(または Promise<boolean>)を返す必要があります。request を許可するには true、"Unauthorized" error で拒否するには false を返します。

Backend code
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}`;
}
}

McpAuthorizationRequest fields

FieldTypeDescription
bodyanyparse 済みの JSON-RPC request body
queryParamsRecord<string, string>request URL からの query parameters
headersRecord<string, string>request からの HTTP headers
authMcpAuthContext | undefined検証済みの auth context。OAuth validation を通過した request の場合のみ存在します

@mcpAuthorizer method が定義されておらず、OAuth が有効でない場合、MCP server へのすべての request が許可されます。

OAuth authentication

Claude やその他の MCP-compatible assistants など、end users が使用する MCP clients では、shared secret token は実用的ではありません。各 user が自分の identity で sign in する必要があります。@mcpServeroauth option を追加すると、すべての request で有効な OAuth 2.0 bearer token が必須になります。

Backend code
import { mcpServer, mcpTool, SquidService } from '@squidcloud/backend';

@mcpServer({
id: 'crm',
name: 'crmServer',
description: 'Provides CRM lookup tools',
version: '1.0.0',
oauth: {
integrationId: 'my-auth0',
},
})
export class CrmMcpService extends SquidService {
// Tools go here
}

McpOAuthOptions には単一の field があります。

FieldTypeRequiredDescription
integrationIdstringYesこの app に登録された auth integration の ID
注記

MCP OAuth は現在 Auth0 auth integrations をサポートしており、TypeScript Backend SDK でのみ利用できます。Python の @mcp_server decorator は oauth option を受け付けません。

oauth が設定されている場合、Squid は OAuth flow の resource-server 側を自動的に処理します。

  1. 有効な bearer token を持たない requests は、server の protected resource metadata を指す WWW-Authenticate challenge を含む HTTP 401 Unauthorized response を受け取ります。
  2. Squid は <mcp-server-url>/.well-known/oauth-protected-resourceOAuth 2.0 Protected Resource Metadata document を公開し、authorization server として Auth0 tenant を listing します。MCP clients はこれを検出し、Auth0 login を自動的に user に案内します。
  3. Squid は参照された integration に対してすべての request の bearer token を検証します。token は Auth0 tenant によって RS256 署名されている必要があり(JWKS によって検証)、issuer は tenant domain、audience は integration の client ID である必要があります。

要求される scopes は openidprofileemailoffline_access です。

oauth が有効で @mcpAuthorizer がない場合、integration からの有効な token を持つすべての request が許可されます。

OAuth と authorizer を組み合わせる

特定の users や domains への access 制限など、token validation の上に独自の rules を適用するには、@mcpAuthorizer を追加します。authorizer は OAuth validation が成功した後に実行され、検証済み context は request.auth で利用できます。

Backend code
import { mcpAuthorizer, McpAuthorizationRequest, mcpServer, mcpTool, SquidService } from '@squidcloud/backend';

@mcpServer({
id: 'crm',
name: 'crmServer',
description: 'Provides CRM lookup tools',
version: '1.0.0',
oauth: {
integrationId: 'my-auth0',
},
})
export class CrmMcpService extends SquidService {
@mcpAuthorizer()
async authorize(request: McpAuthorizationRequest): Promise<boolean> {
const email = request.auth?.claims['email'] as string | undefined;
return !!email && email.endsWith('@my-company.com');
}

// Tools go here
}

McpAuthContext fields:

FieldTypeDescription
type'oauth'この context を生成した auth mechanism
integrationIdstringrequest の検証に使用された auth integration の ID
claimsRecord<string, unknown>検証済み JWT claims (sub, aud, iss, exp, custom claims)

検証済み claims は @mcpAuthorizer 内でのみ利用できます。@mcpTool methods は input arguments のみを受け取ります。tools 内で per-user context を期待するのではなく、authorizer で identity-based access を一元的に適用してください。

Error Handling

Tool errors

tool method が error を throw すると、MCP server はそれを catch し、isError: true を持つ tool response として返します。呼び出し元の agent は error message を受け取り、それを relay するか、次にどう進めるかを判断できます。

agent が有用な feedback を提供できるよう、明確で説明的な errors を throw してください。

Backend code
@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> {
// Look up the order using your own data layer.
const order = await this.findOrder(orderId);
if (!order) {
throw new Error(`Order ${orderId} not found`);
}
if (order.status === 'shipped') {
throw new Error(`Order ${orderId} has already shipped and cannot be cancelled`);
}
await this.updateOrderStatus(orderId, 'cancelled');
return `Order ${orderId} has been cancelled`;
}

Protocol-level errors

MCP server は protocol-level issues に標準の JSON-RPC error codes を使用します。

Error CodeMeaningCause
-32001Unauthorized@mcpAuthorizer method が false を返した
-32601Method not foundrequest された JSON-RPC method または tool name が存在しない
-32000Server errorMCP server ID が見つからなかった、または internal error が発生した

OAuth が有効な場合、bearer token が欠落している、または無効な requests は、JSON-RPC processing の前に WWW-Authenticate challenge を含む HTTP 401 Unauthorized response で拒否されます。

Common issues

IssueCauseSolution
duplicate ID error で deployment が失敗する2 つの @mcpServer classes が同じ id を使用している各 MCP server に一意の id を使用する
duplicate tool name で deployment が失敗する1 つの server 内で 2 つの @mcpTool methods が同じ name を持っているどちらかの method の名前を変更する
tool が agent に呼び出されないtool description が user prompts と一致していないtool が何を行うかを明確に述べるよう description を書き直す
Authorization が常に失敗するtoken または header の check が正しくないMcpAuthorizationRequest fields を log して debug する
OAuth がすべての request で 401 invalid_token を返すtoken issuer または audience が integration と一致していないtoken が integration の Auth0 tenant から来ていることを確認する。audience は integration の client ID である必要があります
OAuth-enabled server がすべての requests を拒否するoauth.integrationId が登録済み Auth0 integration を参照していないapp に Auth0 integration を登録し、その ID を使用する

Best Practices

明確な tool descriptions を書く

description は、agents が tool をいつ呼び出すかを判断する主要な方法です。tool が何を行い、どの情報を返すかを具体的に記述してください。

Good: 'Returns the current stock level for a product. Use when asked about inventory or availability.'

Bad: 'Gets product info'

input schemas を慎重に設計する

  • tool がそれなしでは機能できない場合にのみ、parameters を required としてマークします
  • enum を使用して values を既知の set に制限します
  • agent が提供すべき format を理解できるよう、各 property に明確な description fields を書きます
  • 適切な JSON Schema types (string, number, boolean, array, object) を使用します

MCP servers を secure にする

  • server が sensitive operations を公開する場合は、必ず @mcpAuthorizer を追加します
  • authorization tokens は hardcoded values ではなく stored secrets に対して validate します
  • authentication(誰が呼び出しているか)と authorization(何ができるか)の両方を check します
  • Claude などの user-facing MCP clients では、shared tokens よりも OAuth authentication を優先し、request.auth?.claims を介して authorizer 内で per-user rules を適用します

tool inputs を validate する

input schema は type constraints を提供しますが、edge cases に対応するため、tool methods 内で inputs を validate してください。

Backend code
@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}`;
}

tools を focused に保つ

各 tool は 1 つのことをうまく行うべきです。多くの operations を扱う単一の tool よりも、小さく focused された複数の tools を推奨します。これにより、agent が task に適した tool を選択しやすくなります。

agent を MCP server として公開する

上記はすべて、your backend tools から MCP server を構築するものです。逆の方法も利用できます。任意の Squid agent 自体を、backend code なしで MCP server として公開できます。server は prompt を agent に転送する単一の ask tool を公開するため、IDE assistant などの MCP client が、 instructions、connectors、knowledge bases がすでに attach された agent に問い合わせることができます。

endpoint は標準の MCP Streamable HTTP transport 経由で /mcp/<agentId> に提供されます。

設定には API key が必要なため、backend code から実行します。

Backend code
const agent = this.squid.ai().agent('banking-copilot');

// Let Squid draft the descriptions from the agent's instructions and connected resources.
// This returns them without saving, so you can review or edit them first.
const generated = await agent.generateMcpDescriptions();

await agent.updateMcpServer({
enabled: true,
description: generated.description,
toolDescription: generated.toolDescription,
requireApiKey: true,
});

Configuration options

FieldDescription
enabledagent が MCP server として公開されるかどうか。
descriptionMCP initialize result の instructions field として提供されます。デフォルトは agent description。
toolDescriptiontools manifest 内の ask tool の description。デフォルトは generic description。
oauthIntegrationIdauth connector の bearer tokens を使用して endpoint を OAuth で保護します。
requireApiKeyagent 自身の API key を bearer token として要求します。

endpoint を保護する

oauthIntegrationIdrequireApiKey も設定されていない場合、MCP endpoint は public です。agent ID を 知っている誰でも呼び出すことができ、すべての call が AI quota を消費します。agent を意図的に open にする場合を除き、 必ず少なくともどちらか一方を設定してください。

requireApiKey を使用する場合、callers は agent の API key を Authorization: Bearer <key> または x-squid-agent-api-key header で提示します。これは application API key ではなく agent-scoped key であるため、 その 1 つの agent への access のみを許可し、それ以外には何も許可しません。backend code から取得または rotate します。

Backend code
const agent = this.squid.ai().agent('banking-copilot');

const key = await agent.getApiKey();
const rotated = await agent.regenerateApiKey();

oauthIntegrationIdrequireApiKey の両方を設定すると、どちらか一方 の credential が受け入れられます。これにより、 interactive clients は OAuth で sign in し、automated clients は agent key を提示できます。

Next Steps