DAY 21 WEEK 5: ADVANCED

MCP & Tool Integration

Master the Model Context Protocol (MCP), understand Loopback MCP for auto-generated tools, configure MCP Gateway authorization, and build AI-ready tool integrations.

AI agents need tools to be useful. But how do agents discover what tools exist, understand their parameters, and invoke them securely? The Model Context Protocol (MCP) is the answerβ€”a standard way for AI to interact with external capabilities. EmpowerNow's MCP implementation automatically exposes all your connectors and workflows as tools that any AI agent can use.

πŸ“‹Learning Objectives

  • Understand MCP (Model Context Protocol) and its role in AI agent architectures
  • Learn MCP primitives: Tools, Resources, and Prompts
  • Understand how Loopback MCP auto-generates tools from ServiceConfigs
  • Configure MCP Gateway for secure, authorized tool access
  • Make tool discovery and invocation requests via JSON-RPC
  • Integrate MCP tools in Experience plugins

πŸ”ŒWhat is MCP?

The Model Context Protocol (MCP) is an open standard that defines how AI models interact with external tools, resources, and prompts. Think of it as a universal adapter that lets any AI agent (Claude, GPT, local models) discover and use tools without custom integrations.

πŸ”§ Tools

Actions the AI can invoke (create user, run workflow, query database)

πŸ“ Resources

Data the AI can read (files, database records, configurations)

πŸ“ Prompts

Pre-built prompt templates the AI can use (playbooks, workflows)

πŸ”’ Security

OAuth authentication and PDP authorization for every operation

Why MCP Matters

Without MCP With MCP
Custom integrations per AI provider One standard, all AI providers
Hardcoded tool lists in prompts Dynamic discovery at runtime
No authorization control PDP-enforced permissions per tool
Scattered documentation Self-describing schemas

πŸ—οΈEmpowerNow MCP Architecture

EmpowerNow implements MCP with multiple server types that AI agents can connect to through a central gateway.

