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

Model Context Protocol (MCP)

カスタム MCP server を作成し、AI agent が標準 MCP protocol 経由でバックエンドツールにアクセスできるようにします。​

MCP を使用する理由​

AI agent が外部 server でホストされているツールを呼び出す必要がある場合、または MCP-compatible client が検出して呼び出せるツールとして独自のバックエンドロジックを公開したい場合に使用します。

MCP がなければ、agent とツールの接続ごとにカスタム integration ロジックを構築する必要があります。MCP を使用すると、server 上でツールを定義でき、互換性のある 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 ツールを検出・呼び出しできるようになります。

概要​

MCP (Model Context Protocol) は、AI agent が外部 server 上のツールを検出・呼び出す方法を標準化する open protocol です。Squid はバックエンドでの MCP server 作成を組み込みでサポートしており、agent が JSON-RPC 経由で呼び出せる decorated method としてツールを定義できます。

MCP を使用する場合​

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

仕組み​

  1. SquidService を拡張する service 内で、class を @mcpServer で decorate します
  2. その class 内の method に @mcpTool を使用して server にツールを追加します
  3. Squid は deploy 時に MCP server を登録し、JSON-RPC endpoint として公開します
  4. MCP-compatible client は接続して tools/list 経由で利用可能なツールを検出し、tools/call 経由で呼び出します
  5. 任意で、アクセスを制御するために OAuth を有効化するか、@mcpAuthorizer method を追加します

クイックスタート​

前提条件​

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

ステップ 1: ツールを持つ MCP server を作成する​

SquidService を拡張する service class を作成し、@mcpServer で decorate して、ツールを追加します。

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

ステップ 2: service を登録する​

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

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

ステップ 3: backend を deploy する​

ローカル開発では、Squid CLI を使用して backend をローカルで実行します。

squid start

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

ステップ 4: MCP server を agent に接続する​

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

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

これで agent は会話中に MCP ツールを検出・呼び出しできるようになります。

コアコンセプト​

@mcpServer decorator​

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

FieldType必須説明
idstringはいMCP endpoint URL で使用される一意の identifier
namestringはいMCP manifest で公開される server 名
descriptionstringはいserver の目的を説明します
versionstringはいMCP manifest で公開される server version
oauthMcpOAuthOptionsいいえすべての request で OAuth 2.0 bearer token を要求します。OAuth authenticationを参照してください。

各 id は、application 内のすべての MCP server で一意である必要があります。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 は、MCP client が検出・呼び出しできるツールとして method を公開します。以下の field を持つ configuration object を受け取ります。

FieldType必須説明
descriptionstringはいツールの機能と呼び出すタイミングを agent に伝えます
inputSchemaJSONSchemaはいツールの input parameter を定義する JSON Schema
outputSchemaJSONSchemaいいえツールの output format を説明する JSON Schema

method 名が MCP manifest 内のツール名になります。各ツール名は 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​

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

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 はツールの戻り値の構造を説明し、client が 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 request を検証し、権限のない caller を拒否します。

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 field​

FieldType説明
bodyanyparse 済み JSON-RPC request body
queryParamsRecord<string, string>request URL の query parameter
headersRecord<string, string>request の HTTP header
authMcpAuthContext | undefined検証済み auth context。OAuth validationを通過した request にのみ存在します

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

OAuth authentication​

Claude やその他の MCP-compatible assistant など、エンドユーザーが使用する MCP client では共有 secret token は実用的ではありません。ユーザーごとに自身の identity で sign in する必要があるためです。@mcpServer に oauth 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 には 1 つの field があります。

FieldType必須説明
integrationIdstringはいこの app に登録されている auth integration の ID
注記

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

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

  1. 有効な bearer token のない request には、server の protected resource metadata を指す WWW-Authenticate challenge を含む HTTP 401 Unauthorized response が返されます。
  2. Squid は <mcp-server-url>/.well-known/oauth-protected-resource で OAuth 2.0 Protected Resource Metadata document を公開し、authorization server として Auth0 tenant を一覧表示します。MCP client はこれを検出し、ユーザーを Auth0 login へ自動的に案内します。
  3. Squid は、参照される integration に対してすべての request の bearer token を検証します。token は Auth0 tenant によって RS256-signed されている必要があり(その JWKS を介して検証)、tenant domain を issuer、integration の client ID を audience とする必要があります。

