MCP Integration

Model Context Protocol (MCP) integration for advanced tool capabilities.

What is MCP?

MCP (Model Context Protocol) is an open protocol that enables:

  • Secure tool access via OAuth

  • User approval workflows for sensitive operations

  • Standardized tool interfaces across platforms

  • External service integration (Slack, GitHub, etc.)

Configuration

Enable MCP

// In AgentSettings
settings.EnableMcpTools = true;

// MCP approval timeout
behaviour.McpApprovalTimeoutSeconds = 30;

MCP Controller

// Check if agent has MCP tools
bool hasMcp = agent.HasMcpTool;

// Access MCP controller
var mcpController = agent.mcpController;

OAuth Token Providers

Implement Token Provider

public class CustomOAuthProvider : IMcpOAuthTokenProvider
{
    public async UniTask<string> GetAccessTokenAsync(
        string service,
        string[] scopes,
        CancellationToken ct = default)
    {
        // Implement OAuth flow
        // 1. Check if token exists
        // 2. If not, redirect to OAuth page
        // 3. Get authorization code
        // 4. Exchange for access token
        // 5. Return token
        
        return accessToken;
    }
}

Register Provider

var tokenService = new McpAccessTokenService();
tokenService.RegisterProvider("slack", new SlackOAuthProvider());
tokenService.RegisterProvider("github", new GitHubOAuthProvider());

// Pass to agent
var agent = new Agent(
    settings: settings,
    behaviour: behaviour,
    tokenService: tokenService
);

Approval Handlers

Built-in Approval Dialog

// Agent prompts user automatically
behaviour.McpApprovalBehaviour = McpApprovalBehaviour.Prompt;

Custom Approval Handler

agent.onMcpToolApprovalRequested += async (toolCall, timeoutSeconds) =>
{
    // Show custom UI
    bool approved = await ShowApprovalDialogAsync(
        toolName: toolCall.Function.Name,
        arguments: toolCall.Function.Arguments,
        timeout: timeoutSeconds
    );
    
    return approved;
};

Approval Timeout

// Set timeout in AgentBehaviour
behaviour.McpApprovalTimeoutSeconds = 30;

// Or at runtime
agent.McpApprovalTimeoutSeconds = 60;

Access Token Service

Token Caching

public class TokenCache
{
    private Dictionary<string, CachedToken> tokens = new();
    
    public async UniTask<string> GetOrRefreshTokenAsync(
        string service,
        string[] scopes)
    {
        if (tokens.TryGetValue(service, out var cached))
        {
            if (!cached.IsExpired)
            {
                return cached.Token;
            }
        }
        
        // Refresh token
        var newToken = await RefreshTokenAsync(service, scopes);
        tokens[service] = new CachedToken(newToken);
        return newToken;
    }
}

Token Refresh

public async UniTask<string> RefreshTokenAsync(
    string service,
    string refreshToken)
{
    // Use refresh token to get new access token
    var response = await HttpClient.PostAsync(
        $"https://{service}/oauth/token",
        new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["grant_type"] = "refresh_token",
            ["refresh_token"] = refreshToken
        })
    );
    
    var result = await response.Content.ReadAsStringAsync();
    var token = JsonConvert.DeserializeObject<TokenResponse>(result);
    
    return token.AccessToken;
}

Complete Example

using UnityEngine;
using Glitch9.AIDevKit.Agents;
using Glitch9.AIDevKit.Agents.McpTools;

public class McpManager : MonoBehaviour
{
    [SerializeField] private AgentBehaviour agent;
    
    void Start()
    {
        // Enable MCP
        agent.HasMcpTool = true;
        agent.McpApprovalTimeoutSeconds = 30;
        
        // Setup approval handler
        agent.onMcpToolApprovalRequested += OnApprovalRequested;
        
        // Setup token providers
        SetupTokenProviders();
    }
    
    void SetupTokenProviders()
    {
        var tokenService = new McpAccessTokenService();
        
        // Register OAuth providers
        tokenService.RegisterProvider("slack", new SlackOAuthProvider());
        tokenService.RegisterProvider("github", new GitHubOAuthProvider());
        
        // Use with agent (if creating manually)
        // var agent = new Agent(settings, behaviour, tokenService: tokenService);
    }
    
    async UniTask<bool> OnApprovalRequested(
        ToolCall toolCall,
        int timeoutSeconds)
    {
        Debug.Log($"Approval requested for: {toolCall.Function.Name}");
        
        // Show approval dialog
        bool approved = await ShowApprovalDialog(
            toolName: toolCall.Function.Name,
            description: GetToolDescription(toolCall),
            timeoutSeconds: timeoutSeconds
        );
        
        return approved;
    }
    
    async UniTask<bool> ShowApprovalDialog(
        string toolName,
        string description,
        int timeoutSeconds)
    {
        // Show custom UI dialog
        var dialog = Instantiate(approvalDialogPrefab);
        dialog.SetToolInfo(toolName, description);
        dialog.SetTimeout(timeoutSeconds);
        
        // Wait for user decision
        bool approved = await dialog.WaitForDecisionAsync();
        
        Destroy(dialog.gameObject);
        return approved;
    }
    
    string GetToolDescription(ToolCall toolCall)
    {
        // Generate human-readable description
        return $"Execute {toolCall.Function.Name} with arguments: " +
               JsonConvert.SerializeObject(toolCall.Function.Arguments);
    }
}

// OAuth Provider Example
public class SlackOAuthProvider : IMcpOAuthTokenProvider
{
    public async UniTask<string> GetAccessTokenAsync(
        string service,
        string[] scopes,
        CancellationToken ct = default)
    {
        // 1. Check for cached token
        if (HasValidToken(service))
        {
            return GetCachedToken(service);
        }
        
        // 2. Start OAuth flow
        string authUrl = BuildAuthUrl(service, scopes);
        Application.OpenURL(authUrl);
        
        // 3. Wait for callback
        string authCode = await WaitForAuthCodeAsync(ct);
        
        // 4. Exchange for token
        string accessToken = await ExchangeCodeForTokenAsync(authCode);
        
        // 5. Cache and return
        CacheToken(service, accessToken);
        return accessToken;
    }
}

Best Practices

1. Secure Token Storage

// Use secure storage (not PlayerPrefs)
public class SecureTokenStorage
{
    public void SaveToken(string service, string token)
    {
        // Use platform-specific secure storage
        #if UNITY_IOS
            // Use Keychain
        #elif UNITY_ANDROID
            // Use KeyStore
        #else
            // Use encrypted file
        #endif
    }
}

2. Handle Token Expiration

if (tokenResponse.ExpiresIn.HasValue)
{
    var expiryTime = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn.Value);
    CacheTokenWithExpiry(service, token, expiryTime);
}

3. User-Friendly Approvals

// Clear description
string description = $"Allow agent to {action} in {service}?";

// Show required permissions
ShowPermissionsList(requiredScopes);

// Provide option to trust always
bool trustAlways = await ShowTrustAlwaysOption();

Next Steps

Last updated