flowchart TB subgraph CLIENTS["AI Agents & Plugins"] A1["Claude Agent"] A2["Custom Agent"] A3["Experience Plugin"] end GW["MCP Gateway
(Authentication + Authorization)"] subgraph SERVERS["MCP Servers"] LB["Loopback MCP
(CRUDService)"] IDP["IdP MCP
(Identity Tools)"] EXT["External MCP
(Vendor Tools)"] end subgraph BACKEND["Backend Systems"] SC["ServiceConfigs
(Connectors + Workflows)"] PDP["PDP
(Authorization)"] end A1 --> GW A2 --> GW A3 --> GW GW --> PDP GW --> LB GW --> IDP GW --> EXT LB --> SC style GW fill:#f43f5e22,stroke:#f43f5e style PDP fill:#a855f722,stroke:#a855f7 style LB fill:#10b98122,stroke:#10b981

Key Components

Component Role Port
MCP Gateway Central proxy for all MCP traffic. Validates tokens, checks PDP permissions, routes to servers. 8004
Loopback MCP Auto-generates tools from ServiceConfigs connectors and workflows. Served via BFF at /api/crud/mcp/. Via BFF (8004)
IdP MCP Identity management tools: user lookup, token info, delegation status. 8002
External MCP Third-party MCP servers (vendor tools, custom integrations). Various

πŸ”„Loopback MCP

Loopback MCP is EmpowerNow's magic: it automatically converts your ServiceConfigs connectors and workflows into MCP tools that any AI agent can discover and invoke.

How Tools Are Generated

flowchart LR SC["ServiceConfigs
YAML Files"] --> |"Parse"| LB["Loopback MCP"] LB --> |"Generate"| T1["entra.prod.user.create"] LB --> |"Generate"| T2["entra.prod.user.search"] LB --> |"Generate"| T3["workflow.access_request"] LB --> |"Generate"| T4["ldap.corp.group.add_member"] style SC fill:#00d9ff22,stroke:#00d9ff style LB fill:#10b98122,stroke:#10b981

Tool Naming Convention

Tool Type Format Example
Connector Actions provider.instance.entity.action entra.prod.user.create
Workflows workflow.name workflow.access_request
Promoted Workflows promoted.workflow_name promoted.user_onboarding
πŸ“ Name Limits

Tool names are limited to 50 characters. Longer names get a deterministic hash suffix (e.g., entra.prod.very_long_na_abc123).

Tool Schema Example

Each tool exposes a JSON schema describing its inputs and outputs:

{
  "name": "entra.prod.user.create",
  "description": "Create a new user in Entra ID (Azure AD)",
  "inputSchema": {
    "type": "object",
    "properties": {
      "display_name": {
        "type": "string",
        "description": "User's display name"
      },
      "user_principal_name": {
        "type": "string",
        "description": "UPN (e.g., user@domain.com)"
      },
      "mail_nickname": {
        "type": "string",
        "description": "Mail alias"
      },
      "department": {
        "type": "string",
        "description": "Department name"
      }
    },
    "required": ["display_name", "user_principal_name", "mail_nickname"]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "id": { "type": "string" },
      "user_principal_name": { "type": "string" },
      "created_date_time": { "type": "string" }
    }
  }
}

πŸ›‘οΈMCP Gateway

The MCP Gateway is the security perimeter for all MCP traffic. Every tool invocation passes through it for authentication and authorization.

sequenceDiagram participant Agent participant Gateway as MCP Gateway participant PDP participant MCP as Loopback MCP participant Backend as Backend System Agent->>Gateway: tools/call (Bearer Token) Gateway->>Gateway: Validate OAuth Token Gateway->>PDP: Can user invoke 'entra.prod.user.create'? PDP-->>Gateway: βœ“ Permit (or βœ— Deny) alt Permitted Gateway->>MCP: Forward tool invocation MCP->>Backend: Execute action Backend-->>MCP: Result MCP-->>Gateway: Tool result Gateway-->>Agent: Success response else Denied Gateway-->>Agent: 403 Forbidden end

Authorization Rules

PDP policies control which users/agents can invoke which tools:

# Example PDP policy for MCP tools
applications:
  mcp-tools:
    resources:
      tool:
        actions:
          - invoke
        rules:
          # Allow admins to invoke any tool
          - effect: permit
            condition: subject.roles contains "admin"
          
          # Allow helpdesk to invoke user tools only
          - effect: permit
            condition: |
              subject.roles contains "helpdesk" and
              resource.id starts_with "entra.prod.user"
          
          # Deny by default
          - effect: deny
⚠️ Production Rule

Never expose CRUDService MCP endpoints (/mcp/*) directly to the network. All MCP traffic must go through the MCP Gateway for security.

πŸ“‘JSON-RPC Protocol

MCP uses JSON-RPC 2.0 for communication. Here are the key methods:

Tool Discovery

# List all available tools
curl -X POST https://bff.self.empowernow.ai/api/crud/mcp/jsonrpc \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "req-001",
    "method": "tools/list",
    "params": {}
  }'

# Response
{
  "jsonrpc": "2.0",
  "id": "req-001",
  "result": {
    "tools": [
      {
        "name": "entra.prod.user.create",
        "description": "Create a new user in Entra ID",
        "inputSchema": { ... }
      },
      {
        "name": "workflow.access_request",
        "description": "Submit an access request workflow",
        "inputSchema": { ... }
      }
    ]
  }
}

Tool Invocation

# Invoke a tool
curl -X POST https://bff.self.empowernow.ai/api/crud/mcp/jsonrpc \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "req-002",
    "method": "tools/call",
    "params": {
      "name": "workflow.access_request",
      "arguments": {
        "resource_id": "resource-123",
        "justification": "Need access for Q4 project",
        "duration": "7d"
      }
    }
  }'

# Response
{
  "jsonrpc": "2.0",
  "id": "req-002",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Workflow started: run-abc123"
      }
    ],
    "isError": false
  }
}

Progressive Tool Discovery

For large tool catalogs, use progressive discovery to reduce token usage:

# Step 1: List domains (minimal tokens)
{
  "method": "tools/call",
  "params": {
    "name": "registry.list_domains",
    "arguments": { "minimal": true }
  }
}

# Step 2: List tools in a domain
{
  "method": "tools/call",
  "params": {
    "name": "registry.list_tools",
    "arguments": {
      "domain": "identity-tools",
      "detail": "descriptions"  // minimal | descriptions | full
    }
  }
}

# Step 3: Get full schema for specific tool
{
  "method": "tools/call",
  "params": {
    "name": "registry.get_tool_schema",
    "arguments": { "tool_id": "entra.prod.user.create" }
  }
}

πŸ”§Plugin SDK Integration

Experience plugins can invoke MCP tools through the SDK:

Using sdk.mcp

// src/hooks/useMcpTool.ts
const sdk = (globalThis as any).pluginSdk;

export async function listTools(): Promise<McpTool[]> {
  const response = await sdk.api.fetch('/api/crud/mcp/tools/list');
  const data = await response.json();
  return data.tools;
}

export async function invokeTool(
  toolName: string,
  args: Record<string, any>
): Promise<McpResult> {
  const response = await sdk.api.fetch('/api/crud/mcp/jsonrpc', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: crypto.randomUUID(),
      method: 'tools/call',
      params: {
        name: toolName,
        arguments: args
      }
    })
  });
  
  const result = await response.json();
  
  if (result.error) {
    throw new Error(result.error.message);
  }
  
  return result.result;
}

Tool Selector Component

// src/components/ToolSelector.tsx
const React = (globalThis as any).React;
const { useState, useEffect } = React;

interface McpTool {
  name: string;
  description: string;
  inputSchema: object;
}

export function ToolSelector({ onSelect }: { onSelect: (tool: McpTool) => void }) {
  const [tools, setTools] = useState<McpTool[]>([]);
  const [loading, setLoading] = useState(true);
  const [filter, setFilter] = useState('');

  useEffect(() => {
    listTools()
      .then(setTools)
      .catch(console.error)
      .finally(() => setLoading(false));
  }, []);

  const filtered = tools.filter(t =>
    t.name.toLowerCase().includes(filter.toLowerCase()) ||
    t.description.toLowerCase().includes(filter.toLowerCase())
  );

  if (loading) return <div>Loading tools...</div>;

  return (
    <div>
      <input
        type="text"
        placeholder="Filter tools..."
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      />
      <ul>
        {filtered.map(tool => (
          <li key={tool.name} onClick={() => onSelect(tool)}>
            <strong>{tool.name}</strong>
            <span>{tool.description}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}
πŸ’‘ plugins.yaml Permissions

Remember to add MCP endpoints to your plugin's API allowlist:

permissions:
  api:
    - method: GET
      path: /api/crud/mcp/tools/list
    - method: POST
      path: /api/crud/mcp/jsonrpc

πŸ‘οΈVirtual Views & Scoped Discovery

For large tool catalogs, Virtual Views filter tools by provider, tenant, or tags. Instead of listing all tools, agents can discover only what's relevant.

View Endpoints

Endpoint Description
/mcp/tools/list All tools (global view)
/mcp/entra/tools/list Only Entra ID tools
/mcp/workflows/tools/list Only workflow tools
/mcp/{view}/jsonrpc JSON-RPC scoped to a view

Defining Virtual Views

# ServiceConfigs/CRUDService/config/mcp_virtual_servers.yaml
virtual_servers:
  - name: "entra"
    path_prefix: "/entra"
    filters:
      source: ["system"]
      provider: ["entra"]
  
  - name: "workflows"
    path_prefix: "/workflows"
    filters:
      source: ["workflow"]
  
  - name: "prompts"
    path_prefix: "/prompts"
    filters:
      source: ["prompt"]
  
  - name: "ad"
    path_prefix: "/ad"
    filters:
      source: ["system"]
      provider: ["ad", "ldap"]

View-Scoped JSON-RPC Example

# List only Entra tools (fewer tokens for AI)
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{"limit":50}}' \
  https://bff.self.empowernow.ai/api/crud/mcp/entra/jsonrpc

# Invoke a tool within the entra view
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0","id":"2","method":"tools/call",
    "params": {
      "name":"entra.cont.account.get_by_id",
      "arguments":{"SystemIdentifier":"00000000-0000-0000-0000-000000000000"}
    }
  }' \
  https://bff.self.empowernow.ai/api/crud/mcp/entra/jsonrpc
πŸ“ Health Tools Always Included

The system.health and system_health tools are always included in every view, even if they don't match the filters.

πŸ“MCP Prompts

Prompts are pre-built prompt templates that return messages for AI agents. They don't execute anythingβ€”they plan and explain, while workflows do the actual work.

Prompts vs Tools vs Workflows

Concept What It Does Output
Prompt Returns messages for the AI to use in planning Messages + optional Plan IR
Tool Executes an action directly Action result
Workflow Orchestrates multiple steps with approvals Workflow run ID + status

Prompt Definition

# ServiceConfigs/CRUDService/config/prompts/identity_onboard.yaml
name: identity.onboard
title: "Onboard New Identity"
description: "Plan the onboarding of a new user across identity systems"
arguments:
  - name: email
    description: "User's email address"
    required: true
  - name: targets
    description: "Target systems (entra, ad, ldap)"
    required: false
    default: ["entra"]
messages:
  - role: system
    content: |
      You are an identity provisioning assistant.
      Create a Plan IR for onboarding {{ email }} to {{ targets }}.
      Always include an approval step for new account creation.
  - role: user
    content: |
      Please create an onboarding plan for {{ email }}.

Using Prompts via JSON-RPC

# List available prompts
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"prompts/list"}' \
  https://bff.self.empowernow.ai/api/crud/mcp/jsonrpc

# Get a prompt with arguments (returns rendered messages)
curl -s -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0","id":"2","method":"prompts/get",
    "params": {
      "name": "identity.onboard",
      "arguments": {
        "email": "jane.doe@example.com",
        "targets": ["entra", "ad"]
      }
    }
  }' \
  https://bff.self.empowernow.ai/api/crud/mcp/jsonrpc

πŸ”Authentication & Scopes

MCP endpoints require OAuth tokens with specific scopes.

Required Scopes

Operation Required Scope
List tools (tools/list) mcp.tools.discovery
Invoke tools (tools/call) mcp.tools.invoke
List prompts (prompts/list) mcp.tools.discovery
Get prompt (prompts/get) mcp.tools.discovery

BFF MCP Proxy

The BFF proxies MCP requests and exempts them from CSRF/DPoP requirements:

# BFF MCP routes (prefix: /api/crud/mcp)
POST /api/crud/mcp/jsonrpc              # JSON-RPC to global view
POST /api/crud/mcp/{view}/jsonrpc       # JSON-RPC to specific view
GET  /api/crud/mcp/{view}/jsonrpc       # SSE bridge for Cursor/streamable clients

Cursor IDE Configuration

To use EmpowerNow MCP from Cursor IDE, create or edit ~/.cursor/mcp.json:

{
  "mcpServers": {
    "crud-mcp": {
      "type": "streamable-http",
      "url": "https://bff.self.empowernow.ai/api/crud/mcp/jsonrpc",
      "headers": {
        "Authorization": "Bearer <ACCESS_TOKEN_WITH_mcp_scopes>",
        "Content-Type": "application/json"
      }
    }
  }
}

Then in Cursor, open the MCP tools panel, enable crud-mcp, and ask: "List tools from crud-mcp."

πŸ“šTool Catalogue Behavior

The Tool Catalogue is automatically built at startup and refreshes when configurations change.

Catalogue Sources

  • Built-ins: From config/tools.yaml
  • Loopback MCP: Auto-generated from ServiceConfigs (systems + workflows)
  • External MCP: Discovered from config/mcp_endpoints.yaml

Auto-Refresh Triggers

The catalogue refreshes automatically when you:

  • Create, update, or delete a system definition (/config/systems/*)
  • Create, update, or delete a workflow (/workflows/*)
  • Replace the tools config (PUT /config/tools)
  • Add or modify prompts (/config/prompts/*)
πŸ’‘ Hot-Reload Notification

When prompts change, the server emits notifications/prompts/list_changed so connected clients (like Cursor) can refresh their tool list without reconnecting.

Environment Variables

Variable Default Description
MCP_MAX_TOOLS 80 Max tools returned in one list
MCP_TOOL_NAME_CAP 50 Max characters in tool name
MCP_DUPLICATE_POLICY fail Handle duplicate names: fail|keep_first|keep_last|drop
MCP_ENABLE_ROUTER false Enable short names with oneOf schema routing

πŸ”Troubleshooting

Tool Not Found

Symptom: {"error": {"code": -32601, "message": "Tool not found: xyz"}}

Causes & Fixes:

  • Tool name typo: Use tools/list to verify exact tool names
  • Connector not configured: Check ServiceConfigs for the connector definition
  • Tool filtered by PDP: The tool may exist but you lack permission to see it

403 Forbidden on Tool Invocation

Symptom: {"error": {"code": 403, "message": "Access denied"}}

Debug:

# Check PDP evaluation directly
curl -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": { "type": "user", "id": "current" },
    "resource": { "type": "tool", "id": "entra.prod.user.create" },
    "action": { "name": "invoke" },
    "context": { "reason_admin": true }
  }'

Invalid Arguments Error

Symptom: {"error": {"code": -32602, "message": "Invalid params"}}

Fix: Fetch the tool schema and validate your arguments match the inputSchema:

# Get full tool schema
{
  "method": "tools/call",
  "params": {
    "name": "registry.get_tool_schema",
    "arguments": { "tool_id": "your-tool-name" }
  }
}

MCP Server Unreachable

Symptom: {"error": {"code": -32603, "message": "Internal error"}}

Debug:

# Check MCP server health
curl https://bff.self.empowernow.ai/api/crud/health

# Check Gateway logs
docker logs mcp-gateway --tail 100

❓Knowledge Check

1 What are the three MCP primitives? β–Ό

Tools (actions AI can invoke), Resources (data AI can read), and Prompts (pre-built prompt templates). Tools are the most commonly used primitive in EmpowerNow.

2 What is Loopback MCP and why is it valuable? β–Ό

Loopback MCP automatically generates MCP tools from your ServiceConfigs connectors and workflows. It's valuable because you don't need to write any codeβ€”every connector action and workflow you define automatically becomes an AI-invocable tool with proper schemas.

3 Why must all MCP traffic go through the Gateway? β–Ό

The MCP Gateway provides: (1) OAuth token validation, (2) PDP authorization checks per tool invocation, (3) Audit logging, and (4) Rate limiting. Without it, any authenticated user could invoke any tool without authorization checks.

4 What's the tool naming format for a connector action? β–Ό

provider.instance.entity.action β€” for example, entra.prod.user.create means Entra ID provider, prod instance, user entity, create action.

5 How do you invoke an MCP tool from a plugin? β–Ό
const response = await sdk.api.fetch('/api/crud/mcp/jsonrpc', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: crypto.randomUUID(),
    method: 'tools/call',
    params: { name: 'workflow.access_request', arguments: {...} }
  })
});

πŸ“Day 21 Checkpoint

  • Understand MCP's role in AI agent tool integration
  • Know the three MCP primitives (Tools, Resources, Prompts)
  • Understand how Loopback MCP auto-generates tools from ServiceConfigs
  • Know the tool naming convention (provider.instance.entity.action)
  • Understand MCP Gateway's authorization role
  • Can discover tools using tools/list
  • Can invoke tools using tools/call