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.
π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.
(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
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 |
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.
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
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>
);
}
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
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/*)
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/listto 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
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.
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.
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.
provider.instance.entity.action β for example, entra.prod.user.create means Entra ID provider, prod instance, user entity, create action.
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