要求される scope は、openid、profile、email、および offline_access です。

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

OAuth と authorizer の組み合わせ​

特定のユーザーや domain にアクセスを制限するなど、token validation に加えて独自ルールを適用するには、@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 field:

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

検証済み claims は @mcpAuthorizer 内でのみ使用できます。@mcpTool method は input argument のみを受け取ります。ツール内で user ごとの context を想定するのではなく、authorizer で identity-based access を一元的に強制してください。

Error Handling​

ツールエラー​

ツール method が error を throw すると、MCP server はそれを catch し、isError: true を持つツール response として返します。呼び出し元の agent は error message を受け取り、それを伝達するか、どのように続行するかを判断できます。

agent が有用な feedback を提供できるよう、明確で説明的な error を 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 error​

MCP server は protocol-level の問題に対して標準 JSON-RPC error code を使用します。

Error Code意味原因
-32001Unauthorized@mcpAuthorizer method が false を返しました
-32601Method not found要求された JSON-RPC method またはツール名が存在しません
-32000Server errorMCP server ID が見つからなかったか、internal error が発生しました

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

よくある問題​

問題原因解決策
deployment が ID 重複 error で失敗する2 つの @mcpServer class が同じ id を使用している各 MCP server に一意の id を使用する
deployment がツール名重複で失敗する1 つの server 内で 2 つの @mcpTool method が同じ名前を持ついずれかの method の名前を変更する
agent がツールを一度も呼び出さないツール description が user prompt と一致しないツールの機能を明確に示すよう description を書き直す
authorization が常に失敗するtoken または header の check が正しくないdebug のために McpAuthorizationRequest field を log する
OAuth がすべての request で 401 invalid_token を返すtoken の issuer または audience が integration と一致しないtoken が integration の Auth0 tenant からのものであることを確認します。audience は integration の client ID である必要があります
OAuth-enabled server がすべての request を拒否するoauth.integrationId が登録済み Auth0 integration を参照していないapp 用の Auth0 integration を登録し、その ID を使用する

ベストプラクティス​

明確なツール description を記述する​

description は、agent がツールを呼び出すタイミングを判断する主な手段です。ツールの機能と、返す情報を具体的に記述してください。

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

Bad: 'Gets product info'

Input schema を慎重に設計する​

  • ツールが parameter なしでは機能しない場合にのみ、parameter を required としてマークします
  • enum を使用して値を既知の集合に制限します
  • agent が提供すべき format を理解できるよう、各 property に明確な description field を記述します
  • 適切な JSON Schema type(string、number、boolean、array、object)を使用します

MCP server を保護する​

  • server が機密性の高い operation を公開する場合は、必ず @mcpAuthorizer を追加します
  • hardcoded value ではなく、保存済み secret に対して authorization token を検証します
  • authentication(誰が呼び出しているか)と authorization(何を実行できるか)の両方を確認します
  • Claude などの user-facing MCP client には、共有 token よりも OAuth authenticationを優先し、request.auth?.claims を介して authorizer 内で user ごとのルールを適用します

ツール input を検証する​

input schema は type constraint を提供しますが、edge case を処理するためにツール method 内でも input を検証してください。

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

ツールの焦点を絞る​

各ツールは 1 つのことを適切に実行するようにしてください。多くの operation を扱う単一のツールよりも、小さく焦点を絞った複数のツールを推奨します。これにより、agent はタスクに適したツールを選択しやすくなります。

agent を MCP server として公開する​

ここまででは、あなたの backend ツールから MCP server を構築しました。逆方向も利用可能です。backend code をまったく記述せずに、任意の Squid agent 自体を MCP server として公開できます。server は prompt を agent に転送する単一の ask ツールを公開するため、IDE assistant などの MCP client は、instructions、connectors、knowledge bases がすでに接続された 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 option​

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

endpoint を保護する​

oauthIntegrationId と requireApiKey のどちらも設定されていない場合、MCP endpoint は public です。agent ID を知っている人は誰でもこれを呼び出すことができ、呼び出しのたびに AI quota が消費されます。意図的に公開する agent でない限り、必ず少なくとも一方を設定してください。

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

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

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

oauthIntegrationId と requireApiKey の両方を設定すると、いずれかの credential を受け入れます。これにより、interactive client は OAuth で sign in でき、automated client は agent key を提示できます。

次のステップ​