MCP Tool
Connect to MCP servers to access external tools and resources.
Overview
MCP (Model Context Protocol) enables agents to:
Connect to MCP servers
Access server-provided tools
Use external resources
Integrate with third-party services
Extend agent capabilities dynamically
Basic Setup
Enable MCP
// Access MCP controller
var mcp = agent.MCPController;
// Connect to server
await mcp.ConnectAsync("server-name");Connecting to Servers
Connect to Local Server
// Connect to local MCP server
await mcp.ConnectAsync(
serverName: "filesystem",
transport: "stdio",
command: "node",
args: new[] { "path/to/mcp-server.js" }
);Connect to Remote Server
// Connect via HTTP
await mcp.ConnectAsync(
serverName: "api-server",
transport: "http",
url: "https://api.example.com/mcp"
);
// Connect via SSE (Server-Sent Events)
await mcp.ConnectAsync(
serverName: "sse-server",
transport: "sse",
url: "https://api.example.com/mcp/sse"
);Server Discovery
List Available Servers
var servers = await mcp.ListServersAsync();
foreach (var server in servers)
{
Debug.Log($"Server: {server.Name}");
Debug.Log($" Description: {server.Description}");
Debug.Log($" Version: {server.Version}");
}Check Server Status
var status = await mcp.GetServerStatusAsync("filesystem");
Debug.Log($"Status: {status.Connected}");
Debug.Log($"Tools: {status.ToolCount}");
Debug.Log($"Resources: {status.ResourceCount}");Using MCP Tools
List Server Tools
var tools = await mcp.ListToolsAsync("filesystem");
foreach (var tool in tools)
{
Debug.Log($"Tool: {tool.Name}");
Debug.Log($" Description: {tool.Description}");
Debug.Log($" Parameters: {string.Join(", ", tool.Parameters)}");
}Call MCP Tool
// MCP tools are automatically available to agent
await agent.SendAsync("List files in the Documents folder");
// Or call directly
var result = await mcp.CallToolAsync(
serverName: "filesystem",
toolName: "list_files",
arguments: new { path = "Documents" }
);Built-in MCP Servers
Filesystem Server
public class FilesystemMCP : MonoBehaviour
{
[SerializeField] private AgentBehaviour agent;
async void Start()
{
var mcp = agent.MCPController;
// Connect to filesystem server
await mcp.ConnectAsync(
serverName: "filesystem",
transport: "stdio",
command: "npx",
args: new[] { "-y", "@modelcontextprotocol/server-filesystem", "." }
);
Debug.Log("✓ Filesystem MCP connected");
}
public async void ListFiles(string directory)
{
await agent.SendAsync($"List all files in {directory}");
}
public async void ReadFile(string filePath)
{
await agent.SendAsync($"Read the contents of {filePath}");
}
}GitHub MCP
async void ConnectToGitHub()
{
var mcp = agent.MCPController;
await mcp.ConnectAsync(
serverName: "github",
transport: "stdio",
command: "npx",
args: new[] { "-y", "@modelcontextprotocol/server-github" },
env: new Dictionary<string, string>
{
{ "GITHUB_TOKEN", "your-token-here" }
}
);
// Now agent can interact with GitHub
await agent.SendAsync("List my GitHub repositories");
await agent.SendAsync("Create a new issue in my-repo");
}Database MCP
async void ConnectToDatabase()
{
var mcp = agent.MCPController;
await mcp.ConnectAsync(
serverName: "postgres",
transport: "stdio",
command: "npx",
args: new[] { "-y", "@modelcontextprotocol/server-postgres" },
env: new Dictionary<string, string>
{
{ "DATABASE_URL", "postgresql://localhost/gamedb" }
}
);
// Query database through agent
await agent.SendAsync("Show all players with score > 1000");
}OAuth Authentication
Handle OAuth Flow
public class MCPOAuthHandler : MonoBehaviour
{
[SerializeField] private AgentBehaviour agent;
void Start()
{
var mcp = agent.MCPController;
mcp.onOAuthRequired.AddListener(HandleOAuthRequest);
}
async void HandleOAuthRequest(OAuthRequest request)
{
Debug.Log($"OAuth required for: {request.ServerName}");
Debug.Log($"Auth URL: {request.AuthUrl}");
// Open browser for authorization
Application.OpenURL(request.AuthUrl);
// Wait for callback (implement your own callback handler)
string authCode = await WaitForAuthCallback();
// Complete OAuth
await agent.MCPController.CompleteOAuthAsync(
request.ServerName,
authCode
);
Debug.Log("✓ OAuth completed");
}
async UniTask<string> WaitForAuthCallback()
{
// Implement callback listener
// This could be a local HTTP server or deep link handler
await UniTask.Delay(1000);
return "auth-code-from-callback";
}
}Resource Access
Read Resources
// MCP servers can provide resources (files, data, etc.)
var resources = await mcp.ListResourcesAsync("filesystem");
foreach (var resource in resources)
{
Debug.Log($"Resource: {resource.Uri}");
Debug.Log($" Type: {resource.MimeType}");
Debug.Log($" Size: {resource.Size}");
}
// Read resource
var content = await mcp.ReadResourceAsync(
serverName: "filesystem",
resourceUri: "file:///path/to/file.txt"
);Approval System
Request Approval
public class MCPApprovalHandler : MonoBehaviour
{
[SerializeField] private AgentBehaviour agent;
[SerializeField] private GameObject approvalDialogPrefab;
void Start()
{
var mcp = agent.MCPController;
mcp.onApprovalRequired.AddListener(HandleApprovalRequest);
}
async void HandleApprovalRequest(ApprovalRequest request)
{
Debug.Log($"Approval needed: {request.ToolName}");
Debug.Log($"Server: {request.ServerName}");
Debug.Log($"Arguments: {request.Arguments}");
// Show approval dialog
var dialog = Instantiate(approvalDialogPrefab);
var handler = dialog.GetComponent<ApprovalDialog>();
bool approved = await handler.ShowAndWaitAsync(request);
if (approved)
{
await mcp.ApproveToolCallAsync(request.Id);
Debug.Log("✓ Tool call approved");
}
else
{
await mcp.DenyToolCallAsync(request.Id);
Debug.Log("✗ Tool call denied");
}
}
}Custom MCP Server
Create Simple Server
// mcp-server.js
const { MCPServer } = require('@modelcontextprotocol/sdk');
const server = new MCPServer({
name: 'game-server',
version: '1.0.0'
});
// Register tool
server.registerTool({
name: 'get_player_stats',
description: 'Get player statistics',
parameters: {
playerId: { type: 'string', required: true }
},
handler: async (args) => {
// Fetch player stats
const stats = await getPlayerStats(args.playerId);
return JSON.stringify(stats);
}
});
server.listen();Connect to Custom Server
async void ConnectToGameServer()
{
var mcp = agent.MCPController;
await mcp.ConnectAsync(
serverName: "game-server",
transport: "stdio",
command: "node",
args: new[] { "path/to/mcp-server.js" }
);
// Use custom tool
await agent.SendAsync("Get stats for player123");
}Error Handling
Connection Errors
try
{
await mcp.ConnectAsync("filesystem");
}
catch (MCPConnectionException ex)
{
Debug.LogError($"Failed to connect: {ex.Message}");
if (ex.Reason == "server_not_found")
{
Debug.Log("Server not installed. Install with: npm install -g @modelcontextprotocol/server-filesystem");
}
else if (ex.Reason == "timeout")
{
Debug.Log("Connection timed out. Server may not be responding.");
}
}Tool Execution Errors
agent.onToolCallError.AddListener((toolCall, error) =>
{
if (toolCall.Type == ToolType.MCP)
{
Debug.LogError($"MCP tool failed: {error}");
// Parse MCP error
if (error.Contains("unauthorized"))
{
ShowMessage("Authorization required. Please authenticate.");
}
else if (error.Contains("not_found"))
{
ShowMessage("Resource not found.");
}
}
});Server Management
Disconnect Server
await mcp.DisconnectAsync("filesystem");
Debug.Log("✓ Disconnected from filesystem server");Reconnect
public async UniTask ReconnectServer(string serverName)
{
await mcp.DisconnectAsync(serverName);
await UniTask.Delay(1000);
await mcp.ConnectAsync(serverName);
}Monitor Connection
void Start()
{
var mcp = agent.MCPController;
mcp.onServerConnected.AddListener(server =>
{
Debug.Log($"✓ Connected: {server.Name}");
});
mcp.onServerDisconnected.AddListener(server =>
{
Debug.LogWarning($"✗ Disconnected: {server.Name}");
// Auto-reconnect
ReconnectServer(server.Name);
});
}Best Practices
1. Validate Server Availability
async UniTask<bool> IsServerAvailable(string serverName)
{
try
{
await mcp.ConnectAsync(serverName);
await mcp.DisconnectAsync(serverName);
return true;
}
catch
{
return false;
}
}2. Handle Missing Dependencies
async void ConnectWithDependencyCheck()
{
if (!IsNodeInstalled())
{
ShowMessage("Node.js is required for MCP servers");
return;
}
try
{
await mcp.ConnectAsync("filesystem");
}
catch (MCPConnectionException ex)
{
if (ex.Reason == "server_not_found")
{
ShowInstallInstructions();
}
}
}
bool IsNodeInstalled()
{
try
{
var process = Process.Start(new ProcessStartInfo
{
FileName = "node",
Arguments = "--version",
RedirectStandardOutput = true,
UseShellExecute = false
});
process.WaitForExit();
return process.ExitCode == 0;
}
catch
{
return false;
}
}3. Cache Server Connections
public class MCPConnectionCache : MonoBehaviour
{
private HashSet<string> connectedServers = new();
public async UniTask<bool> EnsureConnected(string serverName)
{
if (connectedServers.Contains(serverName))
{
return true;
}
try
{
await mcp.ConnectAsync(serverName);
connectedServers.Add(serverName);
return true;
}
catch
{
return false;
}
}
}Complete Example
using UnityEngine;
using Glitch9.AIDevKit.Agents;
using Cysharp.Threading.Tasks;
public class MCPManager : MonoBehaviour
{
[SerializeField] private AgentBehaviour agent;
private MCPController mcp;
private HashSet<string> connectedServers = new();
async void Start()
{
await SetupMCP();
}
async UniTask SetupMCP()
{
mcp = agent.MCPController;
// Listen for events
mcp.onServerConnected.AddListener(OnServerConnected);
mcp.onServerDisconnected.AddListener(OnServerDisconnected);
mcp.onOAuthRequired.AddListener(HandleOAuth);
mcp.onApprovalRequired.AddListener(HandleApproval);
// Connect to servers
await ConnectFilesystem();
await ConnectGitHub();
Debug.Log("✓ MCP ready");
}
async UniTask ConnectFilesystem()
{
try
{
await mcp.ConnectAsync(
serverName: "filesystem",
transport: "stdio",
command: "npx",
args: new[] { "-y", "@modelcontextprotocol/server-filesystem", "." }
);
Debug.Log("✓ Filesystem connected");
}
catch (Exception ex)
{
Debug.LogError($"Filesystem connection failed: {ex.Message}");
}
}
async UniTask ConnectGitHub()
{
string token = PlayerPrefs.GetString("GitHubToken", "");
if (string.IsNullOrEmpty(token))
{
Debug.LogWarning("GitHub token not set");
return;
}
try
{
await mcp.ConnectAsync(
serverName: "github",
transport: "stdio",
command: "npx",
args: new[] { "-y", "@modelcontextprotocol/server-github" },
env: new Dictionary<string, string>
{
{ "GITHUB_TOKEN", token }
}
);
Debug.Log("✓ GitHub connected");
}
catch (Exception ex)
{
Debug.LogError($"GitHub connection failed: {ex.Message}");
}
}
void OnServerConnected(MCPServer server)
{
Debug.Log($"✓ Connected: {server.Name}");
connectedServers.Add(server.Name);
}
void OnServerDisconnected(MCPServer server)
{
Debug.LogWarning($"✗ Disconnected: {server.Name}");
connectedServers.Remove(server.Name);
// Auto-reconnect after delay
ReconnectAfterDelay(server.Name);
}
async void ReconnectAfterDelay(string serverName)
{
await UniTask.Delay(5000);
try
{
await mcp.ConnectAsync(serverName);
Debug.Log($"✓ Reconnected: {serverName}");
}
catch (Exception ex)
{
Debug.LogError($"Reconnect failed: {ex.Message}");
}
}
async void HandleOAuth(OAuthRequest request)
{
Debug.Log($"OAuth required: {request.ServerName}");
Application.OpenURL(request.AuthUrl);
// Wait for user to complete OAuth
string authCode = await WaitForOAuthCallback();
if (!string.IsNullOrEmpty(authCode))
{
await mcp.CompleteOAuthAsync(request.ServerName, authCode);
Debug.Log("✓ OAuth completed");
}
}
async void HandleApproval(ApprovalRequest request)
{
Debug.Log($"Approval needed: {request.ToolName}");
// For demo, auto-approve safe operations
bool approved = IsSafeOperation(request);
if (approved)
{
await mcp.ApproveToolCallAsync(request.Id);
}
else
{
await mcp.DenyToolCallAsync(request.Id);
}
}
bool IsSafeOperation(ApprovalRequest request)
{
// Define safe operations
string[] safeOps = { "read_file", "list_files", "get_*" };
return safeOps.Any(op =>
request.ToolName.StartsWith(op.Replace("*", "")));
}
async UniTask<string> WaitForOAuthCallback()
{
// Implement OAuth callback handling
await UniTask.Delay(1000);
return ""; // Return auth code from callback
}
void OnApplicationQuit()
{
// Disconnect all servers
foreach (var server in connectedServers)
{
mcp.DisconnectAsync(server).Forget();
}
}
}Next Steps
Last updated