mcp_loopback_howto
Loopback MCP tools in CRUDService – Admin How‑To
What you get
- In‑process MCP server under
/mcpthat exposes tools generated from ServiceConfigs (systems + workflows). - Single source of truth: tools derive from your YAML configs (no separate MCP processes required).
- ToolCatalogue auto‑merge: built‑ins from
config/tools.yaml+ loopback MCP + any external MCP endpoints inconfig/mcp_endpoints.yaml.
Endpoints
-
GET
/mcp/tools/list- Returns
{ "tools": [ { name, description, inputSchema, source, metadata } ] } - Requires scope:
mcp.tools.discovery
- Returns
-
POST
/mcp/jsonrpc(JSON‑RPC 2.0)- Supported methods:
tools/list→ returns same structure as REST listtools/invoke→ executes named tool; body:{ id, method: "tools/invoke", params: { name, arguments } }tools/call→ alias fortools/invoke(compatibility)
- Requires scope:
mcp.tools.invoke
- Supported methods:
Auth and scopes
- Reuses CRUDService authn/z. Provide
Authorization: Bearer <token>. - Scopes enforced:
mcp.tools.discoveryfor listingmcp.tools.invokefor invoking
- PDP
enable_authorizationflag is read fromconfig/pdp.yaml.
Tool sources and naming
- Default namespaced:
provider.instance.basefor system tools (e.g.,entra.cont.account.get_by_id,ldap.av.account.get_by_dn). Workflows useworkflow.<name>. - Router (optional): short names with
oneOfinput schema; enable viaMCP_ENABLE_ROUTER=true(invocation routing remains namespaced). - JSON Schema for inputs is auto‑derived; falls back to a permissive object when unspecified.
Catalogue behavior
- On startup, CRUDService builds a ToolCatalogue with:
- Built‑ins from
config/tools.yaml - Loopback MCP tools (when
ToolCatalogueis constructed with aconfig_loader– default insrc/main.py) - External MCP endpoints in
config/mcp_endpoints.yaml(merged)
- Built‑ins from
- After mutations, catalogue refreshes automatically:
- Systems: create/update/delete (
/config/systems/*) - Workflows: create/update/delete (
/workflows/*) - Tools config replace:
PUT /config/tools
- Systems: create/update/delete (
Observability
- Structured logs with
correlation_idand durations:mcp_tools_list_start/mcp_tools_list_completemcp_jsonrpc_call/mcp_jsonrpc_complete- Errors/denied:
mcp_jsonrpc_error,mcp_jsonrpc_forbidden
Examples
Architecture (transport) overview
List tools (REST)
curl -H "Authorization: Bearer $TOKEN" \
https://crud.service.local/mcp/tools/list
Response (truncated):
{ "tools": [
{ "name": "entra.cont.account.get_by_id", "source": "system", "inputSchema": {"type":"object",...} },
{ "name": "workflow.onboard_user", "source": "workflow", "inputSchema": {"type":"object",...} }
]}
List tools (JSON‑RPC)
Health (JSON‑RPC)
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","id":"1","method":"tools/call",
"params":{"name":"system.health","arguments":{"verbose":true}}
}' \
https://crud.service.local/mcp/jsonrpc
Also supported: "name":"system_health".
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/list"}' \
https://crud.service.local/mcp/jsonrpc
Invoke a workflow tool
WF=workflow_onboard_user
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","id":"2","method":"tools/invoke",
"params": {"name":"'"$WF"'","arguments":{"username":"jdoe","email":"jdoe@example.com"}}
}' \
https://crud.service.local/mcp/jsonrpc
Response (loopback MVP):
{ "jsonrpc": "2.0", "id": "2", "result": {
"content": [{ "type": "text", "text": "{\"accepted\": true, \"workflow\": \"onboard_user\", \"params\": {\"username\":\"jdoe\",\"email\":\"jdoe@example.com\"}}" }]
}}
Invoke a system command tool
TOOL=ldap_account_create
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0","id":"3","method":"tools/invoke",
"params": {"name":"'"$TOOL"'","arguments":{"uid":"jdoe","cn":"John Doe"}}
}' \
https://crud.service.local/mcp/jsonrpc
Response (truncated):
{ "jsonrpc": "2.0", "id": "3", "result": { "content": [ { "type": "text", "text": "{...}" } ] } }
Rollout tips
- Start in non‑prod; monitor the structured log events above.
- Ensure tokens used by agent UIs carry the minimal scopes required.
- External MCP endpoints remain optional; add them via
config/mcp_endpoints.yaml.
Troubleshooting
- 403 or JSON‑RPC error
-32001: missing scope (mcp.tools.discoveryormcp.tools.invoke). - Empty list: confirm ServiceConfigs are mounted and readable; check logs for loader validation errors.
- Stale list: trigger a change or call an admin mutation to refresh; or restart service.
-32601 Method not found: usetools/call(alias) or check method casing.- Health tool missing: set
MCP_LOOPBACK_ONLY_HEALTH=false(or unset) and redeploy.
Quick start
- Set
MCP_MAX_TOOLS=80on CRUDService. - Add minimal
mcpblocks to one Azure/Auth0 GET command and one workflow. - Call tools/list; verify tools plus
system.health/system_health. - Invoke health via
tools/callwith{ verbose: true }. - Invoke an enabled Azure/Auth0 tool with required params.
Cursor setup (copy/paste)
Create or edit ~/.cursor/mcp.json (Windows: %USERPROFILE%\.cursor\mcp.json).
{
"mcpServers": {
"crud-mcp": {
"type": "streamable-http",
"url": "https://api.ocg.labs.empowernow.ai/api/crud/mcp/jsonrpc",
"headers": {
"Authorization": "Bearer <ACCESS_TOKEN_WITH_scopes_mcp.tools.discovery_mcp.tools.invoke>",
"Content-Type": "application/json"
}
}
}
}
Then in Cursor:
- Open the MCP tools panel → enable
crud-mcpif disabled - Ask: “List tools from crud-mcp” (Cursor sends
tools/list) - Ask: “Run the CRUD MCP health check with verbose true” (Cursor sends
tools/calltosystem.health)
Tips
- If you see 403/CSRF, ensure the URL points to the BFF proxy and you’re sending a Bearer token.
- If Cursor uses
tools/call, the server handles it;tools/invokealso works.
See also
- Quickstart:
../how-to/mcp-quickstart.md - Cursor integration:
../how-to/mcp-cursor-integration.md - API Reference:
./mcp_api_reference.md - BFF MCP proxy routing:
../../bff/devops/mcp_proxy_routing.md - Opt‑in authors guide:
../how-to/mcp-opt-in-authors-guide.md